Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

176 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""Falco-based container escape detection agent.
Manages Falco rules, parses Falco alert output, and generates
escape detection reports from Falco JSON event streams.
"""
import argparse
import json
import subprocess
import sys
from datetime import datetime
ESCAPE_RULE_TAGS = ["container", "escape", "T1611", "T1610", "namespace",
"docker_socket", "cgroup", "kernel_module", "privileged"]
SEVERITY_MAP = {
"Emergency": 0, "Alert": 1, "Critical": 2, "Error": 3,
"Warning": 4, "Notice": 5, "Informational": 6, "Debug": 7
}
def check_falco_status():
try:
result = subprocess.run(["falco", "--version"],
capture_output=True, text=True, timeout=10)
if result.returncode == 0:
version = result.stdout.strip()
return {"installed": True, "version": version}
except FileNotFoundError:
pass
try:
result = subprocess.run(["systemctl", "is-active", "falco"],
capture_output=True, text=True, timeout=5)
return {"installed": True, "service_status": result.stdout.strip()}
except FileNotFoundError:
pass
return {"installed": False}
def validate_rules_file(rules_path):
try:
result = subprocess.run(
["falco", "--validate", rules_path],
capture_output=True, text=True, timeout=30)
return {
"valid": result.returncode == 0,
"output": result.stdout.strip() if result.returncode == 0
else result.stderr.strip(),
}
except (FileNotFoundError, subprocess.TimeoutExpired) as e:
return {"valid": False, "error": str(e)}
def parse_falco_alerts(filepath, min_priority="Warning"):
min_level = SEVERITY_MAP.get(min_priority, 4)
alerts = []
escape_alerts = []
with open(filepath, "r") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
evt = json.loads(line)
except json.JSONDecodeError:
continue
priority = evt.get("priority", "Informational")
if SEVERITY_MAP.get(priority, 6) > min_level:
continue
alert = {
"time": evt.get("time", ""),
"rule": evt.get("rule", ""),
"priority": priority,
"source": evt.get("source", "syscall"),
"output": evt.get("output", ""),
"fields": evt.get("output_fields", {}),
"tags": evt.get("tags", []),
}
alerts.append(alert)
tags = set(evt.get("tags", []))
if tags & set(ESCAPE_RULE_TAGS):
escape_alerts.append(alert)
return {"total_alerts": len(alerts), "escape_alerts": escape_alerts}
def generate_escape_rules_yaml():
rules = """# Container Escape Detection Rules for Falco
# Deploy to /etc/falco/rules.d/container-escape.yaml
- list: escape_binaries
items: [nsenter, unshare, mount, umount, pivot_root, chroot, modprobe, insmod]
- macro: container_escape_binary
condition: spawned_process and container and proc.name in (escape_binaries)
- rule: Container Escape Binary Execution
desc: Detect execution of binaries commonly used for container escape
condition: container_escape_binary
output: "Escape binary in container (user=%user.name cmd=%proc.cmdline container=%container.name image=%container.image.repository)"
priority: CRITICAL
tags: [container, escape, T1611]
- rule: Docker Socket Access from Container
desc: Container accessing Docker socket enables full host control
condition: (open_read or open_write) and container and fd.name = /var/run/docker.sock
output: "Docker socket accessed (user=%user.name container=%container.name cmd=%proc.cmdline)"
priority: CRITICAL
tags: [container, escape, docker_socket, T1610]
- rule: Cgroup Release Agent Write
desc: Writing to cgroup release_agent is a known container escape vector
condition: open_write and container and fd.name endswith release_agent
output: "Cgroup escape attempt (user=%user.name container=%container.name file=%fd.name)"
priority: CRITICAL
tags: [container, escape, cgroup]
- rule: Kernel Module Load from Container
desc: Container attempting to load kernel module
condition: spawned_process and container and proc.name in (modprobe, insmod, rmmod)
output: "Kernel module load (user=%user.name container=%container.name cmd=%proc.cmdline)"
priority: CRITICAL
tags: [container, escape, kernel_module]
- rule: Sensitive Proc Access from Container
desc: Container accessing sensitive /proc or /sys paths
condition: open_read and container and (fd.name startswith /proc/sysrq-trigger or fd.name startswith /proc/kcore or fd.name startswith /proc/kallsyms)
output: "Sensitive proc access (container=%container.name path=%fd.name cmd=%proc.cmdline)"
priority: CRITICAL
tags: [container, escape, proc_access]
"""
return rules
def main():
parser = argparse.ArgumentParser(description="Falco Container Escape Detection")
parser.add_argument("--check-status", action="store_true", help="Check Falco installation")
parser.add_argument("--validate-rules", help="Validate a Falco rules file")
parser.add_argument("--parse-alerts", help="Parse Falco JSON alert log")
parser.add_argument("--min-priority", default="Warning",
choices=list(SEVERITY_MAP.keys()))
parser.add_argument("--generate-rules", action="store_true",
help="Output escape detection rules YAML")
args = parser.parse_args()
results = {"timestamp": datetime.utcnow().isoformat() + "Z"}
if args.check_status:
results["falco_status"] = check_falco_status()
if args.validate_rules:
results["validation"] = validate_rules_file(args.validate_rules)
if args.parse_alerts:
parsed = parse_falco_alerts(args.parse_alerts, args.min_priority)
results["alert_summary"] = {
"total_alerts": parsed["total_alerts"],
"escape_alerts_count": len(parsed["escape_alerts"]),
}
results["escape_alerts"] = parsed["escape_alerts"]
if args.generate_rules:
print(generate_escape_rules_yaml())
return
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()