Agentjacking: A Fake Sentry Error Became a 'Fix' That Claude Code and Cursor Ran

2026-11-07来源 dev.toEnglish
Agentjacking: A Fake Sentry Error Became a 'Fix' That Claude Code and Cursor Ran

You open your coding agent and type the most ordinary request of the week: fix the unresolved Sentry issues. The agent calls Sentry, reads the top issue, and finds a tidy section headed Resolution with one command in a code block. It runs the command. The person who wrote that command has never had access to your code, your Sentry account or your laptop.

That is agentjacking, the attack Tenet Security published in June 2026 and The Hacker News reported on June 12. Tenet found 2,388 organizations with Sentry DSNs it could inject into. In a controlled test against more than 100 organizations, it reports an 85% success rate, with Claude Code, Cursor and Codex all acting on planted errors.

No step in the chain broke a rule. Tenet's phrase is that "every step is authorized."

The key that is meant to be public

Sentry is an error tracker. Your app reports crashes to it using a DSN, a URL with a key in it, and the browser SDK needs that DSN in your frontend JavaScript to work at all. Sentry's own DSN explainer says "DSNs are safe to keep public" because they "only allow submission of new events and related event data" and "do not allow read access to any information."

That's true, and it's the whole problem. Write-only access to your error feed was harmless while only humans read the feed. The same page admits "there is a risk of abusing a DSN," calls it "a rare occurrence," and points to IP blocking and key rotation.

Then the reader changed. The Sentry MCP server connects Claude Code, Cursor, VS Code and any other MCP client to your Sentry account for "searching errors, analyzing performance, triaging issues." Whatever anyone wrote into your error feed is now text your agent reads while it holds a shell.

The chain, hop by hop

Here is the attack as Tenet describes it, with the reason each hop was allowed.

  1. Find a DSN. Tenet lists website JavaScript, Censys searches for Sentry's ingest host, and GitHub code search. Allowed, because the DSN is designed to ship to every browser.
  2. POST one error event. The attacker sends a crafted event to Sentry's ingest endpoint. No authentication beyond the DSN, and Sentry answers HTTP 200. Allowed, because accepting events from strangers is what a frontend DSN is for.
  3. Dress it as Sentry. The payload sits in the event's message field and in the names of its context keys. It is markdown: headings, code blocks, and a fake ## Resolution section with an npx command, laid out to look like the template Sentry's MCP output already uses. Allowed, because an error message is user input and can say anything.
  4. Wait for triage. A developer asks the agent to fix unresolved Sentry issues. Tenet calls it "a plain triage request." Allowed, because the developer set up the MCP server and asked for exactly this.
  5. The MCP server hands it over. The server signs in to your Sentry account over OAuth and returns real Sentry data, which happens to contain the attacker's markdown. Per Tenet, the agent can't tell a planted event from a real crash. Allowed, because the tool call did what it was asked.
  6. The agent runs the "fix." It runs npx on an npm package the attacker controls, with the developer's privileges. In Tenet's tests the package was its own validation package. It checked environment variables, probed the file sizes of ~/.aws/config, ~/.npmrc and ~/.docker/config.json, looked for a VPN, and sent two POSTs to a beacon. Tenet says it did not keep the probe data.

A chain of six links running from a browser window to a developer's terminal, with one link shaped like a forged envelope bearing an official-looking seal
Six hops, each one permitted on its own. The forged one sits in the middle and looks like the rest.

Tenet says the attack "bypasses EDR, WAF, IAM, VPN, Cloudflare, and firewalls" (The Hacker News). There's no malware to scan for. There's an HTTPS request to Sentry, a tool call, and an npx command that looks like a thousand others.

The instinctive fix is a line in your agent's instructions: don't trust what you read in tool output. Tenet tested that. Agents ran the payload "even when explicitly instructed" to ignore untrusted data, and its conclusion is blunt: "You cannot fix this with a better prompt."

Tenet's write-up doesn't say which permission mode the agents ran in, or whether a developer clicked through an approval prompt. It does say sandboxed, CI-based and internal-network agents were also reached, so a sandbox helps only as far as what it denies.

Sentry's answer was one string

Tenet disclosed to Sentry on June 3, 2026, and Sentry replied the same day. It acknowledged the issue and declined to fix the root cause, calling it "technically not defensible" (Tenet). The Cloud Security Alliance's research note names the two root fixes Sentry turned down: authenticated ingestion, and sanitizing event content before the MCP server returns it. What Sentry did ship was a global content filter that blocks one specific payload string.

Be fair to Sentry on the first one. A browser can't keep a secret, so authenticated ingestion from frontend code isn't really on the table. Sanitizing is harder to wave away. A draft pull request in Sentry's MCP repository wraps the attacker-writable parts of get_issue_details (description, culprit, event details) in an <untrusted_event_telemetry> section. As of November 7 it is still an open draft. Even merged, the model still reads everything inside the tag, and nothing stops it following an instruction there.

Tenet picked Sentry, but any MCP tool that returns text an outsider can write works the same way: issue trackers, support inboxes, log search. The CSA note frames the injection pathway as architectural, and that matches the chain above.

Defenses that hold at the last hop

Hops 1 through 5 happen on systems you don't control. Hop 6 happens on your machine, so that's where the controls go: one rule, and two settings that enforce it without depending on the model behaving.

Treat MCP output as data, and enforce it at execution. A Sentry event can tell the agent where a bug is. It should never be the source of a command. Since instructions alone failed in Tenet's tests, that rule has to live in the approval step, where you read the command before it runs.

Keep approvals on for shell commands. In Claude Code's Manual mode, every Bash command asks first, except a built-in read-only set like ls, cat and grep (permissions docs). npx isn't on that list. Rules are checked in the order deny, ask, allow, and a matching ask rule prompts even when an allow rule also matches. So ask rules for package runners keep prompting even after someone saved a broad allow. And don't run triage in bypassPermissions: the docs say to use it only "in isolated environments like containers or VMs." The CSA note makes the same call: if the integration stays on, require human approval before the agent runs commands or installs packages from MCP-retrieved content.

Take the credentials away from the shell. Claude Code's sandbox is off by default, and even when on, its default read access covers "credential files such as ~/.ssh and ~/.aws/credentials." The sandbox.credentials setting denies files and environment variables to sandboxed commands. The network allowlist starts empty, so the first connection to a new host, like a beacon, stops and asks you in Manual or Accept edits mode instead of going out silently (strictAllowlist refuses it outright). It runs on macOS, Linux and WSL2. A project .claude/settings.json with the ask rules, the sandbox and the credential denies:

{ "permissions": { "ask": [ "Bash(npx *)", "Bash(npm exec *)", "Bash(pnpm dlx *)", "Bash(bunx *)" ] }, "sandbox": { "enabled": true, "allowUnsandboxedCommands": false, "credentials": { "files": [ { "path": "~/.aws", "mode": "deny" }, { "path": "~/.ssh", "mode": "deny" }, { "path": "~/.npmrc", "mode": "deny" }, { "path": "~/.docker/config.json", "mode": "deny" } ], "envVars": [ { "name": "AWS_SECRET_ACCESS_KEY", "mode": "deny" }, { "name": "GITHUB_TOKEN", "mode": "deny" }, { "name": "NPM_TOKEN", "mode": "deny" } ] } } } 

Turning the sandbox on changes the prompting. With autoAllowBashIfSandboxed at its default of true, Claude Code runs other sandboxed Bash commands without asking. Content-scoped ask rules like these npx ones still force a prompt.

Two limits. The docs say "there is no built-in credential deny list," so only what you list is protected. And a Bash rule "isn't a security boundary": Bash(curl *) doesn't stop sh -c 'curl ...'. The ask rules catch the usual form. For the rest, the sandbox limits what the command can read and where it can connect. Cursor and Codex have their own approval and sandbox settings, so ask the same of them: what prompts, what runs sandboxed, and what's denied.

On the Sentry side, the CSA note recommends disabling the MCP integration if you don't need it and rotating exposed DSNs (from the project's Client Keys settings, per Sentry's docs). It also suggests evaluating a server-side relay so the DSN stays out of client code. Scoping the MCP connection to one organization or project hides discovery tools you don't use (Sentry MCP).

Where Hermes fits, and where it doesn't

I build Hermes IDE, so weigh this section with that in mind. Hermes runs every agent in a real terminal, exactly as it would anywhere else. That cuts both ways. The permission rules and sandbox you set in Claude Code apply unchanged, and the agent has the same reach it has in your own shell. A worktree is a folder, not a sandbox: ~/.aws is the same file from every worktree. Hermes alone doesn't stop this attack. The settings above do the credential work.

What it adds is around the approval step. ⌘N (Ctrl+Shift+N on Windows and Linux) starts the triage task on its own hermes/ branch and worktree, and the sheet shows the approval mode (Accept edits in the screenshot below) and a "Hermes will run" preview of the claude command. So you can see you're starting Claude Code in Accept edits, which auto-accepts file edits and common filesystem commands like mkdir and mv, not npx.

The Hermes New task sheet with a refund-endpoint task typed, chips for Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and the Hermes will run preview of the claude command
The ⌘N New task sheet: Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and the "Hermes will run" preview of the claude command.

When the agent stops to ask about an npx command, the session shows "needs approval," the attention inbox counts it, and ⌘I (Ctrl+Shift+I) jumps to it. You answer in the agent's own terminal. Hermes never answers a permission prompt for you and never types into a terminal on its own.

If a planted "fix" did change files, every agent turn is saved. The Review Desk (⌘G, or Ctrl+Shift+G) shows changes by turn with risk flags, and you can diff or restore a single turn with undo.

The Hermes Review Desk in By turn view for a Claude Code task, with two turns listed, turn 1's diff of src/db.ts, the changed-files list and a drafted commit message
The Review Desk (⌘G) by turn: two Claude Code turns, turn 1's diff of src/db.ts, the changed files, and a commit message drafted from the turns.

Hermes is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux (download, GitHub). It isn't affiliated with Sentry, Anthropic, Cursor, OpenAI or Nous Research.

Your error feed is now an inbox

Count the MCP servers your agents use that return text a stranger can write. For each one, check what happens at the moment a command from that text reaches your shell: who reads it, and what it can reach once it runs.

How does your team run Sentry triage with an agent today: approvals on, sandboxed, or MCP off? Tell me in the comments.

If this was useful

Agentjacking works because the model can't tell an instruction it was given from text that only looks like one. If you write the prompts and instruction files your agents run on, the Prompt Engineering Pocket Guide is my short book on how models take instructions, which helps you see where a fake Resolution section gets its pull. And if you want one place to watch which agent is waiting on an approval, Hermes IDE is free.

Prompt Engineering Pocket Guide: Techniques for Getting the Most from LLMs

原文:https://www.a8q9.cn/en/article/agentjacking-a-fake-sentry-error-became-a-fix-that-claude-code-and-cursor-ran-2d39ec.html

相关文章