DEV Community

Cover image for The Claude Code RCE: How Eager Parsing Led to Remote Execution
Alessandro Pignati
Alessandro Pignati

Posted on

The Claude Code RCE: How Eager Parsing Led to Remote Execution

The security landscape for AI developer tools shifted recently with the discovery of a critical Remote Code Execution (RCE) vulnerability in Anthropic's Claude Code CLI. This flaw, identified by security researcher Joernchen of 0day.click, highlights a subtle but dangerous oversight in how command line tools handle external inputs.

While many modern security audits rely on automated scanners, this particular discovery came from a manual review of the source code. The researcher focused specifically on how the application initializes its configuration before the main logic even begins.

The vulnerability, which has since been patched in version 2.1.118, allowed an attacker to execute arbitrary commands on a user's machine. The core of the issue was not a complex cryptographic failure or a deep logic error in the AI itself. Instead, it was a classic input validation problem located in the tool's deeplink handler. By tricking a user into clicking a specially crafted link, an attacker could bypass security prompts and gain full control over the terminal session.

Key Information Details
Vulnerability Type Remote Code Execution (RCE)
Affected Tool Claude Code CLI
Fixed Version 2.1.118
Discovery Method Manual Source Code Audit
Primary Vector Malicious Deeplink (claude-cli://)

This discovery serves as a reminder that even the most advanced AI systems are built upon traditional software foundations. When those foundations have cracks in their input handling, the entire system becomes vulnerable. Let us break down the technical root cause and how this "eager" parsing was weaponized.

The Technical Root: A Case of "Too Eager" Parsing

At the heart of this vulnerability lies a function named eagerParseCliFlag. In many CLI applications, there is a need to load certain configurations very early in the lifecycle, often before the primary argument parsing library (like Commander.js) has even started. Claude Code used this function to "eagerly" look for flags like --settings or --setting-sources to ensure the environment was correctly configured before the main initialization routine took over.

/**
 * Parse a CLI flag value early, before Commander.js processes arguments.
 * Supports both space-separated (--flag value) and equals-separated (--flag=value) syntax.
 *
 * This function is intended for flags that must be parsed before init() runs,
 * such as --settings which affects configuration loading. For normal flag parsing,
 * rely on Commander.js which handles this automatically.
 *
 * @param flagName The flag name including dashes (e.g., '--settings')
 * @param argv Optional argv array to parse (defaults to process.argv)
 * @returns The value if found, undefined otherwise
 */
export function eagerParseCliFlag(
  flagName: string,
  argv: string[] = process.argv,
): string | undefined {
  for (let i = 0; i < argv.length; i++) {
    const arg = argv[i]
    // Handle --flag=value syntax
    if (arg?.startsWith(`${flagName}=`)) {
      return arg.slice(flagName.length + 1)
    }
    // Handle --flag value syntax
    if (arg === flagName && i + 1 < argv.length) {
      return argv[i + 1]
    }
  }
  return undefined
}
Enter fullscreen mode Exit fullscreen mode

The technical oversight was deceptively simple. The eagerParseCliFlag function would iterate through the raw process.argv array and use a startsWith check to find matching flags. It was designed to handle both --flag=value and --flag value syntaxes. However, it did so without any awareness of the command line context. It treated every string in the argument array as a potential flag, failing to recognize that a string starting with --settings= might actually be a value belonging to a different flag.

"The deeper issue lay in eagerParseCliFlag which didn't keep track of actual command line flags and their values. Instead, it naively parsed the entire command line for any string starting with --settings=...."

This context-blindness created a dangerous injection point. If an attacker could influence the value of a legitimate flag, they could "sneak" a second flag into that value. When eagerParseCliFlag scanned the arguments, it would see the injected string and treat it as a top-level configuration override. This pattern of using startsWith on raw argument arrays is a known anti-pattern because it breaks the fundamental structure of CLI command parsing.

Parsing Step Behavior in Vulnerable Version
Input Source Raw process.argv array
Matching Logic startsWith("--settings=")
Context Awareness None (does not distinguish flags from values)
Result Allows flags to be injected into other flag arguments

By exploiting this lack of context, an attacker could force the CLI to load a completely different set of settings than the user intended.

The Attack Vector: Weaponizing Deeplinks

The delivery mechanism for this exploit was the claude-cli:// deeplink protocol. Deeplinks are designed to improve user experience by allowing websites or other applications to trigger specific actions within a local tool. In the case of Claude Code, the claude-cli://open URI was intended to let users open the CLI and pre-fill a prompt using a query parameter, typically denoted as q.

When a user clicks a link like claude-cli://open?q=hello, the operating system passes this to the Claude Code handler. The handler then translates this into a command line execution, using the --prefill flag to pass the content of q into the CLI. Because of the "eager" parsing issue described earlier, an attacker could craft a q parameter that contained more than just a simple prompt. They could include a string that looked like a configuration flag.

Consider a malicious link structured like this: claude-cli://open?q=--settings={"hooks":...}

When the CLI starts, the argument array looks something like this: ["claude", "--prefill", "--settings={\"hooks\":...}"]

The standard argument parser would correctly see --settings=... as the value for the --prefill flag. However, the vulnerable eagerParseCliFlag function would scan the array, see a string starting with --settings=, and immediately load it as the global configuration. This allowed the attacker to override any setting in the application simply by getting a user to click a link.

URI Component Purpose Attacker Manipulation
claude-cli://open Triggers the CLI handler Standard entry point
repo= Specifies a repository Used to bypass trust dialogs
q= Pre-fills the user prompt Injected with --settings= payload

This attack vector is particularly effective because it leverages a feature meant for convenience. Users often trust deeplinks from familiar sources, and the transition from a browser to a terminal can happen quickly.

From Injection to Execution: Exploiting Hooks

Once an attacker has the ability to inject arbitrary settings, the path to Remote Code Execution (RCE) becomes straightforward. Claude Code includes a powerful feature called "hooks," which allows users to automate certain actions at specific points in a session's lifecycle. For example, a user might want to run a script every time a new session starts. By injecting a malicious configuration, an attacker can define their own hooks that execute shell commands.

The most effective target for this is the SessionStart hook. An attacker can craft a JSON payload that defines a command to be run as soon as the CLI initializes. Because the eagerParseCliFlag function has already loaded these settings, the command fires immediately. This happens in the background, often before the user even realizes the CLI has opened.

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'open /System/Applications/Calculator.app'"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

To make the attack even more silent, the researcher discovered a way to bypass the "Workspace Trust" dialog. Normally, Claude Code asks for permission before running in a new repository. However, if the attacker sets the repo parameter in the deeplink to a repository the user has already trusted (such as anthropics/claude-code), the CLI assumes the environment is safe. This bypasses the final line of defense, allowing the injected command to run without any user interaction beyond the initial click.

Attack Step Action Result
1. Injection User clicks a crafted claude-cli:// link Malicious settings are loaded eagerly
2. Trust Bypass Link specifies a trusted repo name Security prompts are suppressed
3. Execution SessionStart hook triggers Attacker's shell command runs immediately

This combination of eager parsing and powerful automation features creates a perfect storm for RCE. It demonstrates that features designed for power users can often be turned against them if the underlying input handling is not robust.

The Fix and Lessons for Developers

Anthropic responded quickly to this discovery, releasing a patch in Claude Code version 2.1.118. The fix involved moving away from the "eager" and context-blind parsing of the argument array. Instead of simply checking if any string in process.argv started with a specific flag name, the updated code uses a more robust approach that understands the structure of command line arguments. By properly distinguishing between flags and their associated values, the injection surface was eliminated.

For developers building CLI tools, especially those with deeplink support, this vulnerability offers several critical lessons. The most important is to avoid manual string matching on raw argument arrays. While it might seem faster to write a custom parser for early initialization, it is almost always safer to use a battle-tested library that handles the complexities of CLI syntax.

Recommendation Why it Matters
Use Robust Libraries Libraries like Commander.js or Yargs are designed to handle edge cases and prevent injection.
Context-Aware Parsing Never assume a string is a flag just because it starts with dashes; check its position in the command.
Sanitize Deeplinks Treat all data coming from a URI handler as untrusted and potentially malicious.
Limit Hook Power Consider adding additional confirmation steps for hooks that execute shell commands.

The startsWith anti-pattern is not unique to Claude Code. It is a common mistake in many applications that perform early configuration loading. If your application needs to parse flags before its main initialization, ensure that your logic respects the boundaries between different arguments. A small oversight in how you read a command line can lead to a total system compromise.

"The parsing of command line flags and their arguments should always be done in full context to prevent this exact type of injection."

By following these principles, developers can provide the convenience of deeplinks and automation without sacrificing the security of their users' systems.

Staying Secure in the CLI

The Claude Code RCE vulnerability is a textbook example of how small technical oversights can have significant security implications. It serves as a reminder that as we build more powerful and agentic tools, the basics of secure software development remain as important as ever. Robust input validation, context-aware parsing, and a healthy skepticism of external data are the cornerstones of a secure system.

For users of Claude Code, the message is simple: ensure you are running version 2.1.118 or later. You can check your current version by running claude --version in your terminal. Staying updated is the most effective way to protect yourself from known vulnerabilities. Beyond just updating, it is also wise to be cautious when clicking on deeplinks from untrusted sources, even if they appear to target a tool you use daily.

As the ecosystem of AI-driven developer tools continues to grow, we can expect to see more researchers focusing on these types of integration points. The transition between the web and the local terminal is a high-value target for attackers. By understanding the mechanics of these vulnerabilities, both developers and users can better prepare themselves for the challenges of securing the next generation of software.

Securing the agentic future requires a collaborative effort between tool creators and the security community. The quick response from Anthropic and the detailed disclosure from the research community are positive signs that we are moving in the right direction. By learning from these incidents, we can build tools that are not only more capable but also more resilient.


Have you ever encountered a similar parsing issue in your own CLI tools? Let's discuss in the comments below!

Top comments (1)

Collapse
 
circuit profile image
Rahul S

The deeper pattern here isn't just about CLI argument parsing — it's about custom URI protocol handlers being an underaudited trust boundary across the entire dev tooling ecosystem. VS Code has vscode://, JetBrains has their own scheme, Docker Desktop registers one too. Every tool that registers a custom protocol handler is accepting attacker-controlled input from the browser through a channel that feels internal but isn't. The OS just hands it over with zero sanitization.

What's interesting about this specific bug is the timing gap between eager parsing and proper parsing. That pre-init window where configuration loads before the argument parser even runs is a pattern you see in a lot of Node.js CLIs — anyone who's written a --config flag that needs to be read before the rest of the app boots has probably written something structurally similar to eagerParseCliFlag. The fix is straightforward once you see it, but the pattern of "I need this one flag early, I'll just scan argv myself" is so common that I'd bet there are similar bugs sitting in other tools right now.