{"slug": "governed-is-not-confined", "title": "Governed Is Not Confined", "summary": "A live Claude Code session governed by the kernel granted requests to read /etc/shadow and write ~/.bashrc exactly as it granted reading the project's own README, revealing that the governance manifest controls action types and trust flow but has no spatial concept of confinement. The taint floor, which blocks network exfiltration after untrusted content is touched, does not stop an agent from reading a local secret and sending it out because local reads are considered clean and do not taint the session.", "body_md": "# Governed Is Not Confined\n\nWe turned our governance on against our own agent — a live Claude Code session running\nunder the harness, every tool call passing through the kernel first. Then we did the thing\nyou should always do with a safety claim: we tried to break it. We asked the governed agent\nto read `/etc/shadow`\n\n. To write `~/.bashrc`\n\n. Files that have nothing to do with the project\nit was working in.\n\nIt said yes to all of them — and not grudgingly. It granted them **exactly** the way it\ngrants reading the project’s own `README`\n\n. Same verdict, no hesitation, no difference.\n\nThat surprised us less than it should have, and the reason why is the whole point of this post.\n\n## What we assumed “governed” meant\n\nWhen you wire a governance hook into a project and watch it start denying things, the\nnatural story in your head is: *the agent is now contained here.* Fenced to this folder.\nKept away from the rest of the disk. That’s what “sandbox” trains you to expect, and\n“governed” borrows the feeling.\n\nIt’s the wrong story, and our own agent proved it in one command.\n\n## What the kernel actually sees\n\nThe request the kernel decides on is small. It’s the tool being called, its arguments, and\na little context — which session, what mode, and the taint state. That’s it. Read it again\nand notice what’s *not* there: **no path.** No working directory, no notion of “inside the\nproject” versus “outside” it. When the agent asks to read a file, the kernel sees “a Read”\n— not *where*.\n\nSo `Read /etc/shadow`\n\nand `Read ./README`\n\naren’t two things the kernel weighs differently.\nThey’re the *same* thing: a Read. There is no “own directory” anywhere in the machinery.\nThe project folder mattered exactly once — at setup, to decide *which sessions* get\ngoverned at all. After that, it’s invisible. The gate governs *what kind of action* the\nagent takes and *where its data came from*. It never governs *where the action lands.*\n\n## Two axes people fold into one word\n\nHere’s the distinction the `/etc/shadow`\n\nmoment forced us to say out loud. “Keeping an\nagent safe” is really two different jobs, and “governed” only names one of them:\n\n**Governance**— controlling*what kind of action*is allowed and*how trust flows*through it. Is this an action the agent may take? Did the thing driving it come from a source we trust? This is what our manifest does.**Confinement**— controlling*where*the agent can reach. Can it leave this folder? Can it touch`~/.ssh`\n\n? This is a*spatial*question, and our manifest has no concept of space at all.\n\nThey feel like the same thing. They are not. You can be fully governed and completely\nunconfined — which is exactly what our agent was. It couldn’t be tricked into an action it\nshouldn’t take (that part works), but nothing stopped it from taking a perfectly ordinary,\nfully-approved action *against a file across the filesystem.*\n\n## The part that’s easy to miss\n\nThere’s a sharper edge, and honesty demands we name it. Our one real runtime protection is\nthe *taint floor*: once the session has touched something untrusted — a web page, a tool\nresult — it can no longer reach back out to the network. That’s what stops a\n[poisoned document](/blog/the-zombieagent-threat/) from exfiltrating a secret.\n\nBut reading a **local** file is *clean*. It doesn’t taint the session, because the file came\nfrom your own disk, not from an attacker. Which means: read a local secret, then send it\nsomewhere, and the taint floor **does not stop you** — the session was never tainted. The\nfloor defends against *injection* (untrusted content driving an action), not against an\nagent exfiltrating files you already trusted. Different threat, different axis, again.\n\nIt wasn’t always so. An earlier version of the engine *did* taint reads — it even recorded\nwhich file did it — and that behavior was quietly set aside during a rewrite. So the defense\nfor exactly this case has existed before; restoring it, as something you *declare* per path\nrather than something hard-coded, is part of the fix below.\n\n## Is this a bug? No — it’s a missing primitive\n\nWe want to be precise, because “governance tool can read /etc/shadow” is the kind of\nsentence that sounds like a scandal and isn’t. The harness does what it claims: it governs\nthe *ontology of actions* and the *flow of trust*. Spatial confinement was never in it. It’s\nnot broken; it’s **incomplete** — a primitive we simply hadn’t built.\n\nAnd the shape of that primitive is clear. The Model Context Protocol already has a concept\ncalled **roots** — a declared set of directories an agent is scoped to. Our manifest has\nnothing like it, and noticing that *absence* is what named the gap. The fix is\nunglamorous and deterministic:\n\n- Let the manifest declare\n**roots**— the directories in scope. - Give the kernel the action’s\n**path**(the one thing it’s currently blind to). - Compare: a Read or Write\n**under a root** is allowed;**outside** one, it’s asked or denied — and a path under a sensitive root can finally carry the`Secret`\n\nlabel the manifest already has a word for but no way to attach.\n\nNo model, no guessing — a path comparison. The kind of boring, checkable rule the whole approach is built on.\n\n## The takeaway\n\nIf you’re governing an agent, know **which axis your governance is on.** Ours is on trust\nand action — genuinely useful, and the thing most “permission” systems can’t do. But it is\nnot a jail, and we were one command away from believing it was.\n\nFor actual confinement — keep the agent off the rest of the disk — you still need the other\naxis: an OS-level sandbox, a [throwaway container](/blog/running-claude-safely/) with the\nfilesystem and network fenced, or path-scoped capabilities once we ship them. A trust\nmonitor and a jail are different tools. The mistake isn’t using one; it’s using one and\nthinking you have both.\n\nWe found this by pointing our own review at our own live agent — the same move that keeps\nturning up the most useful things we know. “Governed” felt like enough. Then we asked it to\nread the password file, and it taught us the word we were missing: *confined.*", "url": "https://wpnews.pro/news/governed-is-not-confined", "canonical_source": "https://ai2rules.dev/blog/governed-is-not-confined/", "published_at": "2026-07-22 00:00:00+00:00", "updated_at": "2026-08-15 07:42:46.042390+00:00", "lang": "en", "topics": ["ai-safety", "ai-agents", "ai-policy"], "entities": ["Claude Code"], "alternates": {"html": "https://wpnews.pro/news/governed-is-not-confined", "markdown": "https://wpnews.pro/news/governed-is-not-confined.md", "text": "https://wpnews.pro/news/governed-is-not-confined.txt", "jsonld": "https://wpnews.pro/news/governed-is-not-confined.jsonld"}}