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.
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.
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.
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.
- rule: Terminal shell in containerdesc: An interactive shell was opened inside a containercondition: >spawned_process and containerand shell_procs and proc.tty != 0and container.image.repository != ""output: >Shell spawned in a container(user=%user.name user_loginuid=%user.loginuid container=%container.nameimage=%container.image.repository shell=%proc.name parent=%proc.pnamecmdline=%proc.cmdline k8s_pod=%k8s.pod.name k8s_ns=%k8s.ns.name)priority: WARNINGtags: [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/.
# 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_imagesitems: [registry.acme.dev/platform/toolbox, registry.acme.dev/ci/debug]- rule: Terminal shell in containercondition: 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.
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: Okkubectl exec -it -n shop deploy/api -- shkubectl 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.
config:slack:webhookurl: https://hooks.slack.com/services/… # triage channelminimumpriority: warningpagerduty:routingkey: …minimumpriority: error # Critical and Error pageloki:hostport: http://loki.monitoring:3100minimumpriority: 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.
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