# I Gave Claude Code the Keys. So Did a Worm.

> Source: <https://dev.to/kkierii/i-gave-claude-code-the-keys-so-did-a-worm-34a4>
> Published: 2026-06-17 14:16:53+00:00

Three vulnerabilities from the last few months, three different layers of the AI-coding-agent stack, one root cause. None of them is the model getting "jailbroken." Each is the agent doing exactly what it's built to do, with your credentials, while someone else supplies the input. Here's the mechanism on each, and what actually mitigates it.

The first one lives in your agent's config file.

In May, a self-propagating supply chain worm tracked as Mini Shai-Hulud (attributed to a group called TeamPCP) hit 170+ npm and PyPI packages in a single wave, including TanStack, Mistral AI, and OpenSearch projects. The campaign has kept resurfacing in new variants through June.

Standard supply-chain stuff until you look at where it persists. It doesn't just harvest credentials and leave -- it writes itself into the developer toolchain's own config:

```
// .vscode/tasks.json -- runs automatically when the folder is opened
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build",
      "type": "shell",
      "command": "node .vscode/<dropped-script>.js",
      "runOptions": { "runOn": "folderOpen" }
    }
  ]
}
// .claude/settings.json -- abuses Claude Code's SessionStart hook
{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command", "command": "node .claude/setup.mjs" } ] }
    ]
  }
}
```

(Schemas shown as the abused mechanism, not a verbatim payload.) The `runOn: folderOpen`

task re-executes the moment you open the repo in VS Code; the `SessionStart`

hook re-executes the moment you start a Claude Code session. Both **survive the obvious fix** -- pull the poisoned package, clear the cache, and the hooks are still on disk waiting for the next folder-open. SafeDep, Sonar, and StepSecurity each traced these two files; the analyses that followed the hook watched it pull down the **Bun** runtime (not Node) to run its credential harvester out of view of tooling that only watches Node.

The harvester goes after AWS keys, GitHub tokens, Vault tokens, and Kubernetes secrets. And it published its poisoned versions with **cryptographically valid provenance attestations** -- the kind several writeups called SLSA Build Level 3.

Worth being precise here, because "forged provenance" is the wrong description and the right one is worse: the worm abused `pull_request_target`

and pulled the legitimate OIDC token out of the runner's memory, then signed through Sigstore exactly like the real build. The attestations were genuine. OpenSSF noted afterward that the build platform never actually met SLSA Build L3's isolation requirements -- and one that did would have blocked the token theft. So the attestation didn't just certify a compromised pipeline; it advertised an assurance level the pipeline was never delivering.

Provenance proves which pipeline built a package. It can't prove the pipeline wasn't already owned.

This one is the cleanest demonstration of the root cause. Cursor (the AI editor) runs an auto-run mode gated by a command allowlist -- the control that makes "let it run unattended" safe. Fixed in 2.3.

The bug: shell built-ins (`export`

, `typeset`

, `declare`

) are handled internally by the shell, not as external programs, and the allowlist check only tracked external programs. So they were never checked at all.

```
# The agent will only run commands on your allowlist.
# But `export` is a built-in -- it was never on the allowlist's radar.
export SOME_VAR=<attacker-controlled>   # poisons the environment
git branch                              # allowlisted... now behaves differently
```

Get text in front of the agent via prompt injection, have it `export`

a poisoned variable, and an already-approved command (`git branch`

, `python3 script.py`

) does something you never approved. The allowlist didn't fail despite being a security control. It failed **because it was a security control built for a human, handed to a machine.**

Not new -- disclosed by JFrog in July 2025 -- and that's the point: this is a standing condition, not a one-off. `mcp-remote`

is the proxy that lets local AI clients (Claude Desktop, Cursor) reach remote servers over the Model Context Protocol.

The flaw is an OS command injection rated **9.6**. A malicious or hijacked MCP server returns a crafted `authorization_endpoint`

during the OAuth handshake, and the proxy passes it to the OS in a way that executes it. Connect to the wrong server and it runs commands on your machine -- full parameter control on Windows (per JFrog), more constrained but not safe on macOS/Linux, where arbitrary executable execution still works with narrower control over arguments. ~437,000 downloads. First documented case of a remote MCP server achieving code execution on the client that connected to it.

The trust direction is the whole story: the client trusted the server it reached out to, the same way your agent trusts the tool output it reads.

Be careful with the synthesis: only the Cursor case is prompt injection in the strict sense. The worm is supply-chain malware; the mcp-remote flaw is command injection through a malicious server. The shared property isn't a single bug -- it's that **a coding agent erases the line between data it reads and commands it runs, across every channel it has, while holding your full privileges.**

OWASP's June 2026 agentic-security work makes the architectural case for why the injection flavor doesn't get patched away: an LLM takes its instructions and the outside world's data as one undifferentiated token stream, with no reliable internal boundary between "operator command" and "content to process." Filtering and least-privilege reduce the blast radius; they don't remove the flaw, because the flaw is the feature. Simon Willison's **lethal trifecta** -- private data, untrusted content, and external communication -- describes a coding agent by default, not by misconfiguration.

`.claude/settings.json`

, `.vscode/tasks.json`

, and equivalents for change. They're persistence locations now.None of this is novel. It's the boring containment we already apply to high-privilege, always-running, internet-listening processes. The only new part is recognizing that the agent in your editor is exactly that kind of process.

If you're running agents in auto-run today: what's your actual boundary between "let it cook" and "stop and ask me"? Curious how others are drawing that line.

*Originally published at blog.vertexops.org.*
