Skip to content
All posts

The Permission Prompt Is the Attack Surface: AI Coding Agent Security in October 2026

Devdatta Narawade13 min read

Nobody is going to write a launch post about a permission checkbox. Which is exactly why it is where the damage happens.

In the last ten days the three biggest coding agents shipped work on steering, context, and sandbox flags: Claude Code landed v2.1.305 and v2.1.306 on October 20–21, Codex CLI shipped 0.163.0 on October 15, Cursor shipped CLI 2026.10.08, and GitHub's Copilot moved local sandboxing to GA on October 7. Read the changelogs quickly and it looks like a month of ergonomics.

Read them again and every item is a decision about who is allowed to do what, decided by a human at the exact moment the human is most tired. Then read the October research: a paper submitted on October 16 gets production agents to ask for dangerously broad permissions 58% of the time, and a second paper on October 10 shows that a tool allowlist loses 66 of 74 attacks because it checks a name instead of an object.

That is the story worth your attention. Not a smarter model. A weaker gate.

What actually shipped (and why each item is a permission change)

Claude Code v2.1.305 — the sandbox gets a documented escape hatch

On October 20, Anthropic added sandbox.allowNestedUserNamespaces. The need is real: sandboxed commands that cannot create user namespaces break Chromium and rootless podman, and Chromium is how a lot of agents take screenshots or run browser tests.

The changelog then says the quiet part out loud: this lets them get around the sandbox's file protections. A flag whose purpose is to unblock a browser is, structurally, a flag that unblocks the thing the sandbox exists to stop. That is not a bug in the release; it is a tradeoff shipped as a setting, with the risk documented in the same paragraph.

The same release fixed a regression that made every request fail on Vertex and Bedrock when a gateway rejects tool search, and added a retry watchdog for usage limits lifted before reset. v2.1.306 (October 21) is two fixes: claude -p in cloud sessions reporting "Not logged in", and an API error being replaced by "prompt is too long" in sessions with many MCP tools.

Codex CLI 0.163.0 — the state you resume inherits your permissions

Codex's headline feature is mid-turn steering: send a follow-up while the turn is still running, and interrupting now also stops active Code Mode cells and their nested tool calls. Paired with parallel read-only tools (skills, memory, conversation history) and a searchable /config, it is a month of making a long session feel controllable.

The security-relevant line is buried: resuming a saved thread restores its permissions and network approvals, while respecting explicit overrides and current managed policy. That is the correct behaviour, and it is also the mechanism that makes a one-time click permanent. The same release hardened the network proxy — permission is checked before DNS lookup, duplicate Host headers are rejected, and credential aliases can no longer slip past inspection hooks. All three are the same class of bug that made a September CVE a 9.4.

Cursor CLI 2026.10.08 — auto-review learns the delete that lies

Cursor tightened Auto-review to ask before recursive, force, wildcard, or mirror deletes whose target could resolve differently than written — an unset variable, a stray space in the path, a glob that expands wider than it reads. It also added spec validation to /skills: names over 64 characters, non-hyphenated names, a frontmatter name that does not match the folder, descriptions over 1,024 characters, bodies over ~5,000 tokens.

That is the healthiest thing shipped this month. A guardrail whose job is to notice that the command you approved is not the command that will run is worth more than another eval point. The skills validator is the same instinct one level up: a skill is executable policy, so its shape is now checked.

Meanwhile GitHub made local sandboxing GA for Copilot CLI, the Copilot app, and VS Code Agent Host sessions on October 7 — no extra cost, bounded file, network and credential access. Sandboxing is no longer a differentiator. Default-on is.

The finding: a permission request is an input channel

"From Approval to Abuse: Privilege Escalation Attacks on Harness-Protected Coding Agents" (arXiv 2610.22987, Yang et al., NUS / NTU / Ant Group, October 16) makes one argument that reframes the whole discussion.

Execution harnesses intercept out-of-sandbox actions and prompt the user. To fight approval fatigue, users can accept an agent-proposed allow-rule, after which matching actions run silently forever. The paper's claim is that untrusted context can therefore influence the permissions presented for approval. Adversarial content planted in a repository — a README, a fixture, a comment in a file the agent was asked to fix — steers a routine task into requesting a rule that looks reasonable and is not.

The numbers: allow-rule elicitation succeeded on 58% of experimental instances across Claude Code and Codex CLI, and an acquired rule covered 56.9% of the category-specific capability test cases — the gap between the rule you think you approved and what it actually permits.

Why 58% is an architecture number, not a model number

A smarter model does not fix this, because the model is not the component being fooled. The approval dialog is a UI that renders a proposal generated from tainted context, and the human approves the summary, not the semantics. Upgrade the model and the proposal gets more persuasive, not narrower.

The paper's real contribution is naming persistence. Approving a rule is not approving an action; it is switching the harness off for a class of actions. One decision, standing effect, no expiry.

The blast radius is the actual metric

56.9% is the number to put on a dashboard. A rule like "allow npm commands" sounds narrow until you notice npm can run arbitrary lifecycle scripts and fetch from any registry. A rule like "allow git" covers git config, git push, and a hook that runs on checkout. You are not granting a command; you are granting a namespace.

Names are not objects

"Names Are Not Objects: Object-Bound Confinement for Compromised AI Coding Agents" (arXiv 2610.14488, October 10) goes after the same wound from the other side.

Most deployed agent exploits share a shape: a check approves a name — a path, an argv, a directory — and a later step maps that name to a different object. The file behind a symlink. The behaviour an option selects. The repository git discovers on its own two directories up.

The evaluation is blunt: against 74 attacks with ground-truth oracles, including replays of ten publicly disclosed incidents, a tool allowlist loses 66. The proposed alternative, Zero-Trust Agent Cells, runs the agent in a kernel sandbox whose only route to files, programs and network is a socket to a broker. The broker resolves each request to the object it will act on, decides on the object, and re-checks at use that it is still the object it authorised. Approvals are object-bound and single-use. The macOS prototype is 3.2k lines of trusted Rust.

Three name confusions you can test for this week

  • Path vs. target. Ask the agent to write inside ./build. A symlink turns that into /etc or your home directory without changing the string you approved.
  • Tool vs. invocation. An allowlist entry for bash with a "safe" prefix is a rule about text. bash -c "$(cat secrets.txt)" and bash script.sh are the same prefix.
  • Repo vs. resolved repo. Git can resolve to a parent worktree, an fsmonitor hook, or a submodule. The path you named and the repository that executes are different objects.

What object-bound approval looks like in practice

You do not need 3.2k lines of Rust to get most of the benefit. You need approvals to name a resolved object, a scope, and an expiry — and then to verify at use:

{
  "rule": "fs.write",
  "object": "/repo/build/**",
  "resolvedFrom": "symlink-checked",
  "scope": "session",
  "expires": "2026-10-22T18:00:00Z",
  "singleUse": false,
  "requires": ["path.startsWith(repoRoot)", "not(isSymlink(target))"]
}

If a rule cannot express its object, it is a vibe, not a permission.

The escapes that already happened

Three disclosures make the trend concrete, and each one fails differently.

DeepSeek Harness, CVE-2026-82533 (September 8, CVSS 9.4). The agent-control API listened on a local HTTP port without authentication and trusted a client-supplied Host header instead of the peer address. The OS sandbox restricted file writes but left loopback networking open, so a sandboxed agent could issue one request and elevate itself to danger-full-access with prompts disabled. The sandbox was not broken. The sandboxed process was allowed to reach its own control plane.

SharedRoot (July 2026, CSA research note). An unprivileged Claude Cowork session escaped to the host Mac and read private keys and cloud credentials with no prompt. The chain needed no single bug: unprivileged user namespaces plus a seccomp policy allowing netlink traffic-control calls, a generic kernel privilege escalation (CVE-2026-46331), and a writable whole-host mount at /mnt/.virtiofs-root. Anthropic classified it "Informative" and shipped no fix for local execution; the default routes execution to the cloud instead, which sidesteps the path for default users and leaves the targeted local mode unpatched.

Pillar Security's "Week of Sandbox Escapes" (July 20, 2026). Reproducible escapes across Cursor, Codex, Gemini CLI and Antigravity, clustered into four failure modes: denylist sandboxes that cannot keep pace with OS complexity, workspace configs that are executable code, command allowlists that trust the name rather than the invocation, and privileged local daemons sitting outside the sandbox entirely.

Read the three together and sandbox.allowNestedUserNamespaces stops looking like a footnote. User namespaces are the first link in the SharedRoot chain. A vendor cannot both harden against that chain and ship a flag that re-enables its entry point by default for anyone who needs a browser.

A permission model that survives an untrusted repo

Six rules, in the order they pay off.

1. Assume the repository is hostile. A cloned repo is input from a stranger. That includes the README, project rule files, agent config, test fixtures, and package scripts. See why config became code if you still read agent config as documentation.

2. Deny by default, allow by object. The allowlist should describe resolved objects — a file under a checked root, a specific binary with a specific argv shape, a host and port — not command prefixes. Where the harness cannot express the object, the answer is "ask every time", not "trust the prefix". The prompt is a UI; the gate should be deterministic. That is the argument for lifecycle hooks, covered in fail-closed hooks.

3. Expire every standing rule. Session scope by default. Anything that outlives the session needs an owner, a reason, and a review date. Codex restoring approvals on resume is correct and still means an old click can outlive the task that justified it.

4. Close the loop back to the agent. No control API reachable from inside the sandbox. Bind to a unix socket with peer-credential checks, or authenticate every request with a token the sandbox cannot read. CVE-2026-82533 was a one-command privilege escalation because the agent could talk to its own supervisor.

5. Keep credentials out of the process tree. An agent that can read your cloud keys, registry tokens, or app secrets does not need to break the sandbox; it just needs to be helpful. Mount secrets per-command, or not at all.

6. Log the approval, not just the action. Record what was proposed, what was approved, who approved it, and what actually ran. When a rule turns out to be too broad — and one will — the log is the difference between a rollback and a rewrite.

The cost angle matters here too: broad rules are cheaper because they stop the interruptions. That is the entire mechanism of the attack, and it is why "we saved 40 prompts a day" is not a security argument. See the harness tax for how the same economics show up in spend.

Monday morning, thirty minutes

  1. Grep your saved approvals. The global and project settings files for each agent, plus project rule directories. Delete every rule that names a command prefix or a directory root. You will find at least one you cannot defend.
  2. Pin versions and read the security lines of the changelog. v2.1.305, 0.163.0 and 2026.10.08 all changed what gets approved and when. Deploying a permission change is a security change.
  3. Leave nested user namespaces off unless a task needs a browser, and when it does, run that task in a container with no host mounts. The flag is a capability, not a default.
  4. Add one adversarial fixture. A file whose comment politely suggests a broader permission. If the agent asks for it, you have found a real weakness before an attacker does.
  5. Re-read your review gate. A human approving agent output under time pressure is the same failure as a human approving an allow-rule under time pressure. Passing tests prove less than you think applies to approvals too.

FAQ

What is an allow-rule in an AI coding agent?

A saved permission that lets matching actions run without prompting. Claude Code, Codex CLI and Cursor all offer them to reduce approval fatigue. Because they persist and apply to a class of actions, one approved rule can silently authorise far more than the task that produced it.

How often can an attacker get a coding agent to request a broad allow-rule?

In the October 2026 study "From Approval to Abuse", allow-rule elicitation succeeded on 58% of experimental instances across Claude Code and Codex CLI, and acquired rules covered 56.9% of category-specific capability tests. The content was planted in a repository the agent was working in.

Is sandbox.allowNestedUserNamespaces unsafe?

It is a real tradeoff. Added in Claude Code v2.1.305, it lets sandboxed commands create user namespaces, which Chromium and rootless podman need. The changelog states it also lets those commands get around the sandbox's file protections. Enable it per task, not globally, and keep host mounts read-only or absent.

Can a sandboxed agent escape by talking to itself?

Yes — CVE-2026-82533 in DeepSeek Harness (CVSS 9.4) let a sandboxed agent reach an unauthenticated local control API and elevate itself to danger-full-access with prompts off. If the agent's own supervisor is reachable over loopback, the sandbox is advisory.

Do tool allowlists stop prompt injection?

Poorly. In "Names Are Not Objects", a tool allowlist lost 66 of 74 attacks because it approves a name while a later step resolves that name to a different object — a symlink target, an option's behaviour, a parent repository. Approvals need to bind to the resolved object and be re-checked at use.

The gate is the product

None of this argues for turning agents off. It argues for being precise about what you approved.

The frontier labs spent October making long sessions feel controllable: mid-turn steering, parallel reads, idle compaction, restarted daemons. Those are good changes. They also lengthen the window in which a standing rule from three hours ago is still silently authorising work you have stopped watching.

The agents got better this week. The permissions did not. Fix the second one and the first one is finally safe to use unattended. For the attack surface underneath, see AI coding agent supply-chain attacks; for the isolation story, see the unbundled coding agent harness; and for the release wave that started the month, see what actually changed in October.

Sources

Related reading