{"id":51423897,"url":"https://github.com/yeet-src/agent-jail","last_synced_at":"2026-07-05T01:30:31.208Z","repository":{"id":367822537,"uuid":"1281472491","full_name":"yeet-src/agent-jail","owner":"yeet-src","description":"Confine an AI coding agent (oh-my-pi/omp) to one directory with Landlock, and watch it live over eBPF. Experimental.","archived":false,"fork":false,"pushed_at":"2026-06-27T19:48:47.000Z","size":2681,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-27T21:19:35.204Z","etag":null,"topics":["ai-agents","ebpf","landlock","llm-tools","oh-my-pi","sandbox","security","showcase","yeet"],"latest_commit_sha":null,"homepage":"https://yeet.cx","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yeet-src.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-26T15:34:05.000Z","updated_at":"2026-06-27T19:48:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/yeet-src/agent-jail","commit_stats":null,"previous_names":["yeet-src/agent-jail"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/yeet-src/agent-jail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fagent-jail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fagent-jail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fagent-jail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fagent-jail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yeet-src","download_url":"https://codeload.github.com/yeet-src/agent-jail/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fagent-jail/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35141083,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","ebpf","landlock","llm-tools","oh-my-pi","sandbox","security","showcase","yeet"],"created_at":"2026-07-05T01:30:28.320Z","updated_at":"2026-07-05T01:30:31.182Z","avatar_url":"https://github.com/yeet-src.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `agent-jail`\n\n\u003e **A cell for your coding agent.** The kernel decides what it can touch, and you watch it try.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/platform-Linux-1793D1\" alt=\"Linux\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/built%20with-yeet%20%2B%20eBPF-8A2BE2\" alt=\"yeet + eBPF\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-GPL--2.0-3DA639\" alt=\"GPL-2.0\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/category-security-D1242F\" alt=\"security\"\u003e\n  \u003ca href=\"https://discord.gg/dYZu9PjKB\"\u003e\u003cimg src=\"https://img.shields.io/badge/chat-Discord-5865F2\" alt=\"Discord\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n![agent-jail live demo: an agent works inside its project while reaches at ~/.ssh, ~/.aws, and /etc/passwd climb the escape leaderboard, every one blocked](assets/agent-jail.gif)\n\n**`agent-jail` confines a coding agent (`omp`) and every process it spawns to one directory with the Linux Landlock LSM, and shows each file it opens, live, over eBPF.**\n\n\u003e [!NOTE]\n\u003e Enforcement here is **Landlock** (a small C launcher + an eBPF watcher), which\n\u003e works on any Linux 5.13+ with Landlock enabled. If your kernel has the BPF LSM\n\u003e (`CONFIG_BPF_LSM=y`), the sibling\n\u003e [`agent-lock`](https://github.com/yeet-src/agent-lock) does the same job with a\n\u003e single eBPF program (no launcher). Same UI; pick by what your kernel supports.\n\n\u003e [!TIP]\n\u003e The enforcement and the watching are two separate kernel mechanisms aimed at the same process. Landlock applies the cage before the agent starts, so the restriction is inherited across `exec` and cannot be lifted from inside. eBPF reads every open and its return value, so you see the verdict the kernel actually returned, not a guess.\n\n\u003e [!WARNING]\n\u003e **Experimental (v0.x).** The confinement holds against a breakout self-test at zero leaks, but it has only been verified on one kernel and architecture (Linux 6.12, arm64, Debian 13) against a shell stand-in, not against real `omp` across the environments people run. Treat it as a layer to evaluate and harden, not a sole barrier you bet secrets on.\n\n## Quick start\n\n```sh\ncurl -fsSL https://yeet.cx | sh\ngit clone https://github.com/yeet-src/agent-jail.git \u0026\u0026 cd agent-jail\nmake                                  # builds the BPF object, the launcher, and the JS bundle\nsudo ./scripts/agent-jail ~/project   # jail omp in ~/project and watch it live\n```\n\u003csub\u003e[Manual install guide](https://yeet.cx/docs/install/manual-installation) | Linux only\u003c/sub\u003e\n\n`agent-jail` carries a compiled launcher and a wrapper script, so it builds from source rather than running straight from a remote `yeet run`. The dashboard runs in your terminal with `omp` jailed underneath it. `↑` / `↓` moves the highlighted row in the escape list, `c` copies a session summary, `q` quits. `--headless` drops the TUI and streams escape reports for a background run.\n\n## A 60-second primer on jailing a process\n\nA coding agent runs with your user's full filesystem access, and it decides what to read on its own. Confining it means letting the kernel, not the agent, decide which paths are reachable. Two kernel features do the work.\n\n| Term | What it means here |\n|---|---|\n| **Landlock** | A Linux Security Module, mainline since 5.13, that lets an unprivileged process drop its own filesystem access. No root, no container, no namespaces. |\n| **ruleset** | The set of paths the jailed process keeps: read and write under the project directory, read-only on the system paths a program needs to start. Everything else is denied. |\n| **inherited restriction** | The ruleset is applied before `omp` starts and survives `exec`, so every child it spawns (`git`, `node`, `cat`) is bound by it too, and none of them can undo it. |\n| **escape attempt** | An open of a path outside the project directory. The kernel refuses it; the watcher records it with the kernel's error code. |\n| **reached** | An out-of-bounds open that succeeded. Under a working jail this stays zero; you see it only in `--audit` mode, which runs the agent unconfined. |\n\nThe reason a path filter built this way holds: Landlock checks the resolved inode, not the path string you passed. A `../../etc/passwd`, a symlink pointing out of the directory, and the `/proc/self/root` re-entry trick all resolve to the same forbidden file, and all three are refused.\n\n## Common use cases\n\nDevelopers running an AI coding agent on a real codebase, and anyone auditing what an agent does to the filesystem before trusting it. Where you'd otherwise wrap the agent in a container or a VM to keep it away from `~/.ssh`, `agent-jail` applies a kernel ruleset to the process directly, with no image to build and no guest to boot.\n\n- Running `omp` on a work repo. Will it stay out of `~/.ssh` and `~/.aws`?\n- Evaluating a new agent. What does it reach for outside the project?\n- Recording a sandbox demo. Can you show the kernel blocking an escape on camera?\n- Running an agent unattended. What tried to leave the directory while you were away?\n\n## What you're looking at\n\nA status masthead on top, two framed panels, and a key-hint footer.\n\n**Masthead.** The jail state (`JAILED` or `AUDIT`), the directory, a one-line verdict (`37 escape attempts blocked`), and a bar splitting every open into in-bounds, system, blocked, and reached, with a live access-rate sparkline. The split is the proof: in-bounds work and blocked escapes are counted from the same stream.\n\n**Escape attempts.** The paths `omp` reached for outside the directory, ranked by how often. Sensitive targets (keys, credentials, shell history) are flagged with a 🔥. The badge on the right is the kernel's verdict: `blocked` means the open was refused, `reached` means it got through (which only happens in audit mode). `↑` / `↓` moves a highlighted cursor down the list as it grows.\n\n**Live opens.** The stream of recent opens as they happen: in-bounds work in green, interleaved with the occasional blocked escape, each attributed to the child process that made the call (`cat`, `git`, `grep`), since the jail covers the whole process tree.\n\nBenign system and scratch reads (`/usr`, `/lib`, `/tmp`) are counted as \"system\" and kept off the escape list, so a real reach at your data is not buried under library and locale lookups.\n\n## How it works\n\nThe launcher enforces; the eBPF programs observe. They share nothing but the process they are aimed at.\n\n**The launcher** (`src/jail/agent-jail.c`). A dependency-free C program that calls three Landlock syscalls (`landlock_create_ruleset`, `landlock_add_rule`, `landlock_restrict_self`), then `execve`s the target. It grants read and write under the project directory, read-only on the system directories a program needs to run, the specific `/etc` loader files, and the command's own binary, then locks itself before exec. It also strips environment variables whose names look secret-bearing (`KEY`, `TOKEN`, `SECRET`, `AWS_`, and similar), because Landlock cannot stop a process reading its own environment.\n\n**The BPF side.** One object, four tracepoint programs, one ring buffer. Each open event carries the path, the return value, and the comm of the process that made it.\n\n| Program | Hook | Captures |\n|---|---|---|\n| tracepoint | `sys_enter_openat` / `sys_exit_openat`, and the `openat2` pair | Every file open and the kernel's return value (the verdict) |\n| tracepoint | `sched_process_fork` | New children, recorded into the traced-process set |\n| tracepoint | `sched_process_exit` | Process exit, to drop it from the set |\n\nA `traced` hash map seeded on `omp` and extended at each fork is how an open by a child like `cat` is attributed to the jailed tree rather than missed. The send path stages each path in a per-CPU scratch map and a per-thread `inflight` map, then emits one `file_event` struct into the ring buffer.\n\n**The JS side.**\n\n- `src/probes/` is the only BPF-aware code. It loads the object, subscribes to the ring buffer once, and rolls the stream into reactive signals.\n- `src/components/` and `src/lib/` are pure presentation reading those signals: the masthead, the two panels, the path classifier, the theme.\n- `src/main.jsx` wires them together and owns keyboard input.\n\n## Requirements\n\n\u003e [!IMPORTANT]\n\u003e A kernel with Landlock enabled (`CONFIG_SECURITY_LANDLOCK=y`) for the jail, and BTF (`CONFIG_DEBUG_INFO_BTF=y`) for the eBPF watcher. Both are on by default on current Ubuntu, Debian, and Fedora. Landlock is mainline from kernel 5.13.\n\nThe yeet daemon, which handles the privileged BPF load. `curl -fsSL https://yeet.cx | sh` installs it.\n\n## Honest caveats\n\n\u003e [!NOTE]\n\u003e What `agent-jail` does not do, and where it is unproven.\n\n- It confines the filesystem, not the network. Landlock governs files, not sockets, so the agent's calls to model APIs keep working and network exfiltration is not prevented. For that, pair it with a network namespace or firewall.\n- It is verified on one kernel and architecture against a shell stand-in, not against real `omp` across distros and kernel versions. The Landlock ABI gained rights across 5.13 to 6.x, so an older kernel grants a narrower ruleset.\n- `--best-effort` runs the agent unconfined when Landlock is unavailable. That is deliberate, so a missing LSM does not silently break the run, but it means no protection on that host. Without the flag the launcher refuses rather than give false safety.\n- Environment secrets are scrubbed by name before exec, so a secret passed under an unrecognised variable name still reaches the agent. Prefer giving a jailed agent its key through a config file inside the directory.\n- It reads paths and the kernel's verdict, not file contents. It tells you what was reached for, not what was in it.\n\n## Community questions\n\n**Do I need a container or a VM?**\nNo. `agent-jail` applies a Landlock ruleset to the process itself, with no image and no guest OS. The agent runs as a normal process that happens to wake up already confined.\n\n**Will the jail break the tools `omp` runs?**\nTools that stay inside the project work normally. A child that reaches outside (a `git` reading `~/.gitconfig`, say) is refused like any other escape, because the ruleset is inherited across `exec`. Grant an extra path with `--allow` if a tool legitimately needs one.\n\n**Why don't I see any escape attempts?**\nBecause a working jail produces none beyond startup, and the watcher hides benign system and scratch reads. Run `--audit` to see what the agent reaches for with the jail off.\n\n**Is it safe to run against real work?**\nThe enforcement is the Landlock kernel API, and the watcher is passive. It reads file paths and verdicts, so treat its output like any tool that can see filesystem metadata. It is experimental, so do not rely on it as your only barrier yet.\n\n**How is this different from running the agent in Docker?**\nA container isolates with namespaces and a separate filesystem view; `agent-jail` leaves the agent in your filesystem and lets the kernel refuse the paths outside one directory. No image build, no volume mounts, and the eBPF view shows you each refusal as it happens.\n\n## Building from source\n\n```sh\nmake            # clang + bpftool build the BPF object, cc builds the launcher, esbuild bundles the JS\nmake adversary  # build, then run the breakout self-test (must report 0 leaks)\nmake clean\n```\n\nNeeds `clang`, `bpftool`, and a C compiler for the BPF object and the launcher, plus `node` and `npm` for the bundle step. The compiled BPF object, the launcher binary, and the bundled JS are gitignored; `make` regenerates them.\n\n## License\n\nThe BPF program declares `SEC(\"license\") = \"Dual BSD/GPL\"`, required because it uses GPL-only kernel helpers. No repository-wide LICENSE file is present.\n\n---\n\nBuilt with [yeet](https://yeet.cx/docs/?utm_source=github\u0026utm_medium=readme\u0026utm_campaign=agent-jail), a JS runtime for writing eBPF programs on Linux machines. Join us on [discord](https://discord.gg/dYZu9PjKB?utm_source=github\u0026utm_medium=readme\u0026utm_campaign=agent-jail).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeet-src%2Fagent-jail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyeet-src%2Fagent-jail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeet-src%2Fagent-jail/lists"}