DEV Community

Cover image for Stopping AI Agent Shell Breakouts in 2.1ms with eBPF CO-RE and Inline BPF LSM
Anwar Iqbal Dawar
Anwar Iqbal Dawar

Posted on Originally published at xsypher.com

Stopping AI Agent Shell Breakouts in 2.1ms with eBPF CO-RE and Inline BPF LSM

If an MCP server can spawn /bin/sh, a prompt injection is basically a remote shell on your host with extra steps.

While researching AI agent runtime security for xSypher, I kept running into the same architectural wall. Static container profiles like AppArmor or seccomp break almost immediately on autonomous AI agent workloads because agents are dynamic by design—they legitimately write code, invoke interpreters, and execute subprocesses.

On the flip side, standard user-space syscall tracers usually alert you 50 to 2,000 ms after the syscall fires. By the time an alert lands on mcp-server -> python3 -> sh -> curl, the AWS or GCP cloud metadata (IMDS) token at 169.254.169.254 has already left the host.

The signal is not the individual syscall. It is the process ancestry behind it and the network destination in front of it—and containment has to happen synchronously before the socket opens.

To solve this at the Linux syscall boundary without running another bloated, monolithic root daemon, I built and open-sourced xSypher-Stream (v2.0.0).

GitHub logo anwariqbaldawar / xSypher-Stream-Zero-Trust-Kernel-log-Telemetry

Kernel-verified runtime defense for AI agent & MCP hosts using eBPF CO-RE telemetry and inline BPF LSM enforcement.

xSypher-Stream wordmark: emblem and project name, black ink on light themes and white ink on dark themes, with a crimson accent and glow

xSypher-Stream

Kernel-verified runtime defence for AI agent hosts.

eBPF CO-RE telemetry, a policy decision in user space, an inline LSM denial in the kernel.
An injected tool call cannot reach the network if the kernel refuses the syscall first

CI status npm package version 2.0.0 Licensed Apache-2.0, kernel programs GPL-2.0 Kernel 5.10 or newer Python 3.10 or newer Node 18 or newer 63 tests passing 71 percent line coverage of the engine by the pytest suite

Quick start · The problem · Comparison · Architecture · Detections · Compliance · Benchmarks · Deployment · Validation · Docs · Contributing


Ten-second quickstart

From a source checkout (Python 3.10+):

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt && PYTHONPATH=src .venv/bin/python tests/simulate_mcp_attack.py --events 10000
Enter fullscreen mode Exit fullscreen mode

This runs real user-space socket transport and simulated kernel events, not live kernel enforcement. For the native collector, install the prerequisites below, then run cmake -S . -B build && cmake --build build -j2. The npm bootstrap package is unpublished; registry installation is not a supported quickstart.

The problem

An MCP server publishes tools to a language model. The model calls those tools with…


Why Traditional Container Security Misses AI Agent Breakouts

When an LLM agent or Model Context Protocol (MCP) server processes untrusted external input, every individual step of an exploit chain looks normal to traditional controls:

  1. Subprocess execution is expected: Developer and ops agents legitimately spawn git, bash, python3, or node.
  2. Living-off-the-land binaries exist in the image: Standard base images carry /bin/sh, curl, and standard file utilities.
  3. Outbound egress is open: Agents need external network access to reach model APIs, package registries, and documentation endpoints.

If you rely on asynchronous user-space detection (ring buffer -> user-space parser -> rule engine -> kill -9), you are racing the kernel scheduler. A hijacked curl http://169.254.169 request completes a local link-local TCP handshake in microseconds. To stop credential exfiltration, enforcement must live inside the kernel LSM hook itself.


Architecture: Splitting Kernel Privilege from Policy Evaluation

Most runtime security tools run their entire rule parsing and telemetry pipeline inside a single privileged process. If that parser has a memory safety bug or logic flaw, the attacker compromises a root daemon.

In xSypher-Stream, I separated kernel collection from threat evaluation across a strict binary wire contract (ABI v3, magic 0x58535950):

xSypher-Stream Kernel-to-Userspace Wire ABI v3 and Privilege Boundary

1. Kernel Telemetry Plane (5 eBPF CO-RE Tracepoints)

Five eBPF CO-RE programs attach to kernel tracepoints and emit fixed 656-byte kernel_event_t structs into a 16 MiB BPF ring buffer:

  • tp/syscalls/sys_enter_execve — Captures binary path, arguments, UID/GID, and cgroup ID.
  • tp/syscalls/sys_enter_connect — Captures outbound IPv4/IPv6 destination addresses and ports.
  • tp/syscalls/sys_enter_memfd_create — Flags anonymous in-memory file creation used in fileless ELF execution.
  • tp/sched/sched_process_fork — Tracks parent-to-child PID creation in real time.
  • tp/sched/sched_process_exit — Cleans up exited PIDs from the lineage state.

2. Inline Enforcement Plane (5 BPF LSM Hooks)

Five BPF LSM programs attach directly to security decision points in the kernel and check pinned BPF maps (quarantined_pids, quarantined_cgroups, blocked_ipv4):

  • lsm/bprm_check_security — Denies binary execution (-EPERM) for quarantined PIDs or cgroups before execve completes.
  • lsm/socket_connect — Blocks outbound socket connections (-EACCES) to blacklisted IPs (such as Cloud IMDS 169.254.169.254) or from quarantined processes.
  • lsm/file_open — Blocks unauthorized reads to sensitive host paths (/etc/shadow, /proc/$pid/environ, SSH keys).
  • lsm/task_kill — Prevents quarantined processes from signaling or terminating security monitors.
  • lsm/bpf — Tamper-protection hook that denies unauthorized processes from modifying or unloading pinned xSypher BPF maps and programs.

3. Capability-Scoped C++20 Collector (xsypher-collector)

The C++20 collector is the only privileged user-space binary, scoped strictly to CAP_BPF and CAP_PERFMON.

  • It polls the 16 MiB kernel ring buffer via epoll (ring_buffer__poll()).
  • It wraps each 656-byte kernel_event_t inside an 80-byte transport header to form a fixed 736-byte wire_event_msg_t frame.
  • It streams frames over an AF_UNIX (SOCK_SEQPACKET) local IPC socket and listens for validated 32-byte wire_policy_cmd_t commands to update the kernel BPF maps via bpf_map_update_elem().

4. Zero-Capability Python Threat Engine (xsypher-engine)

The Python 3.10+ asyncio engine runs with zero Linux capabilities (NoNewPrivileges=true).

  • O(depth) Lineage Graph: Maintains a 100,000-PID LRU process ancestry graph in memory, resolving up to 8 ancestor hops (mcp-server -> python3 -> sh -> curl) on every incoming event.
  • 6 Shipped MITRE ATT&CK Rules: Evaluates lineage and syscall parameters against YAML rules covering MCP shell breakouts (T1059.004), Cloud IMDS credential theft (T1552.005), /etc/shadow and environment harvesting (T1003.008), fileless memfd execution (T1620), recon tools (T1105), and suspicious C2 egress ports (T1071).
  • Tamper-Evident Audit Ledger: Every alert is written to an append-only JSONL log where each record includes a forward-chained SHA-256 hash (prev_hash + current_payload) verifiable via xsypher verify-log.

The Fixed Binary Wire Contract (ABI v3)

To eliminate serialization overhead (no JSON or Protobuf parsing on the hot path) and prevent parser bugs across the C++/Python boundary, both sides enforce a compile-time verified binary layout defined in include/xsypher_abi.h:

  • kernel_event_t — 656 Bytes (Kernel BPF Ring Buffer -> C++20 Collector)
  • wire_event_msg_t — 736 Bytes (80-Byte Transport Header + 656-Byte Event, Collector -> Python Engine)
  • wire_policy_cmd_t — 32 Bytes (Quarantine / Block Command, Python Engine -> Collector -> BPF Maps)

Running make verify compiles a C offset inspector against include/xsypher_abi.h and asserts byte-for-byte parity with the Python ctypes.Structure definitions before any test suite runs.


Engineering Honesty: What Works Today vs. Live-Kernel Gotchas

When releasing security tooling, I believe in documenting exact boundaries rather than hiding behind marketing claims:

  • What passes in CI today: User-space simulation (~2.1 ms end-to-end containment latency across 5 multi-stage exploit chains), C/Python ABI layout parity (make verify), and 63 pytest unit and integration tests pass cleanly.
  • The BPF LSM Boot Prerequisite: Having CONFIG_BPF_LSM=y compiled into your Linux kernel is necessary, but not sufficient by itself. On stock Ubuntu 22.04/24.04 and most standard cloud images, the bpf module is omitted from /sys/kernel/security/lsm at boot. When bpf is not in the active boot LSM list (lsm=lockdown,capability,landlock,yama,apparmor,bpf), libbpf can load and attach LSM programs without returning an error, yet the kernel will never invoke them on the syscall path.

Before enabling --enforce-kernel in production, always run xsypher doctor to verify /sys/kernel/security/lsm and baseline your workload in audit-only mode on a dedicated node.


Try It in 10 Seconds (Zero Root Privileges Required)

You do not need a custom kernel, BTF, or root access to test the binary wire protocol, O(depth) lineage graph, and MITRE ATT&CK rule engine. The built-in simulation harness replays 10,000 synthetic syscalls interleaved with 5 multi-stage attack chains over a real AF_UNIX SOCK_SEQPACKET pair:

git clone https://github.com
cd xSypher-Stream-Zero-Trust-Kernel-log-Telemetry
pip install -e .
xsypher simulate 10000
Enter fullscreen mode Exit fullscreen mode

xSypher-Stream 10,000-Event Simulation and ABI Verification Terminal Output

Source Code & Full Architectural Audit

If you are working with eBPF CO-RE, Linux LSMs, or sandboxing autonomous AI agents, I would genuinely appreciate your critique on the collector/engine privilege split and wire ABI design!

Top comments (0)