Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for DevOps Security & Hardening By James Joyner IV · · 8 min read

Writing Custom Falco Rules That Catch Real Attacks (Not Just Noise)

Falco's default rules are a starting point, not a strategy. Here's how to write custom detection rules tuned to your environment without drowning in false positives.

  • #security
  • #hardening
  • #falco
  • #runtime-security
  • #detection
  • #kubernetes

Falco watches the syscalls and Kubernetes events flowing through your hosts and screams when something looks like an attack — a shell spawned in a container, a sensitive file read, an unexpected outbound connection. The default ruleset is genuinely useful out of the box. But left alone, it does one of two things in a real environment: it floods you with alerts until you mute it, or it stays quiet on the attacks that actually matter to your stack. The value is in the custom rules you write.

I’ve tuned Falco for a few clusters now. Here’s how to write rules that catch real attacks and don’t bury you.

The anatomy of a Falco rule

A rule has a condition (when to fire), an output (what to say), and a priority. Conditions are built from fields — proc.name, fd.name, container.image.repository, evt.type — combined with and/or. The single most important construct is the list and the macro, because they’re how you keep rules readable and reusable:

- list: trusted_shell_images
  items: ["acme/debug-toolbox", "busybox"]

- macro: shell_spawned
  condition: >
    spawned_process and proc.name in (shell_binaries)

- rule: Shell in production container
  desc: Detect an interactive shell spawned in a prod workload
  condition: >
    shell_spawned
    and container
    and k8s.ns.name = "prod"
    and not container.image.repository in (trusted_shell_images)
  output: >
    Shell spawned in prod container
    (user=%user.name container=%container.name
     image=%container.image.repository cmd=%proc.cmdline)
  priority: WARNING
  tags: [shell, container, mitre_execution]

That not ... in (trusted_shell_images) clause is the difference between a useful rule and an alert storm. Your debug tooling spawns shells on purpose; the rule must know that.

Tune for your environment, not the textbook

Generic rules generate generic noise. The rules that earn their keep encode knowledge specific to you:

  • Sensitive paths that matter to you. Don’t just watch /etc/shadow. Watch the path where your app keeps its config, the mount where secrets land, the keystore directory. A read of /var/run/secrets/db-password by anything other than your app is a signal.
  • Egress to unexpected destinations. If your payments pod only ever talks to the database and one payment API, an outbound connection anywhere else is worth an alert.
  • Process lineage. A curl or wget whose parent is your web server is often the second stage of an exploit — the app was made to download a payload. proc.pname lets you catch that.
- rule: Web server spawned a downloader
  desc: App process spawning curl/wget often means RCE second stage
  condition: >
    spawned_process
    and proc.name in (curl, wget)
    and proc.pname in (node, python, java, nginx, php-fpm)
  output: >
    Downloader spawned by web process — possible RCE second stage
    (parent=%proc.pname cmd=%proc.cmdline container=%container.name)
  priority: CRITICAL
  tags: [mitre_execution, rce]

That single rule has caught more real badness for me than a dozen generic file-watch rules.

Override, don’t fork

When a default rule is too noisy, do not copy the whole ruleset and edit it — you’ll never get upstream improvements again. Falco supports appending to existing rules and macros. Add your exceptions to the upstream rule:

- rule: Read sensitive file untrusted
  append: true
  condition: and not proc.name in (my_backup_agent, my_config_loader)

This keeps you on the upstream rule (and its future fixes) while carving out your known-good exceptions. Treat the default rules as a dependency you extend, not a file you own.

Manage rules like code

Custom rules are security-critical logic, so they get the same treatment as any other code:

  • Version them in Git, separate from the upstream ruleset.
  • Validate in CI. falco --validate my-rules.yaml catches syntax errors before they hit a node and silently disable detection.
  • Test the conditions. falcoctl and the falco test harness let you replay captured events against rules so you can prove a rule fires on the attack and stays quiet on the benign case.
  • Review changes. A loosened condition or an over-broad exception is how detection quietly dies. Every rule change deserves a second pair of eyes.

A rule that silently stopped matching because someone widened an exception is worse than no rule — you think you’re covered and you’re not.

Routing alerts so they get acted on

A CRITICAL alert that lands in a channel nobody watches is theatre. Use Falcosidekick to route by priority: page on CRITICAL (the RCE-second-stage rule), send WARNING to a Slack channel, drop INFORMATIONAL to a log sink for forensics. The discipline that keeps Falco alive long-term is ruthless: if a rule fires often and is always benign, either fix the rule or delete it. A noisy rule that everyone learns to ignore trains your team to ignore the real one next to it.

Fighting alert fatigue

The failure mode for runtime detection is always fatigue, not missing rules. Guardrails that have kept my rulesets healthy:

  • Start narrow. A rule scoped to one namespace and one image with explicit exceptions beats a broad rule you’ll mute by Friday.
  • Tune weekly at first. Every false positive is a rule to refine, not a reason to disable detection.
  • Tie rules to threats you care about. Tag them with MITRE ATT&CK techniques so you can reason about coverage instead of accumulating random rules.
  • Measure signal. Track fire rate per rule. The rule that fires 400 times a day and is always benign is actively harmful.

Done well, Falco becomes the layer that catches the attacker after they’ve slipped past your preventive controls — the shell they spawned, the secret they read, the payload they pulled down. That post-compromise visibility is exactly what turns a silent breach into a detected one.

More runtime and detection hardening lives in our security and hardening guides. When a teammate edits a rule’s condition or adds an exception, the AI code review assistant helps catch the widened clause that would quietly blind your detection.

Rule examples are starting points. Validate and tune against your own workloads before relying on them in production, and test each rule against both the attack and the benign case.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.