container security

Detecting Container Runtime Threats with Falco

Write and deploy Falco rules with the modern eBPF driver to detect container escape, namespace abuse, privileged mounts, and anomalous syscalls at runtime in Kubernetes and Docker.

container-escapedetection-engineeringebpffalcokubernetesruntime-securitysyscall-monitoringthreat-detection
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Falco is the CNCF graduated runtime-security project (originally by Sysdig) that consumes Linux kernel syscalls and Kubernetes audit events through a driver, evaluates them against a YAML rule engine, and emits real-time alerts. It is the de facto open-source detection tool for runtime threats inside containers, including container escape (MITRE ATT&CK T1611, Escape to Host), namespace manipulation (setns), privileged mounts, reverse shells, and unexpected outbound connections.

Falco supports three drivers: the modern eBPF probe (preferred default, requires kernel >= 5.8, shipped directly inside the Falco binary so no init container is needed), the legacy eBPF probe, and the kernel module (kmod). Driver selection is handled by falcoctl driver config --type {kmod|ebpf|modern_ebpf} or driver.kind=modern_ebpf in the Helm chart. On Kubernetes, Falco runs as a DaemonSet so every node is monitored, and falcoctl automatically installs and updates rule artifacts from the Falco rules registry.

This skill covers authoring and deploying custom Falco rules to detect the container-escape primitives and anomalous-behavior signals that the breakout techniques in this collection produce. Each Falco rule has the fields rule, desc, condition, output, priority, and optional tags; reusable logic is factored into macro and list objects. Source: falco.org official documentation; falcosecurity/rules repository; Sysdig Falco detection research (e.g., CVE-2025-22224).

When to Use

  • Building runtime detections for a Kubernetes or Docker environment
  • Validating that container-escape and lateral-movement attempts generate alerts (purple-team)
  • Adding coverage for a newly disclosed runtime CVE
  • Hardening a SOC's container telemetry pipeline (Falco -> Falcosidekick -> SIEM)

Prerequisites

  • A Linux host (kernel >= 5.8 for modern eBPF) or Kubernetes cluster you administer
  • Falco install:
    # Helm (Kubernetes, modern eBPF, JSON output for SIEM ingest)
    helm repo add falcosecurity https://falcosecurity.github.io/charts
    helm repo update
    helm install falco falcosecurity/falco \
      --namespace falco --create-namespace \
      --set driver.kind=modern_ebpf \
      --set collectors.containerd.enabled=true \
      --set falco.json_output=true \
      --set tty=true
     
    # Linux package install (Debian/Ubuntu)
    curl -fsSL https://falco.org/repo/falcosecurity-packages.asc | \
      sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg
    echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] \
      https://download.falco.org/packages/deb stable main" | \
      sudo tee /etc/apt/sources.list.d/falcosecurity.list
    sudo apt-get update -y && sudo apt-get install -y falco
  • Basic familiarity with Falco fields (evt.type, proc.name, container.id, fd.name)

Objectives

  • Install Falco with the modern eBPF driver
  • Understand the rule/macro/list schema and key Falco filter fields
  • Author custom rules for container escape, setns, privileged mounts, sensitive-file reads, and reverse shells
  • Load custom rules and validate syntax
  • Trigger and confirm detections (purple-team validation)
  • Forward alerts to a SIEM via Falcosidekick

MITRE ATT&CK Mapping

Technique ID Name Tactic
T1611 Escape to Host Privilege Escalation
T1059.004 Command and Scripting Interpreter: Unix Shell Execution
T1610 Deploy Container Defense Evasion / Execution
T1543 Create or Modify System Process Persistence
T1071.001 Application Layer Protocol: Web Protocols Command and Control

Workflow

Step 1: Install Falco and Confirm the Driver

# Kubernetes: confirm the DaemonSet is running on every node
kubectl get pods -n falco -o wide
kubectl logs -n falco -l app.kubernetes.io/name=falco | grep -i "driver"
 
# Linux host: configure driver and start
sudo falcoctl driver config --type modern_ebpf
sudo systemctl enable --now falco-modern-bpf.service
sudo systemctl status falco-modern-bpf.service

Step 2: Learn the Rule, Macro, and List Schema

Custom rules live in /etc/falco/falco_rules.local.yaml or /etc/falco/rules.d/, referenced from rules_files in /etc/falco/falco.yaml.

# /etc/falco/rules.d/custom-escape.yaml
- list: shell_binaries
  items: [bash, sh, zsh, dash, ash, ksh]
 
- macro: spawned_process
  condition: evt.type in (execve, execveat) and evt.dir = <
 
- macro: container
  condition: container.id != host

Step 3: Write a Container-Escape Detection Rule (release_agent / cgroup)

- rule: Container Escape via cgroup release_agent
  desc: >
    Detect a process inside a container writing to a cgroup release_agent or
    notify_on_release file, a classic privileged-container breakout primitive.
  condition: >
    container
    and spawned_process
    and (evt.type in (open, openat, openat2) or evt.type=write)
    and (fd.name endswith "release_agent"
         or fd.name endswith "notify_on_release")
    and evt.is_open_write=true
  output: >
    Container escape attempt via cgroup release_agent
    (user=%user.name command=%proc.cmdline file=%fd.name
     container=%container.name image=%container.image.repository)
  priority: CRITICAL
  tags: [container, mitre_privilege_escalation, T1611]

Step 4: Detect Namespace Breakout (setns / nsenter)

- rule: Namespace Change via setns to Host
  desc: >
    Detect setns/nsenter used to enter the host namespace (e.g. nsenter -t 1),
    a common container-to-host escape technique.
  condition: >
    evt.type = setns
    and container
    and proc.name in (nsenter, unshare)
  output: >
    Namespace breakout via setns/nsenter
    (user=%user.name proc=%proc.name cmd=%proc.cmdline
     container=%container.name image=%container.image.repository)
  priority: CRITICAL
  tags: [container, mitre_privilege_escalation, T1611]

Step 5: Detect Privileged Mount and Docker Socket Abuse

- rule: Mount Launched in Privileged Container
  desc: Detect the mount binary running inside a privileged container.
  condition: >
    spawned_process
    and container
    and container.privileged = true
    and proc.name = mount
  output: >
    Mount executed in privileged container
    (cmd=%proc.cmdline container=%container.name image=%container.image.repository)
  priority: WARNING
  tags: [container, mitre_privilege_escalation, T1611]
 
- rule: Docker Socket Accessed From Container
  desc: A container process reads/writes the host Docker daemon socket.
  condition: >
    container
    and (evt.type in (open, openat, openat2, connect))
    and fd.name = /var/run/docker.sock
  output: >
    Container touched docker.sock - possible daemon-API escape
    (proc=%proc.name cmd=%proc.cmdline container=%container.name)
  priority: CRITICAL
  tags: [container, mitre_execution, T1610]

Step 6: Detect Reverse Shells and Sensitive File Reads

- rule: Reverse Shell From Container
  desc: A shell in a container with stdin/stdout wired to a network socket.
  condition: >
    spawned_process
    and container
    and proc.name in (shell_binaries)
    and (fd.num in (0, 1, 2))
    and fd.type in (ipv4, ipv6)
  output: >
    Reverse shell detected in container
    (proc=%proc.cmdline connection=%fd.name container=%container.name)
  priority: CRITICAL
  tags: [container, mitre_execution, T1059.004]
 
- rule: Read Sensitive Host File From Container
  desc: Container reads /etc/shadow or similar after a likely escape.
  condition: >
    container
    and (evt.type in (open, openat, openat2))
    and evt.is_open_read=true
    and fd.name in (/etc/shadow, /etc/sudoers, /root/.ssh/id_rsa)
  output: >
    Sensitive file read from container (file=%fd.name proc=%proc.cmdline
     container=%container.name)
  priority: WARNING
  tags: [container, mitre_credential_access]

Step 7: Validate Rule Syntax and Load

# Dry-run validate a rules file without starting the engine
sudo falco --validate /etc/falco/rules.d/custom-escape.yaml
 
# Run Falco with only the custom rules to test
sudo falco -r /etc/falco/rules.d/custom-escape.yaml
 
# Helm: ship custom rules via values (mounted into /etc/falco/rules.d)
helm upgrade falco falcosecurity/falco -n falco --reuse-values \
  --set-file "customRules.custom-escape\.yaml"=./custom-escape.yaml

Step 8: Trigger and Confirm (Purple-Team)

# In a test container, trigger the setns rule
kubectl run pwn --rm -it --image=alpine --overrides='
{"spec":{"hostPID":true,"containers":[{"name":"pwn","image":"alpine",
"securityContext":{"privileged":true},"stdin":true,"tty":true,
"command":["sh"]}]}}' -- sh -c 'nsenter -t 1 -m -u -i -n -p -- id'
 
# Confirm the alert fired
kubectl logs -n falco -l app.kubernetes.io/name=falco | grep -i "Namespace breakout"

Step 9: Forward Alerts to a SIEM

# Deploy Falcosidekick to fan out alerts (Elastic, Slack, Splunk, etc.)
helm upgrade falco falcosecurity/falco -n falco --reuse-values \
  --set falcosidekick.enabled=true \
  --set falcosidekick.config.elasticsearch.hostport=https://elastic:9200 \
  --set falcosidekick.config.elasticsearch.index=falco

Tools and Resources

Tool Purpose Source
Falco Runtime syscall detection engine https://falco.org
falcoctl Driver + rules artifact manager https://github.com/falcosecurity/falcoctl
falcosecurity/rules Maintained default ruleset https://github.com/falcosecurity/rules
Falcosidekick Alert fan-out to SIEM/chat https://github.com/falcosecurity/falcosidekick
Falco Helm chart Kubernetes DaemonSet deploy https://github.com/falcosecurity/charts

Key Falco Filter Fields

Field Meaning
evt.type Syscall name (execve, setns, open, connect)
evt.dir Event direction (< exit, > enter)
proc.name / proc.cmdline Process name / full command line
container.id / container.privileged Container identity / privileged flag
container.image.repository Image name
fd.name / fd.type File/socket path / type (ipv4, ipv6)
evt.is_open_write / evt.is_open_read Open intent
user.name Acting user

Validation Criteria

  • Falco installed with the modern eBPF driver (DaemonSet on all nodes)
  • Custom rules file validated with falco --validate
  • release_agent / setns / privileged-mount / docker.sock rules loaded
  • Reverse-shell and sensitive-file-read rules loaded
  • Each rule triggered in a lab and the alert confirmed in logs
  • Priorities set appropriately (CRITICAL for escape primitives)
  • Alerts forwarded to the SIEM via Falcosidekick
  • Rule tags include the relevant MITRE technique IDs
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.6 KB

Falco — Rule Schema & CLI Reference

Rule Object Fields

Field Required Purpose
rule yes Unique rule name
desc yes Human description
condition yes Falco filter expression that triggers the rule
output yes Alert message (supports %field interpolation)
priority yes EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG
tags no Categorization (e.g. MITRE IDs)
enabled no Toggle a rule (true/false)
source no Event source (syscall, k8s_audit)

Macro and List Objects

Object Keys Purpose
macro condition Reusable condition fragment
list items Named value set used with in (...)

Key CLI Commands

Command Purpose
falco --validate <file> Validate rule syntax without running
falco -r <file> Run with a specific rules file
falco -L List loaded rules
falco -l <rule> Describe a single rule
falco --list List supported fields
falcoctl driver config --type modern_ebpf Set driver type
falcoctl artifact install <name> Install a rules/plugin artifact
falcoctl artifact list List available artifacts

Driver Types

Driver driver.kind Notes
Modern eBPF modern_ebpf Default; built into binary; kernel >= 5.8
Legacy eBPF ebpf CO-RE eBPF probe
Kernel module kmod Loadable kernel module
Auto auto falcoctl picks best available

Important Filter Fields

Field Description
evt.type Syscall name
evt.dir > enter, < exit
evt.is_open_read / evt.is_open_write open() intent
proc.name / proc.cmdline / proc.pname Process / cmdline / parent
container.id / container.name / container.image.repository Container identity
container.privileged Privileged flag
fd.name / fd.type / fd.num FD path / type / number
user.name / user.uid Acting user
k8s.pod.name / k8s.ns.name Kubernetes context

Configuration (falco.yaml)

Key Purpose
rules_files List of rule files / dirs to load
json_output Emit JSON for SIEM ingest
priority Minimum priority to log
outputs / http_output / program_output Alert sinks

External References

standards.md1.8 KB

Standards and References - Falco Container Runtime Detection

MITRE ATT&CK

Technique ID Name Tactic Rationale
T1611 Escape to Host Privilege Escalation Falco rules detect the syscalls/files used in container breakout (release_agent, setns, privileged mount).
T1059.004 Command and Scripting Interpreter: Unix Shell Execution Reverse-shell rule detects shells wired to network sockets in containers.
T1610 Deploy Container Defense Evasion / Execution docker.sock access rule detects daemon-API container spawning.
T1543 Create or Modify System Process Persistence Anomalous process/service creation inside containers.
T1071.001 Application Layer Protocol: Web Protocols Command and Control Unexpected outbound connections from containers.

NIST CSF 2.0

ID Name Rationale
DE.CM-01 Networks and network services are monitored to find potentially adverse events Falco continuously monitors syscall and network behavior at runtime, surfacing adverse container activity.

Official Resources

Key Research

Scripts 1

agent.py5.1 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Falco rule helper: validate custom rules and triage Falco JSON output.

Two modes:
  --validate <rules.yaml>  : structurally check a Falco rules file (and run
                             `falco --validate` if the falco binary is present).
  --triage <alerts.json>   : parse Falco JSON-formatted alerts (one JSON object
                             per line, as emitted with `json_output: true`) and
                             summarize by rule, priority, and container.

Defensive detection-engineering tool.

Examples:
  python agent.py --validate ./falco_rules.local.yaml
  python agent.py --triage /var/log/falco/events.json --min-priority WARNING
"""
import argparse
import json
import shutil
import subprocess
import sys
from collections import Counter

PRIORITIES = ["DEBUG", "INFO", "NOTICE", "WARNING", "ERROR",
              "CRITICAL", "ALERT", "EMERGENCY"]
REQUIRED_RULE_FIELDS = {"rule", "desc", "condition", "output", "priority"}


def validate_rules(path):
    try:
        import yaml
    except ImportError:
        sys.exit("error: PyYAML required for --validate. Install: pip install pyyaml")

    try:
        with open(path, "r", encoding="utf-8") as fh:
            docs = list(yaml.safe_load_all(fh))
    except (OSError, yaml.YAMLError) as exc:
        sys.exit(f"error: cannot parse {path}: {exc}")

    items = []
    for doc in docs:
        if isinstance(doc, list):
            items.extend(doc)
        elif isinstance(doc, dict):
            items.append(doc)

    rules = [i for i in items if isinstance(i, dict) and "rule" in i]
    macros = [i for i in items if isinstance(i, dict) and "macro" in i]
    lists = [i for i in items if isinstance(i, dict) and "list" in i]
    print(f"[+] parsed {len(rules)} rule(s), {len(macros)} macro(s), {len(lists)} list(s)")

    errors = 0
    for r in rules:
        missing = REQUIRED_RULE_FIELDS - set(r.keys())
        if missing:
            print(f"  [!] rule '{r.get('rule')}' missing fields: {sorted(missing)}")
            errors += 1
        prio = str(r.get("priority", "")).upper()
        if prio and prio not in PRIORITIES:
            print(f"  [!] rule '{r.get('rule')}' invalid priority: {prio}")
            errors += 1

    # If the falco binary is available, run the authoritative validator.
    if shutil.which("falco"):
        print("[*] running 'falco --validate' ...")
        try:
            proc = subprocess.run(["falco", "--validate", path],
                                  capture_output=True, text=True, timeout=60)
            sys.stdout.write(proc.stdout)
            sys.stderr.write(proc.stderr)
            if proc.returncode != 0:
                errors += 1
        except (OSError, subprocess.TimeoutExpired) as exc:
            print(f"  [!] could not run falco --validate: {exc}")
    else:
        print("[i] falco binary not found; performed structural validation only")

    if errors:
        print(f"[!] validation found {errors} issue(s)")
        sys.exit(1)
    print("[+] rules look structurally valid")


def triage_alerts(path, min_priority):
    threshold = PRIORITIES.index(min_priority.upper()) if min_priority else 0
    by_rule = Counter()
    by_priority = Counter()
    by_container = Counter()
    total = kept = 0

    try:
        fh = open(path, "r", encoding="utf-8")
    except OSError as exc:
        sys.exit(f"error: cannot open {path}: {exc}")

    with fh:
        for line in fh:
            line = line.strip()
            if not line:
                continue
            total += 1
            try:
                ev = json.loads(line)
            except json.JSONDecodeError:
                continue
            prio = str(ev.get("priority", "")).upper()
            if prio in PRIORITIES and PRIORITIES.index(prio) < threshold:
                continue
            kept += 1
            by_rule[ev.get("rule", "<unknown>")] += 1
            by_priority[prio or "<none>"] += 1
            fields = ev.get("output_fields") or {}
            cname = fields.get("container.name") or fields.get("container.id") or "host"
            by_container[cname] += 1

    print(f"[+] parsed {total} alert lines, {kept} at/above {min_priority or 'DEBUG'}\n")
    print("== By priority ==")
    for p, c in sorted(by_priority.items(), key=lambda x: -x[1]):
        print(f"  {p:10s} {c}")
    print("\n== Top rules ==")
    for r, c in by_rule.most_common(15):
        print(f"  {c:5d}  {r}")
    print("\n== Top containers ==")
    for cn, c in by_container.most_common(15):
        print(f"  {c:5d}  {cn}")


def main():
    p = argparse.ArgumentParser(description="Falco rule validator / alert triage")
    g = p.add_mutually_exclusive_group(required=True)
    g.add_argument("--validate", metavar="RULES_YAML", help="validate a Falco rules file")
    g.add_argument("--triage", metavar="ALERTS_JSON", help="triage Falco JSON output")
    p.add_argument("--min-priority", default="DEBUG",
                   help="minimum priority to include in triage")
    args = p.parse_args()

    if args.validate:
        validate_rules(args.validate)
    else:
        triage_alerts(args.triage, args.min_priority)


if __name__ == "__main__":
    main()
Keep exploring