Falco rules that catch real attacks (and skip the noise)

Default rulesets page you for everything. Tune Falco to the syscalls that matter and route the rest to a dashboard.

Nov 25, 2025·Updated ·6 min readAdvanced·By SecOpsLog · documentation-verified

Falco sees every syscall on the node and evaluates each one against a list of rules, which is exactly enough power to page someone every ninety seconds about package managers, health checks and a developer's debug shell. The default ruleset is a catalogue of what could be interesting, and it has to be cut down to what is interesting here before any of it reaches a pager. The method is the same one firewalls settled on decades ago: one precise rule, a narrow exception with an owner, and a route that matches the priority.

Syscall to alert

The probe sees everything; the rules decide what becomes an event; the routing decides what becomes a page. Most tuning is in the middle box, and most regret comes from skipping the last one.

Syscall eBPF / driver Falco rules engine condition → priority → output Alert SIEM / pager 1 Observe open, execve, connect… container + k8s metadata high volume, low context alone 2 Tune rules exceptions for known noise priority: NOTICE → CRITICAL custom rules for your apps 3 Route alerts stdout → sidecar → SIEM page only on CRITICAL noise kills the program Default rules are a starting point. Untuned Falco is a pager firehose. syscall → rules → alert → response
Observe — syscallsTune — rulesAlert — route

One rule about behaviour, not about strings

"A shell with a TTY was spawned inside a container" describes a behaviour: it fires for bash, sh and busybox ash alike, and it does not fire for a shell that a build script runs without a terminal. Matching on process lineage and container context, and printing the fields a responder needs (who, which container, which parent, the full command line), is what separates a rule from a grep. The rule below is close to the stock one, and every term in it is worth understanding before a second rule is added.

/etc/falco/rules.d/10-shell.yaml
- rule: Terminal shell in container
desc: An interactive shell was opened inside a container
condition: >
spawned_process and container
and shell_procs and proc.tty != 0
and container.image.repository != ""
output: >
Shell spawned in a container
(user=%user.name user_loginuid=%user.loginuid container=%container.name
image=%container.image.repository shell=%proc.name parent=%proc.pname
cmdline=%proc.cmdline k8s_pod=%k8s.pod.name k8s_ns=%k8s.ns.name)
priority: WARNING
tags: [container, shell, mitre_execution]

Narrow it with override, not with the mute button

The first week produces the exceptions: a toolbox image the platform team opens shells in on purpose, a CI debug container. Disabling the rule for their sake blinds you to the same behaviour everywhere else. The supported way to narrow a rule you did not write is the override section, which appends to (or replaces) the condition of an existing rule from a later file; the append: true key older guides use is deprecated and is removed in Falco 1.0. Because overrides apply in load order, the file that narrows a stock rule has to load after falco_rules.yaml, which is the default for rules.d/.

/etc/falco/rules.d/20-shell-exceptions.yaml
# images the platform team opens interactive shells in on purpose
# owner: platform-oncall reviewed: 2026-09-12 next review: 2026-12-12
- list: allowed_shell_images
items: [registry.acme.dev/platform/toolbox, registry.acme.dev/ci/debug]
- rule: Terminal shell in container
condition: and not container.image.repository in (allowed_shell_images)
override:
condition: append

Falco also supports an exceptions block on rules, which expresses the same idea as named field tuples and is what the stock rules use internally; for a handful of images a list in a condition is easier to read in review. Either way the exception carries an owner and a review date in a comment, lives in version control, and is reviewed on the same cadence as firewall rules, because each entry is a hole opened on purpose.

bash — validate the rules, then watch the one event that matters
falco --validate /etc/falco/rules.d/10-shell.yaml --validate /etc/falco/rules.d/20-shell-exceptions.yaml
/etc/falco/rules.d/10-shell.yaml: Ok
/etc/falco/rules.d/20-shell-exceptions.yaml: Ok
kubectl exec -it -n shop deploy/api -- sh
kubectl logs -n falco ds/falco --since=1m | grep "Shell spawned"
10:42:01.318 Warning Shell spawned in a container (user=root user_loginuid=-1 container=api image=registry.acme.dev/shop/api shell=sh parent=runc cmdline=sh k8s_pod=api-7d9c4f-2x8kq k8s_ns=shop)
kubectl exec -it -n platform deploy/toolbox -- bash
(no event: the toolbox image is in allowed_shell_images)

Route by priority, and start in Audit

Falco emits one stream; Falcosidekick fans it out, and its routing by priority is where a page is separated from a dashboard tile. Critical and Error go to the on-call channel, Warning to a triage channel a human reads within the hour, Notice and below to Loki or the SIEM for correlation. Before any priority reaches a pager, run the ruleset for a week with every output going to storage only, and read what it would have paged about: that list is the exception file above, and it is also the list of rules to drop from the default set because nobody would act on them.

falcosidekick values (excerpt)
config:
slack:
webhookurl: https://hooks.slack.com/services/… # triage channel
minimumpriority: warning
pagerduty:
routingkey: …
minimumpriority: error # Critical and Error page
loki:
hostport: http://loki.monitoring:3100
minimumpriority: debug # everything, for correlation

On the node side, run Falco as a DaemonSet with the modern eBPF probe, which is the default driver: it is compiled into the Falco binary (CO-RE), needs a kernel with BPF ring buffer and BTF support, and needs no kernel module or per-kernel build. It also runs with a small capability set (CAP_SYS_BPF, CAP_SYS_PERFMON, CAP_SYS_RESOURCE, CAP_SYS_PTRACE) rather than as a fully privileged Pod, which is worth insisting on for a component whose job is to notice privilege.

Drop the rules that would never lead to an action
The stock rules for package management in containers, writes below /etc and outbound connections are useful only in an environment where those are abnormal. Where a base image installs packages at start-up they are noise, and the honest choice is to drop them instead of paging about them. The measure of the ruleset is not how many rules it has but how many events led to an action last month.

Falco tells you a door was opened; the doors themselves are the subject of container escape paths, and closing them at admission is what makes a Falco event rare enough to page on. When an event should trigger an action rather than a person, Falco Talon can kill or isolate the Pod from the same alert stream, which is the point at which one high-confidence rule earns automation.

Go deeper in a courseKubernetes security & hardeningRuntime detection, admission control and the rest of the CKS surface.View course

Related posts

Quick reference