Style Guide of Falco Rules
Style Guide
Before diving in, read the sections on Falco rules Basics and Condition Syntax. Also, check out existing Falco Rules for best practices in writing rules.
In addition, the resources under references/rules provide complementary information. We highly recommend regularly revisiting each guide to stay up-to-date with the latest advancements of Falco.
A rule declaration is represented as a YAML object consisting of multiple keys. The suggested order for these keys is as follows:
- rule:
desc:
condition:
output:
priority:
tags:
... other keys if applicable in no particular order
Naming
Choose a concise title that summarizes the essence of the rule's purpose. Rule's name must start with an upper-case letter. For macro
and list
, use lowercase_separated_by_underscores, for example, kernel_module_load
.
Description
Aligning with Falco's Rules Maturity Framework, it is encouraged to not just include a longer description of what the rule detects but also to give advice on how to tune this rule and reduce possible noise. If applicable, elaborate on how to correlate the rule with other rules or data sources for incident response. However, keep them concise. The description should end with a period.
Condition Syntax
These recommendations prioritize performance impact while maintaining a consistent style for better understanding and easier customization. This approach ensures more manageable maintenance of the rules in the long run.
We explain the high-level principles using example rules or snippets.
- Each upstream Falco rule must include an
evt.type
filter; otherwise, you will get a warning.
Rule no_evttype: warning (no-evttype):
proc.name=foo
did not contain any evt.type restriction, meaning that it will run for all event types.
This has a significant performance penalty. Consider adding an evt.type restriction if possible.
- Prioritize the
evt.type
filter first; otherwise, you will get a warning. Falco buckets filters perevt.type
for efficient rules matching through applying the rule's Abstract Syntax Tree (AST) to relevant event types only. A nice side effect is better readability as well.
Rule evttype_not_equals: warning (trailing-evttype):
evt.type!=execve
does not have all evt.type restrictions at the beginning of the condition,
or uses a negative match (i.e. "not"/"!=") for some evt.type restriction.
This has a performance penalty, as the rule can not be limited to specific event types.
Consider moving all evt.type restrictions to the beginning of the rule and/or
replacing negative matches with positive matches if possible.
To maintain performance, avoid mixing unrelated event types in one rule. Typically, only variants should be mixed together, for example:
evt.type in (open, openat, openat2)
.The best practice and requirement for upstream rules are to only define positive
evt.type
expressions. Usingevt.type!=open
, for example, would monitor each of the Supported Syscalls, resulting in a significant performance penalty. For more information, read the Adaptive Syscalls Selection in Falco blog post.After the
evt.type
filter, place your positive expression filters to efficiently eliminate the most events step by step. An exception to this rule is thecontainer
macro, which can quickly eliminate many events. Therefore, the guiding principle of "divide and conquer" commonly used in database query recommendations, also applies to Falco's filter statements.
- macro: open_write
condition: (evt.type in (open,openat,openat2) and evt.is_open_write=true and fd.typechar=`f` and fd.num>=0)
...
- macro: container
condition: (container.id != host)
...
- rule: Detect release_agent File Container Escapes
...
condition:
open_write and container and fd.name endswith release_agent and (user.uid=0 or thread.cap_effective contains CAP_DAC_OVERRIDE) and thread.cap_effective contains CAP_SYS_ADMIN
...
- Effective Falco rules should now already be in a good state. Additionally, use exclusionary statements mostly to filter out common anti-patterns and noise. Often, these statements are based on profiling. You will notice that many upstream rules provide an empty template macro for this purpose, which you can customize.
- macro: spawned_process
condition: (evt.type in (execve, execveat) and evt.dir=<)
...
- list: known_drop_and_execute_containers
items: []
- rule: Drop and execute new binary in container
...
condition:
spawned_process
and container
and proc.is_exe_upper_layer=true
and not container.image.repository in (known_drop_and_execute_containers)
...
- Use existing macros for reusability purposes, if applicable (e.g.
spawned_process
macro). - Exercise caution when dealing with complicated nested statements in Falco rules, and ensure you use parentheses consistently to achieve the desired correct behavior. Remember, using too many parentheses does not cause any harm.
- To avoid grammatical syntax errors or sub-optimal performance, refrain from combining
or
statements with negation. Instead, useor
statements only for positive filters.
- macro: minerpool_https
condition: (fd.sport="443" and fd.sip.name in (https_miner_domains))
...
condition: ... and ((minerpool_http) or (minerpool_https) or (minerpool_other))
- Furthermore, it is preferred to use
and not
to consistently negate a positive sub-expression. - Avoid double-negation.
condition: ... and not fd.snet in (rfc_1918_addresses)
- For operations involving string comparison,
startswith
orendswith
should be preferred overcontains
whenever possible, as they are more efficient. - Whenever possible, try to avoid making a rule expression too long.
- Upstream rules shall not contain any
exceptions
to ensure simpler rules and facilitate better adoption.
Output Fields
Each rule must include output fields.
For the output fields, expect that each Falco release typically exposes new Supported Output Fields that can help you write more expressive rules and/or add more context to a rule for incident response.
Building upon the guide around writing rules with respect to Falco Outputs, when considering upstreaming your rule, core output fields relevant for this rule must be included. At the same time, we try to keep them to a minimum, and adopters can add more output fields as they see fit.
For each rule include the critical fields listed below related to process and user information, as well as the actual event type. For example, the tty
field (terminal) can help determine if the process ran in an interactive shell. Additionally, examining the exepath alongside the process name provides insights into whether the binary might be located in more suspicious folders like tmp
. Understanding the direct parent process is vital for basic process lineage analysis.
evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty
For rules involving only spawned_process
, please also include %evt.arg.flags
in the output fields. If a rule involves multiple syscalls beyond spawned_process
, do not include %evt.arg.flags
.
exe_flags=%evt.arg.flags
General tip: Refer to the Event Table Definitions for the evt.arg.*
fields for each supported syscall, for which we extract and parse the arguments.
For network-related rules include:
connection=%fd.name lport=%fd.lport rport=%fd.rport fd_type=%fd.type fd_proto=fd.l4proto
For file-related rules include:
file=%fd.name
... and additional specialized fields from the raw args if applicable, such as newpath=%evt.arg.newpath
for non-file descriptor events like symlinking or renaming. Alternatively, you can explore more recent fs.path.*
variants to simplify the consistent logging of file or directory paths, even for non-file descriptor events. Previously, tapping into the raw args as described above was required for such events.
When writing a rule for container workloads, you should include a special placeholder field that will resolve to relevant information automatically fetched from the container runtime socket.
%container.info
Read more on the Output Formatting page. For example, when you run Falco with -pk
, the placeholder will resolve to:
container_id=%container.id container_image=%container.image.repository container_image_tag=%container.image.tag container_name=%container.name k8s_ns=%k8s.ns.name k8s_pod_name=%k8s.pod.name
- Supported Output Fields
container.*
retrieved from the container runtime socket - Supported Output Fields
k8s.*
also retrieved from the container runtime socket
All of these fields are incredibly crucial for effective incident response and play a vital role in determining the workload owner.
For specialized use cases, generic fields such as %container.ip
or %container.cni.json
can be further helpful for incident response, especially concerning non-network syscall related alerts in Kubernetes. These fields can be correlated, for example, with network proxy logs. Additionally, for certain rules, it can be important to traverse the parent process lineage for up to 7 levels. In some cases, instead of relying solely on the process name, it might be more effective to traverse the exepath, for example, proc.aexepath[2]
. The process name and executable of the session leader (%proc.sname
, %proc.sid.*
) and process group leader (%proc.vpgid.*
), or other specific process fields such as proc.is_exe_upper_layer
, proc.is_exe_from_memfd
or proc.is_vpgid_leader
, can also hold considerable generic value for each rule. However, it is up to you as an adopter to decide.
We kindly ask you to add fields related to IDs later in your customization process to keep the upstream Falco output fields to a minimum. This is because there are many ID-related fields, such as %proc.pid %proc.ppid %proc.vpid %proc.pvpid %proc.sid %proc.vpgid ...
. You can explore the -p
option for this purpose and add these fields to each rules' output fields.
Falco also supports outputting the output as a resolved string. Therefore, use a sentence style, first concisely re-iterating the rule's purpose, and then including the output field in parentheses after the =
character, with its meaning explained before the =
character, adhering to the snake_case variable naming convention.
output: "Read monitored file via directory traversal (file=%fd.name fileraw=%fd.nameraw gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty %container.info)"
Priority
Please refer to the relevant reference/rules section and the Basic Rules Guide for more information.
When considering upstreaming the rule to The Falco Project, the priority
level shall not be set to DEBUG
and instead shall be at a minimum of INFO
.
Tags
Tags include various categories to convey relevant information about the rule.
According to the Falco Rules Maturity Framework, the first tag in the tags list must always indicate the maturity of the rule. The Rules Repo contains concrete guidance on how to categorize a rule when considering upstreaming the rule to The Falco Project.
maturity_stable
maturity_incubating
maturity_sandbox
maturity_deprecated
Next, the tags must indicate for what workloads this rule is relevant. Add host
and container
if the rule works for any event. You can include additional tags to specify the rule's type, such as process
, network
, k8s
, aws
, etc.
When considering upstreaming your rule, we expect the Mitre Attack phase followed by the best Tactic or Technique, whichever is the best fit. This information is used to create the Rules Overview Document of Falco's predefined rules and also help the Falco adoption process.
Lastly, if the rule is relevant for a compliance use case, please add the corresponding PCI_DSS_*
or NIST_*
tag, referring to the Validating NIST Requirements with Falco and PCI/DSS Controls with Falco blog posts and rules contributing criteria outlined in the Rules Repo.
tags: [maturity_incubating, host, container, filesystem, mitre_defense_evasion, NIST_800-53_AU-10]
Additional Information
Rule Types and Robustness
Some rules are more specific signatures, while others focus on behavior-based detection. When testing rules, it's essential to consider not only if the rule catches the intended attack or how much noise it could generate but also its robustness. Robustness refers to how easily an attacker can bypass the detection by making minor changes to their payload or approach. Exploring different approaches to catch an attack can help identify the most effective detection method.
Rules Loading
Refer to the up-to-date description in the falco.yaml file for rules_files
to understand in which order rules are loaded. Keep in mind that Falco by default applies rules per event type on a "first match wins" basis. Starting from Falco Release 0.36.0, you have the option to modify the configuration to rule_matching: all
. This change ensures that rules sharing the same event type cannot override each other, preventing inconsistent logging. Be aware, though, that this modification may lead to increased CPU usage.
Contributing Your Falco Rules
Refer to the Contributing page and the How to Share Your Falco Rules guide.
Was this page helpful?
Let us know! You feedback will help us to improve the content and to stay in touch with our users.
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.