CKS — Falco Rules
Falco Rules
Falco’s power comes entirely from its rules. A rule defines what to detect (condition), how to describe it (output), and how severe it is (priority). On the CKS exam, you will need to read, write, and modify Falco rules. This is non-negotiable — expect at least one question that requires creating or editing a custom rule.
Rule Files and Load Order
Falco loads rule files in the order specified in /etc/falco/falco.yaml:
rules_file:
- /etc/falco/falco_rules.yaml # Default rules — DO NOT EDIT
- /etc/falco/falco_rules.local.yaml # Custom rules and overrides
Load order matters. Rules in later files override rules with the same name in earlier files. This is how you customize Falco without touching the default rule file.
| File | Purpose | Edit? |
|---|---|---|
/etc/falco/falco_rules.yaml |
300+ default rules from Falco project | Never |
/etc/falco/falco_rules.local.yaml |
Your custom rules and overrides | Yes |
Rule Syntax
Every Falco rule has this structure:
- rule: <name>
desc: <human-readable description>
condition: <Sysdig filter expression — when this rule fires>
output: <format string — what the alert says>
priority: <severity level>
tags: [<tag1>, <tag2>]
enabled: true
Fields explained:
| Field | Required | Description |
|---|---|---|
rule |
Yes | Unique name for this rule |
desc |
Yes | Description of what the rule detects |
condition |
Yes | Boolean filter expression using Falco’s field syntax |
output |
Yes | Alert message template with field placeholders |
priority |
Yes | EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG |
tags |
No | Categorization tags (e.g., container, network, filesystem) |
enabled |
No | Set to false to disable (default: true) |
Priority Levels
EMERGENCY → System is unusable
ALERT → Action must be taken immediately
CRITICAL → Critical conditions
ERROR → Error conditions
WARNING → Warning conditions (most common for CKS)
NOTICE → Normal but significant conditions
INFORMATIONAL → Informational messages
DEBUG → Debug-level messages
Condition Field — Falco Filter Syntax
Conditions use Sysdig filter syntax. These are the building blocks:
System call classes:
| Filter | Meaning |
|---|---|
spawned_process |
A new process was created (execve) |
open_read |
A file was opened for reading |
open_write |
A file was opened for writing |
container |
Event happened inside a container |
evt.type = execve |
Specific syscall type |
evt.type in (open, openat) |
Syscall is one of these types |
Field references:
| Field | Meaning |
|---|---|
proc.name |
Process name |
proc.pname |
Parent process name |
proc.cmdline |
Full command line |
fd.name |
File descriptor name (file path) |
container.id |
Container ID |
container.name |
Container name |
container.image.repository |
Container image name |
k8s.ns.name |
Kubernetes namespace |
k8s.pod.name |
Kubernetes pod name |
user.name |
User who triggered the event |
evt.type |
Syscall type |
Operators:
| Operator | Example |
|---|---|
= |
proc.name = bash |
!= |
container.id != host |
in |
proc.name in (bash, sh, zsh) |
contains |
fd.name contains /etc |
startswith |
fd.name startswith /etc/ |
pmatch |
fd.name pmatch (/etc/shadow, /etc/passwd) |
and |
container and proc.name = bash |
or |
proc.name = bash or proc.name = sh |
not |
not container |
Output Field — Alert Format
The output string supports field placeholders:
output: >
Shell spawned in container
(user=%user.name container=%container.name image=%container.image.repository
command=%proc.cmdline namespace=%k8s.ns.name pod=%k8s.pod.name)
Common placeholders:
| Placeholder | Value |
|---|---|
%user.name |
Username |
%container.name |
Container name |
%container.id |
Container ID |
%container.image.repository |
Image name |
%proc.name |
Process name |
%proc.cmdline |
Full command |
%fd.name |
File path |
%k8s.ns.name |
Namespace |
%k8s.pod.name |
Pod name |
%evt.time |
Event timestamp |
Macros — Reusable Condition Snippets
Macros are named condition fragments you can reference in rules:
- macro: container
condition: container.id != host
- macro: shell_procs
condition: proc.name in (bash, sh, zsh, csh, ksh, dash, ash)
- macro: sensitive_files
condition: >
fd.name startswith /etc and
(fd.name in (/etc/shadow, /etc/passwd, /etc/sudoers)
or fd.name startswith /etc/sudoers.d/)
- macro: spawned_process
condition: evt.type = execve and evt.dir = <
Using macros in a rule:
- rule: Shell in container
desc: Detect shell spawned inside a container
condition: spawned_process and container and shell_procs
output: "Shell spawned (user=%user.name container=%container.name)"
priority: WARNING
Lists — Reusable Value Sets
Lists define sets of values you can reference in conditions:
- list: trusted_images
items: [nginx, redis, postgres, mycompany/app]
- list: shell_binaries
items: [bash, sh, zsh, csh, ksh, dash, ash]
- list: sensitive_file_paths
items: [/etc/shadow, /etc/passwd, /etc/sudoers, /etc/pam.d]
- list: allowed_k8s_namespaces
items: [kube-system, monitoring, falco]
Using lists:
- rule: Untrusted image started
desc: Container started with an image not in the trusted list
condition: >
container and evt.type = container and
not container.image.repository in (trusted_images)
output: "Untrusted image (image=%container.image.repository pod=%k8s.pod.name)"
priority: WARNING
Default Rules Every CKS Candidate Must Know
These rules ship with Falco and are the most likely to appear on the exam:
1. Terminal Shell in Container
- rule: Terminal shell in container
desc: >
A shell was spawned by a program in a container with an attached terminal.
condition: >
spawned_process and container and shell_procs and proc.tty != 0
output: >
A shell was spawned in a container with an attached terminal
(user=%user.name user_loginuid=%user.loginuid container_id=%container.id
container_name=%container.name image=%container.image.repository
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline
terminal=%proc.tty pid=%proc.pid)
priority: NOTICE
tags: [container, shell, mitre_execution]
Triggered by: kubectl exec -it <pod> -- /bin/bash
2. Read Sensitive File Untouched
- rule: Read sensitive file untouched
desc: Detect reads to sensitive files (e.g., /etc/shadow)
condition: >
open_read and sensitive_files and container
and not proc.name in (allowed_sensitive_readers)
output: >
Sensitive file read (user=%user.name command=%proc.cmdline
file=%fd.name container=%container.name image=%container.image.repository)
priority: WARNING
tags: [filesystem, mitre_credential_access]
Triggered by: cat /etc/shadow inside a container
3. Write Below /etc
- rule: Write below etc
desc: Detect writes to /etc directory inside a container
condition: >
open_write and container and fd.name startswith /etc
output: >
File written below /etc (user=%user.name command=%proc.cmdline
file=%fd.name container=%container.name)
priority: ERROR
tags: [filesystem, mitre_persistence]
Triggered by: Writing or modifying files in /etc inside a container
4. Privilege Escalation via setuid/setgid
- rule: Non sudo setuid
desc: Detect setuid calls by non-sudo programs
condition: >
evt.type = setuid and evt.dir = < and container
and not proc.name in (sudo, su)
output: >
Privilege escalation via setuid (user=%user.name command=%proc.cmdline
container=%container.name)
priority: CRITICAL
tags: [users, mitre_privilege_escalation]
5. Contact K8S API Server From Container
- rule: Contact K8S API Server From Container
desc: Detect connections to the Kubernetes API server from a container
condition: >
evt.type = connect and container
and fd.sip.name = "kubernetes.default.svc.cluster.local"
output: >
Container contacted K8S API server
(user=%user.name container=%container.name image=%container.image.repository
connection=%fd.name namespace=%k8s.ns.name pod=%k8s.pod.name)
priority: WARNING
tags: [network, k8s, mitre_discovery]
Writing Custom Rules — Complete Examples
Custom Rule 1: Detect Package Manager Execution
- list: package_managers
items: [apt, apt-get, yum, dnf, apk, pip, pip3, npm]
- rule: Package manager execution in container
desc: Detect package manager running inside a container (potential supply chain attack)
condition: >
spawned_process and container
and proc.name in (package_managers)
output: >
Package manager executed in container
(user=%user.name command=%proc.cmdline container=%container.name
image=%container.image.repository namespace=%k8s.ns.name)
priority: ERROR
tags: [container, software_mgmt, mitre_execution]
Custom Rule 2: Detect Reverse Shell
- rule: Reverse shell in container
desc: Detect reverse shell execution patterns
condition: >
spawned_process and container
and ((proc.name = bash and proc.cmdline contains "/dev/tcp")
or (proc.name = python and proc.cmdline contains "socket")
or (proc.name = nc and proc.args contains "-e")
or (proc.name = ncat and proc.args contains "--exec"))
output: >
Reverse shell detected (user=%user.name command=%proc.cmdline
container=%container.name namespace=%k8s.ns.name pod=%k8s.pod.name)
priority: CRITICAL
tags: [container, network, mitre_execution]
Custom Rule 3: Detect Crypto Mining
- list: crypto_mining_domains
items: [pool.minergate.com, xmr.pool, moneropool, cryptonight]
- list: crypto_mining_processes
items: [xmrig, minerd, cpuminer, minergate, cryptonight]
- rule: Crypto mining activity
desc: Detect cryptocurrency mining processes or connections
condition: >
(spawned_process and container and proc.name in (crypto_mining_processes))
or (container and evt.type = connect and fd.sip.name pmatch (crypto_mining_domains))
output: >
Crypto mining detected (user=%user.name command=%proc.cmdline
container=%container.name namespace=%k8s.ns.name)
priority: CRITICAL
tags: [container, crypto, mitre_execution]
Custom Rule 4: Detect Service Account Token Access
- rule: Service account token read
desc: Detect process reading Kubernetes service account token
condition: >
open_read and container
and fd.name startswith /var/run/secrets/kubernetes.io/serviceaccount
and not proc.name in (curl, wget, kube-proxy)
output: >
Service account token accessed (user=%user.name command=%proc.cmdline
file=%fd.name container=%container.name namespace=%k8s.ns.name)
priority: WARNING
tags: [container, k8s, mitre_credential_access]
Custom Rule 5: Detect Namespace-Specific Shell Access
- rule: Shell in production namespace
desc: Detect shell access in production namespace
condition: >
spawned_process and container and shell_procs
and proc.tty != 0
and k8s.ns.name = "production"
output: >
PRODUCTION ALERT — Shell spawned (user=%user.name container=%container.name
pod=%k8s.pod.name command=%proc.cmdline)
priority: CRITICAL
tags: [container, shell, production]
Overriding Default Rules
You can override a default rule by specifying the same rule name in your custom file with override:
Disable a default rule:
# In /etc/falco/falco_rules.local.yaml
- rule: Terminal shell in container
enabled: false
Change priority of a default rule:
- rule: Terminal shell in container
override:
priority: replace
priority: CRITICAL
Append to a rule’s condition:
- rule: Terminal shell in container
override:
condition: append
condition: and not k8s.ns.name = "debug-allowed"
This adds an exception: shells in the debug-allowed namespace will not trigger the rule.
Append to a macro:
- macro: shell_procs
override:
condition: append
condition: or proc.name = fish
Append to a list:
- list: trusted_images
override:
items: append
items: [mycompany/custom-app, mycompany/sidecar]
Rule Debugging
Validate rules before restarting Falco:
sudo falco --validate /etc/falco/falco_rules.local.yaml
Run Falco in dry-run mode to test rules:
sudo falco --dry-run -r /etc/falco/falco_rules.local.yaml
List all loaded rules:
sudo falco --list
List rules matching a specific tag:
sudo falco --list --tag container
Putting It All Together — Complete Custom Rules File
Here is a complete /etc/falco/falco_rules.local.yaml:
# Custom lists
- list: trusted_images
items: [nginx, redis, mycompany/app-v2]
- list: package_managers
items: [apt, apt-get, yum, dnf, apk, pip, npm]
# Custom macros
- macro: in_production
condition: k8s.ns.name = "production"
# Override — escalate shell detection severity
- rule: Terminal shell in container
override:
priority: replace
priority: CRITICAL
# Custom rule — detect package managers
- rule: Package manager in container
desc: Detect package install attempt inside running container
condition: >
spawned_process and container
and proc.name in (package_managers)
output: >
Package manager executed (user=%user.name cmd=%proc.cmdline
container=%container.name image=%container.image.repository
ns=%k8s.ns.name pod=%k8s.pod.name)
priority: ERROR
tags: [container, software_mgmt]
# Custom rule — detect writes to /bin or /sbin
- rule: Write to binary directory
desc: Detect writes to executable directories in a container
condition: >
open_write and container
and (fd.name startswith /bin or fd.name startswith /sbin
or fd.name startswith /usr/bin or fd.name startswith /usr/sbin)
output: >
Binary directory write (user=%user.name cmd=%proc.cmdline
file=%fd.name container=%container.name ns=%k8s.ns.name)
priority: CRITICAL
tags: [container, filesystem, mitre_persistence]
After editing, always restart Falco:
sudo systemctl restart falco
journalctl -u falco --since "1 minute ago" | head -20
Practice Questions
Question 1
Where should you place custom Falco rules? Why should you never edit the default rules file?
Show Answer
Place custom rules in /etc/falco/falco_rules.local.yaml.
Never edit /etc/falco/falco_rules.yaml because:
- It is overwritten when Falco is upgraded
- Custom rules in the local file automatically override rules with the same name in the default file
- This separation makes it clear what is default vs. custom
Question 2
Write a Falco rule that detects when curl or wget is executed inside any container.
Show Answer
- rule: Network tool in container
desc: Detect curl or wget execution in a container
condition: >
spawned_process and container
and proc.name in (curl, wget)
output: >
Network tool executed (user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository
namespace=%k8s.ns.name pod=%k8s.pod.name)
priority: WARNING
tags: [container, network]
Add to /etc/falco/falco_rules.local.yaml and restart Falco:
sudo systemctl restart falco
Question 3
How do you disable the default “Terminal shell in container” rule without deleting it?
Show Answer
Add the following to /etc/falco/falco_rules.local.yaml:
- rule: Terminal shell in container
enabled: false
Restart Falco:
sudo systemctl restart falco
Question 4
What is the difference between a Falco macro and a Falco list?
Show Answer
| Feature | Macro | List |
|---|---|---|
| What it holds | A reusable condition expression (boolean logic) | A reusable set of values |
| Syntax | condition: <filter expression> |
items: [val1, val2, val3] |
| Used in | condition field of rules or other macros |
in (list_name) inside conditions |
| Example | container.id != host |
[bash, sh, zsh] |
Macro example:
- macro: container
condition: container.id != host
List example:
- list: shell_binaries
items: [bash, sh, zsh]
Question 5
You want the “Terminal shell in container” rule to NOT fire for pods in the kube-system namespace. How do you achieve this?
Show Answer
Use the override and append mechanism in /etc/falco/falco_rules.local.yaml:
- rule: Terminal shell in container
override:
condition: append
condition: and not k8s.ns.name = "kube-system"
This appends the exception to the existing condition without replacing the entire rule. Restart Falco after editing:
sudo systemctl restart falco
Next: Syscall Monitoring →