Last week, security researcher Joernchen published a clever RCE in Claude Code 2.1.118. I spent Saturday reproducing it from the advisory to understand the pattern. The bug is fixed now, but the parsing anti-pattern behind it is everywhere in AI developer tools.
The setup
Claude Code registers a deeplink handler: claude-cli://open. Click it in a browser, Slack, email — anywhere — and the OS spawns Claude Code with the URL’s query parameters passed as CLI arguments.
The vulnerability lives in eagerParseCliFlag, a function in main.tsx that pre-processes critical flags like --settings before the main argument parser runs. The code pattern:
startsWith on raw args. No context awareness. No understanding of whether that string is a flag, a value, or nested inside another flag’s value.
The injection
The deeplink handler uses --prefill to populate the user prompt from the URL’s q parameter. But because eagerParseCliFlag naively scans the entire argument array, an attacker can smuggle --settings inside --prefill‘s value:
The eagerParseCliFlag loop sees --settings=/tmp/evil.json inside the --prefill value and loads it as legitimate configuration.
The payload
A crafted settings file at that path:
Claude Code spawns. The session starts. The hook fires. Arbitrary shell execution.
The trust bypass
Here’s what elevates this from annoying to dangerous: the repo parameter.
If repo points at a repository the user has already cloned and trusted — like anthropics/claude-code — the workspace trust dialog never appears. The user clicks a link, Claude Code opens silently, and the attacker’s settings are already loaded.
Joernchen’s example used anthropics/claude-code specifically because it’s the tool’s own repo. Most users who’ve run Claude Code have implicitly trusted it.
Why this pattern matters
This isn’t a memory corruption bug. It’s not a prompt injection. It’s a CLI parsing anti-pattern in a tool that bridges the web and your shell.
AI coding tools are rushing to add deeplinks, browser integrations, “open in IDE” flows. They’re handling untrusted input — URLs from the web — with parsing logic that assumes trusted, hand-typed arguments.
The startsWith pattern appears elsewhere. Joernchen flagged it as a systemic issue:
The fix
Anthropic patched this in 2.1.119. The deeplink handler now passes arguments through the proper CLI parser before eagerParseCliFlag processes them. The nested --settings inside --prefill is correctly treated as a value, not a flag.
What you should check
If you ran Claude Code 2.1.118 or earlier:
- Check
~/.claude/settings.jsonfor unexpectedhooksentries - Review
~/.claude/projects/*/settings.jsonin trusted projects - If you clicked any
claude-cli://links from untrusted sources, assume compromise
What builders should do
- Deeplink arguments are untrusted. Parse them with the same rigor you’d use for HTTP query parameters.
- One parser, not two. If you need early flag processing, use the same parser for everything. Don’t pre-scan with
startsWith. - Trust dialogs should verify path/content, not string matches. Trusting
anthropics/claude-codeby name means any fork or namesquat passes the check.
Joernchen’s original writeup at 0day.click has the full source analysis and timeline. Worth reading if you’re building anything with deeplink-to-CLI flows.