{"slug": "73-of-what-we-blocked-in-a-month-was-one-syscall-your-sandbox-cannot-see", "title": "73% of What We Blocked in a Month Was One Syscall Your Sandbox Cannot See", "summary": "Grith, a security proxy for AI coding agents, reported that 73% of the 18,518 events it blocked over 29 days were io_uring_setup syscalls, totaling 13,613 denials, because io_uring bypasses seccomp-based sandboxes. The company warns that standard Linux sandboxes cannot see I/O operations submitted through io_uring rings, and notes that Docker, containerd, and Google have already blocked io_uring by default.", "body_md": "# 73% of What We Blocked in a Month Was One Syscall Your Sandbox Cannot See\n\nA security proxy for AI coding agents, enforced at the OS level. [Install grith](https://docs.grith.ai/docs/start/installation) and put a real boundary around your agent.\n\nFor the last month, every AI coding session on this machine ran under [grith](/), which intercepts every syscall an agent makes and scores it before the kernel acts. That is 2,129,319 intercepted events across 29 days, most of them from Claude Code and Codex doing ordinary work.\n\n18,518 of those events were denied. We expected the denials to be the interesting part - a credential read here, a connection to somewhere it should not go there. They were not. **73% of everything we blocked was a single syscall: io_uring_setup.** 13,613 attempts, every one refused.\n\nThat is worth sitting with, because io_uring is not an obscure edge case. It is the one I/O interface that a seccomp-based sandbox - the standard way people contain untrusted processes on Linux - cannot see into at all.\n\n## Why seccomp cannot see io_uring\n\nA normal syscall sandbox works because the application has to ask the kernel for everything through syscalls, and seccomp inspects each one:\n\n``` php\nagent -> openat(\"/home/you/.ssh/id_ed25519\")  -> seccomp inspects -> allow / deny\nagent -> connect(1.2.3.4:443)                  -> seccomp inspects -> allow / deny\n```\n\nThe security model rests on a single assumption: the application cannot perform an operation it never makes a syscall for.\n\nio_uring breaks that assumption. You call `io_uring_setup`\n\nonce to create a ring - a pair of queues in memory shared between the process and the kernel. After that, the process submits operations by writing entries into the ring: `IORING_OP_OPENAT`\n\n, `IORING_OP_CONNECT`\n\n, `IORING_OP_READ`\n\n, `IORING_OP_WRITE`\n\n, and dozens more. The kernel executes them asynchronously. With submission-queue polling enabled, the kernel picks the entries up on its own and **no further syscall is made at all**.\n\nSo a seccomp filter that carefully denies `openat`\n\nand `connect`\n\nis trivially bypassed: do not call `openat`\n\n, submit `IORING_OP_OPENAT`\n\nto the ring instead. The read of your SSH key happens, and the filter watching `openat`\n\nsees nothing, because `openat`\n\nwas never called.\n\n``` php\nagent -> io_uring_setup()          -> ring created\nagent -> [ IORING_OP_OPENAT ] ---.\nagent -> [ IORING_OP_CONNECT ] --+-> kernel executes, no syscall per op\nagent -> [ IORING_OP_READ ] -----'    seccomp sees nothing\n```\n\nThere is exactly one place a seccomp filter can intervene: `io_uring_setup`\n\nitself, which is a real syscall. Block the ring from ever being created, and none of the rest can happen. Allow it, and you have handed the process an I/O channel your policy does not govern.\n\n## The industry already decided this\n\nIf this sounds like an argument someone would resist, they mostly have not. Blocking io_uring at the sandbox boundary is now the default position across the ecosystem:\n\n**Docker** blocks the io_uring syscalls in its default seccomp profile as of 25.0.[1](#user-content-fn-1)**containerd** did the same for its`RuntimeDefault`\n\nprofile.[2](#user-content-fn-2)**Google** turned io_uring off in ChromeOS, Android, and across production servers, citing the volume of exploitable vulnerabilities and the difficulty of sandboxing it.[3](#user-content-fn-3)\n\nThe consensus is not that io_uring is bad engineering - it is a genuinely excellent piece of high-performance I/O. The consensus is that its security model and the seccomp security model do not compose, so anything relying on seccomp to contain an untrusted process has to refuse io_uring outright. That is a trade-off - you lose the performance - and for a sandbox it is the correct one.\n\n## Both agents reach for it constantly\n\nHere is where the month of data matters. io_uring is not a theoretical concern you might one day encounter. In real AI coding sessions it comes up in nearly every one:\n\n**Claude Code: 137 of 150 sessions** attempted io_uring, 11,162 attempts in total.**Codex: all 31 of 31 sessions**, 2,451 attempts.\n\nThe median session made 8 or 9 attempts. The busiest - a long Claude Code session - made **1,713**, spread across two and a half hours of work. Whatever is reaching for io_uring is doing so as a matter of course, over and over, in ordinary sessions doing ordinary tasks.\n\n## This is not an escape attempt, and that is the point\n\nWe are not going to tell you Claude Code and Codex are trying to break out of a sandbox. They almost certainly are not. io_uring attempts at this volume have the fingerprint of an async runtime reaching for the fastest available I/O backend - the sort of thing libuv, or a Rust async runtime, or a language standard library does on your behalf without the agent, or its authors, thinking about it at all. It is a performance optimisation, not an exploit.\n\nThat is exactly why it matters. **The soundness of a sandbox cannot depend on the intent of the thing inside it.** A benign runtime reaching for io_uring to go faster and a deliberate attempt to route a credential read around your filter submit the identical operation to the identical ring. Your seccomp policy cannot tell them apart, because from the syscall layer it sees neither. If the ring exists, your enforcement has a hole in it, and whether that hole is used for good or ill is not a question your security model gets to ask.\n\nWhich means the only safe assumption is the pessimistic one: if an agent's runtime can open a ring, treat your syscall-level policy as advisory.\n\n## What grith does about it\n\ngrith enforces at the syscall level too - ptrace with a seccomp-BPF pre-filter - so it inherits exactly the same blind spot. An io_uring ring would let the agent's I/O flow past grith's own scoring the same way it would past any other seccomp sandbox. grith's answer is the industry's answer: it refuses to let the ring open.\n\n`io_uring_setup`\n\nis denied structurally, before it is ever scored. In the audit log every one of the 13,613 attempts carries the same shape - syscall 425, hard-denied, EPERM returned, `io_uring denied before proxy evaluation`\n\n. There is no threshold to tune and no context that makes it allowable, because allowing it would quietly blind everything else grith does. It is one of the few operations grith treats as non-negotiable rather than scoring on a curve.\n\nThe cost is real and worth stating: an agent runtime that genuinely wanted io_uring for performance does not get it under grith, and falls back to ordinary syscalls. In exchange, the file reads and network connects that follow are ones grith can actually see. For a supervisor, that is the whole job.\n\n## What this means for you\n\nIf you run AI coding agents inside any seccomp-based sandbox - a container with a custom profile, firejail, a homegrown wrapper - check whether it blocks io_uring. Recent Docker and containerd do by default; an older base image or a hand-rolled profile may not. If it does not, the careful `openat`\n\nand `connect`\n\nrules you wrote are enforceable only for as long as the agent's runtime chooses to use `openat`\n\nand `connect`\n\n, and this month's numbers say it reaches for the alternative thousands of times a session.\n\nThe broader lesson is the one behind everything we measure at the syscall boundary: the interesting security questions about AI agents are not answered by what the agent says it is doing, but by what its process actually asks the kernel for - including the requests it makes through channels most tools are not watching.\n\n## About grith\n\ngrith is an open-source security supervisor for AI coding agents. It sits underneath the agent rather than inside it: on Linux, ptrace with a seccomp-BPF pre-filter intercepts every syscall the agent makes and scores it against 18 filters before the kernel executes it. Operations it cannot safely allow - io_uring ring creation among them - are refused at the boundary, and everything it evaluates lands in a local audit log you can reconstruct a month of sessions from, which is exactly what this post is.\n\nIt is Rust, MPL-2.0, a single static binary, and runs entirely on your own machine. Linux x86_64 and aarch64 today.\n\n- Repo:\n[github.com/grith-ai/grith](https://github.com/grith-ai/grith) - Site and docs:\n[grith.ai](https://grith.ai) - Try it:\n`curl -fsSL https://grith.ai/install | sh`\n\n, then`grith exec -- claude-code \"fix the bug\"`\n\n## Footnotes\n\n-\nmoby/moby #46762, \"seccomp: block io_uring_* syscalls in default profile\" - the io_uring syscalls were removed from Docker's default allowlist, landing in Docker 25.0.\n\n[https://github.com/moby/moby/pull/46762](https://github.com/moby/moby/pull/46762)[↩](#user-content-fnref-1) -\ncontainerd #9320, \"Update RuntimeDefault seccomp profile to disallow io_uring related syscalls.\"\n\n[https://github.com/containerd/containerd/pull/9320](https://github.com/containerd/containerd/pull/9320)[↩](#user-content-fnref-2) -\nGoogle's security team disabled io_uring in ChromeOS and Android and across production servers, citing the concentration of exploitable vulnerabilities and the difficulty of sandboxing the interface. See the Docker and containerd discussions above for the container-side reasoning, which cites the same conclusion.\n\n[↩](#user-content-fnref-3)\n\nLike this post? Share it.", "url": "https://wpnews.pro/news/73-of-what-we-blocked-in-a-month-was-one-syscall-your-sandbox-cannot-see", "canonical_source": "https://grith.ai/blog/io-uring-the-syscall-your-sandbox-cant-see", "published_at": "2026-09-04 08:24:52+00:00", "updated_at": "2026-09-04 08:53:14.444498+00:00", "lang": "en", "topics": ["ai-safety", "ai-tools", "ai-infrastructure"], "entities": ["Grith", "Claude Code", "Codex", "Docker", "containerd", "Google", "ChromeOS", "Android"], "alternates": {"html": "https://wpnews.pro/news/73-of-what-we-blocked-in-a-month-was-one-syscall-your-sandbox-cannot-see", "markdown": "https://wpnews.pro/news/73-of-what-we-blocked-in-a-month-was-one-syscall-your-sandbox-cannot-see.md", "text": "https://wpnews.pro/news/73-of-what-we-blocked-in-a-month-was-one-syscall-your-sandbox-cannot-see.txt", "jsonld": "https://wpnews.pro/news/73-of-what-we-blocked-in-a-month-was-one-syscall-your-sandbox-cannot-see.jsonld"}}