There is a file your coding agent reads on startup with a line in it that runs a program — and an attacker can get the agent to write that line for you, from text they sent. That is the part with no clean precedent, so start there. The file is mcp.json. An MCP server, in the stdio case, is nothing more than a local subprocess: the config gives a command and an args array, and the client launches it verbatim and talks JSON-RPC over stdin and stdout. So the config is a launch spec, and it runs under your account, with your account’s reach — the same files, environment, sockets, and warm command-line tools you can touch, modulo the keychain and hardware-key prompts that are weaker on a developer’s box than anyone wants to admit. In the vulnerable clients it launches during startup, before the model reasons and before the approval prompt you think of as the safety layer has anything to gate. Its per-project sibling ships in the repo you just cloned. No earlier autorun vector — not tasks.json, not postinstall, not a git hook — combined all three at once: authored from untrusted input, executed before the model, carrying the authority of whoever opened the file.

This is part three, and it stays on the device. Part one was the server side; part two was the on-device coding agent and the ambient authority it spends without ever asking the OS to elevate anything. Part two ended on a joke that was not really a joke — that this generation of malware says please, because the one thing left between an agent and your whole account is a policy call made by the same engine reading the attacker’s text. This post is the case where it does not even say that. The MCP server in a config file executes at startup, before the planner runs this session, before the auto-mode classifier screens an action, before the tool-use confirmation exists. Every control the last two posts called a sensor is downstream of a decision this file already made.

The objection I have to kill first

The sharp reader already has the rebuttal loaded: this is just a repo file that autoruns, and we have known that since Makefile. Correct, and I want to concede it harder than the objection expects, because the concession is where the argument gets its teeth. A config file committed to a repository that executes code when a teammate opens it is twenty years of prior art with a body count. VS Code tasks.json with runOn: folderOpen runs a shell command on folder open — not hypothetical, it was weaponized into a multi-stage infostealer campaign as recently as this January.1 npm and yarn preinstall/postinstall scripts run on npm install; that is event-stream in 2018 and the Shai-Hulud worm in 2025. This class is old and well documented.

A second version of the objection is sharper and worth answering: editors already run third-party code — extensions, language servers — so MCP is just plugin execution with worse packaging. Half right, and the other half is the point. Extension marketplaces are their own supply-chain swamp — thin review, hijacked publishers, typosquats — so this is not a claim that extensions are safe. It is that even that swamp has an install boundary and a publisher object: something you deliberately added, attributable to a name, revocable by a marketplace. A repo-scoped or user-writable MCP definition collapses install and execution into unreviewed configuration — no publisher, no signature, no install step, just a command string — and, as we are about to see, the agent’s own file-editing can author it. Plugin execution with even those weak boundaries removed is not a smaller problem than plugins. It is the problem plugins spent a decade building controls against.

Because the ecosystem did build them, more than once, and they worked. VS Code shipped Workspace Trust in 2021 precisely because tasks.json was a foot-gun — untrusted folders open in Restricted Mode with tasks, debug, and terminal disabled until you grant trust. Git, which has had executable hooks in every checkout since forever, made the deliberate decision that git clone does not transmit .git/hooks — the hooks stay local, and cloning a hostile repo does not run them. And direnv, the closest structural analogue to an MCP config, solved the hardest version cleanly: its .envrc is a shell script that runs when you cd into the directory, so it refuses to run until you type direnv allow, and it stores that approval in an allowlist keyed to a hash of the file’s contents. Edit one byte and the approval is void; you re-approve or it does not run.

So the argument is not “look, another config file that runs code.” That earns the one-line dismissal it deserves. The argument is that the AI editors took a solved problem, regressed on the solutions, and then added a failure mode none of the old vectors had: a config the agent can write from untrusted input, that executes before any model-level safety system participates. Take those in order.

We re-shipped the bug direnv fixed a decade ago

The regression has a CVE. In Cursor, MCP server approval was keyed on the server’s name, not its contents — you approve a benign entry once, and the approval sticks to the identifier, not the command behind it. So an attacker commits a harmless-looking server, gets it approved on first run, and later changes the command — to anything; it does not need an obvious payload, that is what makes it worse. No second prompt. It re-executes silently on every launch. That is CVE-2025-54136, “MCPoison,”2 and it is a perfect specimen because it is exactly the bug direnv designed against ten years earlier: trust-on-first-use with no re-validation on change. direnv binds approval to a content hash; Cursor bound it to a string. Cursor fixed it in 1.3 — now any change, even whitespace, re-prompts — which is to say they eventually re-derived direnv allow.

Give credit where the ecosystem earned it, because the honest version of this argument requires it. The clients have been hardening. Cursor 1.3 closed the name-keyed gap. Claude Code added a workspace-trust layer in v2.1.196 so a cloned repo can no longer approve its own servers from checked-in settings — you have to run the agent in the folder and accept a trust dialog first. VS Code’s model is the strongest of the lot: untrusted folder, no server, and a distinct trust prompt on first start. These are real controls, default-deny, and I am not going to pretend they are theater.

But look at where they land and the pattern is unmistakable — not “controls are missing,” but controls are inconsistent across clients, bind to the wrong object, or cover project config while leaving user-scope and agent-authored config open. Claude Code’s approval ledger records the server name it approved — Repello, testing v2.1.170, found it stores “not the full server definition, the command, the arguments, or any hash”3 — the same content-blindness MCPoison exploited, one directory over. The folder-trust dialog that four major agent CLIs rely on — Claude Code, Cursor, Gemini CLI, the Copilot CLI, all demonstrated to auto-execute project-defined servers once trust is granted4 — was softened in recent Claude Code builds to a generic “is this a project you trust?” with Yes as the default, no longer naming MCP or enumerating what is about to run. This is the trilogy stated in miniature: a gate that matches a name is a recognizer; a gate that content-hashes the thing that will execute is a control. We keep building the first and calling it the second.

The config writes itself now

Here is the part that is not in tasks.json, not in .envrc, not in postinstall, and is the actual reason this deserves a post. In every classic vector, a human crosses the execution boundary — clones the repo, runs npm install, opens the folder and clicks Trust. With mcp.json, that step can be removed, because the agent will write the file itself.

That is CVE-2025-54135, “CurXecute.”5 The precondition is mundane and default: the agent has a file-editing tool, which is the whole point of a coding agent. An MCP server wired into Cursor — say a Slack connector — reads a message an attacker posted. The message is a prompt injection. It instructs the agent to write a new entry into ~/.cursor/mcp.json, the agent does what agentic file-editing agents do, and Cursor launched the freshly written server the moment the edit hit disk. This is the detail to slow down on: the exploit did not trick a user into clicking approve on a malicious diff. It executed before the approval could apply at all — the write and the launch were a single, un-gated step. The novelty is not “prompt injection made the agent edit a file” — it is which file: an autorun location consumed before any future guardrail engages. Untrusted content arrives through one tool, the confused deputy holding the pen writes the launch spec, the launch spec executes.

Be precise about what this removes. It is not “the victim did nothing” — they configured a Slack connector and let the agent read messages; that is a real setup step. The honest, and still damning, claim is that after that ordinary integration, untrusted content can cross the boundary into persistent local execution with no new clone, no new install, and no new trust decision. The persistence file becomes something an adversary can plant through any channel the agent reads and is allowed to act on — an issue, a README, a dependency’s docs, a tool result.

And the timing is the whole game. The MCP server launches during client init, before the model plans, before it reads your instructions, before the auto-mode classifier screens a single action. I have argued that the auto-mode classifier is a sensor, not a control; here it is not even a sensor, because it never runs against this. Anthropic’s classifier explicitly screens for hostile content the model encountered in a file or web page — but prompt filters operate on the model’s input plane, and the code in command executes on a different plane, as a plain OS process. Even in CurXecute, where the model did read the attacker’s text to perform the write, the compromise that follows — the spawned subprocess — runs where no later classifier pass or model inspection reaches. The recognizer is looking at the wrong stream, on the wrong side of the exec. None of this says classification is impossible — a client could diff config writes, scan command strings, require signed server packages, mediate subprocess launch. The point is narrower and harder to escape: the model-context injection classifier, the thing the industry is actually shipping as the safety layer, sits downstream of a subprocess that has already started.

It runs in the process everyone has learned to ignore

Put that execution somewhere specific. The child is spawned by the editor — Code.exe, Cursor, the claude CLI running under node — and on a developer’s machine that parent is a soft place to hide. This is not an EDR blindspot, and I will not claim one, because on the market-leading stacks it is false: Microsoft states plainly that its endpoint detection “doesn’t adhere to the Microsoft Defender Antivirus Exclusions,”6 and CrowdStrike’s detection exclusions still log the telemetry to the cloud.7 The child doesn’t get a free pass from EDR — behavioral and process-lineage telemetry keep firing.

It is camouflage, which is worse in the ways that matter. The parent is code-signed, so application-control and firewall layers wave it through. Developers routinely carve their repo trees, node_modules, and package caches out of antivirus scanning to stop the performance tax — Microsoft built the Dev Drive “performance mode” feature specifically because the practice is so common. And a node process spawning children is the single most normal, lowest-signal event on a developer’s box; the malicious one is one line in a JSON file away from every legitimate one. Real operators pick this surface for exactly that reason — Operation Digital Eye deployed a portable signed code.exe for command-and-control over Azure because, in SentinelOne’s words, such executables and infrastructure “are often not closely monitored and are typically allowed by application controls and firewall rules.”8 That is adjacent evidence — dev-tool trust as camouflage — not proof that an MCP child evades a tuned sensor. The honest gap is narrower than invisibility and still plenty wide: privileged normality and a spent analyst attention budget.

Persistence on one machine, spread across a team

Split the file by scope and the same primitive becomes two weapons.

The user-scope config is persistence. Write a malicious server into it once and it re-launches on every editor start — for a working developer, several times a day, deterministically, no timing race. It is stealthier than a cron job not because it is more powerful — it is narrower, firing only when you use the tool — but because it lives where defenders do not yet look, spawned by a trusted parent, and it rides your dotfile-sync repo to every machine you own and back after every cleanup. There is not yet a common industry detection surface for a malicious user-scope MCP config; that is a gap, not a law, and the fix is boring file-integrity monitoring on known paths plus a process-lineage rule on the trusted parent spawning something shell-shaped.

The project-scope config — .mcp.json, .vscode/mcp.json, .cursor/mcp.json, committed to the repo — is spread, and the research community has demonstrated it end to end under the name TrustFall: commit a server, a teammate pulls, opens, accepts the folder-trust prompt out of habit, and the payload runs at startup before the agent does a thing. The one gate is that dialog, and I have already said what has happened to it.

The sharpest edge in this whole post is where that gate has no human behind it at all. A CI runner has no TTY to render a trust prompt — so a control that is an interactive prompt is not weakened there, it is absent by construction. Run an agent CLI non-interactively on a build agent that checks out a branch and whatever the client would have asked, no one is there to answer; a design whose only gate is that prompt is simply incompatible with non-interactive execution, which is a primary use case, not an edge case. And CI is a credential-rich place to lose that bet: build agents commonly evaluate untrusted changes while holding deploy keys, registry tokens, and cloud credentials — the privileged, misconfigured ones most of all. I am not going to name a client and a flag, because that is exactly the version-specific detail that rots; the structural claim is the durable one. If you invoke coding agents in CI against code you did not write, audit them as if repo-defined startup behavior executes, unless the vendor documents that it fails closed.

Discipline on the worm, because the temptation is to reach for it and it does not hold: writing .mcp.json into every local repo is not propagation. The loop still has to git commit, git push, and survive review — an autonomous push to a protected main is a screaming anomaly, and a new .mcp.json in a pull request is a reviewable text diff, which is exactly what postinstall never had to face. There is no observed .mcp.json worm in the wild, and I will not invent one.

You don’t need a worm, you need a template

The self-propagation that does worry me is boring, and it is not a worm at all: poisoned templates. A cookiecutter, a starter repo, an npx create-something scaffold that emits a .cursor/mcp.json pointing at node scripts/dev-helper.js, with the payload folded into the scaffold’s own files — every project generated from it inherits the server, and every developer who runs the scaffolder inherits it again. The reason this works is the reason it is nasty: nobody diffs the output of a generator the way they diff a pull request. The entire proposition of create-something is that you trust it to write files you will not read — that is the labor it saves — so the mcp.json it drops lands inside a directory of machine-authored boilerplate that no reviewer has ever been expected to audit line by line. The config does not arrive as a suspicious diff. It arrives as a framework default, which is how you spread at scale without a worm at all — you let people install it on purpose.

The blast radius is the supply chain

None of this is a local problem, because a developer’s laptop is a credential concentrator. Some of what an attacker wants is a flat file readable by any process running as you — ~/.aws/credentials, ~/.npmrc with a publish token, a kubeconfig, a gh token. Some is gated behind a keychain, an SSH agent, or a hardware key and demands more than a cat. But the file-readable set alone is enough, and the precedents are not speculative. Three, each doing a distinct job: the torchtriton dependency-confusion payload in PyTorch’s own post-mortem read ~/.ssh and up to a thousand home-directory files off every machine that installed the nightly9 — home-directory theft from unprivileged exec is the default outcome, not a stretch. The Codecov bash-uploader compromise scraped CI environment variables from more than 23,000 organizations from a single modified script10 — the same exec, moved to CI, is org-scale credential exfiltration. And Shai-Hulud stole an npm publish token off a developer machine and used it to re-publish itself11 — the stolen token becomes the propagation. These are blast-radius precedents, not same-shape MCP attacks; the shape that carries over is “unprivileged code exec on a dev or CI endpoint fans out into the supply chain.” Land inside an agentic editor and the fan-out is more direct still, because the process often already sits next to live sessions the agent was wired into — tokens in its environment, a shared auth cache, config the connectors read — so local exec is natively cross-service, and it happened before the agent’s own guardrails ever got a vote.

Breaking the chain

These are controls, not sensors — and notice that the same short list stops every link, and a model-mediated action prompt is on none of it.

Content-addressed re-approval is the fix for MCPoison, and it is not new — it is what direnv has done from the start. Approve the resolved server definition — command, args, env, cwd, transport — hash it, re-block on any change. A client that matches a name is not running a weaker control; it is authorizing the wrong object. And because a command is usually a loader — npx some-pkg, uvx tool, docker run img:latest, bash scripts/x.sh — that resolves mutable code the JSON never pins, the hash is necessary but not sufficient: it has to reach the package, the image digest, or the script, not just the config line, or a stable entry points at code that moved. Managed-scope policy is the fix for repo-driven execution: a Claude Code managed-mcp.json with an empty server map, or VS Code’s ChatMCP: none machine policy, lets an administrator force no project MCP to load regardless of what a cloned repo contains, and managed scope outranks project scope by design. It is a real control whose only failure is shipping off by default. Treat .mcp.json, .vscode/mcp.json, and the .claude/ directory as CODEOWNERS-gated, branch-protected, human-reviewed paths, and the committed-config spread vector collapses into “a reviewer read the thing that runs code” — the property postinstall denied us and this file can give back. Scope the identity so a planted server inherits a per-task short-lived token, not a long-lived ~/.aws/credentials. Sandbox the execution — container, non-user UID, no host home mounted, egress allowlisted — so the child from a config file lands somewhere with nothing worth stealing and nowhere to send it. And the one that closes the new failure mode specifically: an agent must not be able to modify the config that governs its own future execution without an out-of-band approval. The pen and the launch spec cannot be on the same side of the trust boundary.

Every item on that list is structural. Content-hash approval and CODEOWNERS review are approvals — the point is not that no human ever confirms anything; it is that none of these is a just-in-time, model-mediated action prompt, the artifact the industry keeps selling as the safety layer. That layer is a recognizer, and here the most damning kind, because the MCP server runs before it — so even a perfect classifier watches an empty room. The controls that hold are the ones the trilogy has pointed at all along: bind trust to what executes, scope the identity, sandbox the blast radius, and put the human on the irreversible act. Here the irreversible act is not the agent doing something dangerous later. It is the write to the config, and the launch that follows it.

The malware stopped saying please

In 2004 the malware simply took your authority, because nothing stood in the way. Part two’s generation installs the agent, hands it the token, and leaves one policy call between it and your whole account — so the malware says please, routing its request through the same planner reading the attacker’s text. This file is the next step down. The MCP server does not ask the planner, because it runs before the planner exists this session. It does not ask you, because on a CI runner there is no prompt and on your laptop the prompt matched a name it already trusted. It does not ask the classifier, because it executes as an OS process below the model’s context. And the worst of it is that we had the answer written down — direnv binding approval to content, git declining to ship its hooks, the Workspace Trust prompt we built and then taught users to click through. We remembered it in the patches and forgot it in the defaults, we put pre-model code execution behind trust UX we already knew was inadequate, and we left the config writable by the very thing it controls. Then we sold the model’s downstream guardrails as the security story. The line in the file ran before any of them.

The treatment — an operator’s appendix

The essay is over; this is the field manual, for anyone who runs these tools and wants the argument turned into settings — the two hosts most people run, plus a map of every file to watch. None of it is free: locking MCP down trades away exactly the frictionless local integration these tools are sold on, which is what a trust boundary feels like — the alternative is not having one. Names and paths drift release to release, so verify against your installed version before you trust any of them, but the shape holds.

Claude Code. The real off switch is a managed-mcp.json holding an empty server map — {"mcpServers": {}} — dropped at the system path (/Library/Application Support/ClaudeCode/ on macOS, /etc/claude-code/ on Linux, C:\Program Files\ClaudeCode\ on Windows). It takes exclusive control, and claude mcp add starts refusing.12 Mind the footgun the docs make easy to miss: an empty enabledMcpjsonServers: [] in settings does not disable anything — it auto-approves nothing, which leaves every server merely pending and still addable by hand. To run a fixed catalog instead of nothing, list the approved servers in that same managed file, or set a managed allowedMcpServers allowlist with allowManagedMcpServersOnly: true so a user can’t widen it — and match on serverCommand or serverUrl, never serverName, which Anthropic states outright is not a security control, since any user can name their server github. Know the residual gap while you are in there: the v2.1.196 workspace-trust dialog stops a fresh clone from self-approving, but it does not re-prompt when you pull a poisoned change into a repo you already trusted, and the approval ledger still keys on the server’s name, not its command — MCPoison, one directory over.

VS Code. The org-wide kill switch is a machine policy, not a user setting: ChatMCP = none disables MCP entirely; ChatMCP = registry paired with an internal McpGalleryServiceUrl restricts it to a curated registry and blocks the public one.13 Add chat.mcp.discovery.enabled: false so a rogue Claude Desktop config can’t be discovered and seeded sideways into VS Code, set chat.mcp.autostart to never, and disable bypass mode (ChatToolsAutoApprove) — because a committed .vscode/settings.json that flips chat.tools.autoApprove to true is a documented remote-code-execution path, CVE-2025-53773, a prompt injection that writes the setting and drops the agent into approve-everything.14 The catch worth stating plainly: Workspace Trust itself has no documented policy that forces it on — a user can switch it off — so those machine Chat* policies are the only levers that actually enforce.

Cursor. The client both anchoring CVEs in this post came from is also the one whose enforcement sits furthest from the file, and it is worth saying why rather than leaving it a hole in the table. Cursor’s actual lever is a team-admin Run Mode policy set in the dashboard: configure it and neither a user’s permissions.json nor the in-app allowlist can widen it — that is real, admin-controlled precedence.15 But the local story is weaker than Claude Code’s or VS Code’s in two specific ways, and both matter here. First, the permissions.json that carries the MCP allowlist lives at ~/.cursor/permissions.json and <workspace>/.cursor/permissions.json — both user-writable, both inside the very .cursor/ tree the agent edits — so there is no root-owned managed file that forces load nothing the way managed-mcp.json does; the on-disk control sits on the same side of the trust boundary as the thing it is supposed to restrain. Second, that allowlist matches on server:tool — the server name — which is precisely the object the rest of this post keeps calling not a security control, since the attacker picks the name. An allowlist keyed on an attacker-chosen string is the MCPoison shape transcribed into the hardening guide. So the honest instruction for Cursor is: push the policy to the dashboard or an MDM profile, treat the local permissions.json as advisory rather than tamper-proof, and do not mistake a name-keyed allowlist for a content-aware one.

The repository. For the committed-config spread vector, make the config a reviewed path. A CODEOWNERS entry over every place a server can hide, backed by branch protection that requires code-owner review, turns a config change into a diff a human has to read:

/.mcp.json          @org/security
/.vscode/mcp.json   @org/security
/.cursor/           @org/security
/.claude/           @org/security
/.github/CODEOWNERS @org/security

The single branch-protection option that matters most here is dismiss stale approvals when new commits are pushed16 — it voids the approval of the benign version the instant the command is swapped, which is exactly the move MCPoison relies on slipping through unre-reviewed. A required CI check that fails any pull request touching those paths guarantees the change is seen even where a code-owner rule doesn’t reach.

The filesystem, honestly. Do not reach for chmod 0444 or chflags uchg on the config — the agent runs under your own UID, so the same UID simply undoes them; it is theatre. Only a root-set immutable flag (chattr +i on Linux, chflags schg on macOS) with the agent running as a non-root user actually holds, and even that folds if a passwordless sudo is sitting there for an injected instruction to spend. The durable control is the structural one the last section named: run the agent under a separate identity, or in a container with the MCP config mounted read-only, no host secrets mounted, egress allowlisted, and per-task short-lived credentials instead of long-lived files — the shape of Anthropic’s own reference devcontainer.17 Then the pen and the launch spec sit on opposite sides of a boundary the agent cannot cross, which is the only version of this that does not depend on someone reading a prompt.

Half the battle, though, is just knowing what to watch, because the same file lives in more places than anyone tracks, across three tiers: the user-scope config that re-runs on every launch (persistence), the project-scope config committed into a repo that runs when someone opens it (spread), and the managed config an admin uses to enforce policy — which is also the file an attacker with root would target to weaken it. File-integrity monitoring on these paths is cheap and high-signal because they change rarely, and the highest-fidelity alert is the one that fires when the agent process itself is the writer, the CurXecute tell. The map:

HostTierConfig file · pathRoot key
Claude CodeUser~/.claude.json, ~/.claude/settings.jsonmcpServers
Claude CodeProject.mcp.json, .claude/settings.jsonmcpServers
Claude CodeManagedmanaged-mcp.json / managed-settings.json in …/ClaudeCode/ (macOS), /etc/claude-code/ (Linux), C:\Program Files\ClaudeCode\ (Win)mcpServers
Claude DesktopUser~/Library/Application Support/Claude/claude_desktop_config.json (macOS), %APPDATA%\Claude\ (Win)mcpServers
VS CodeUser…/Code/User/mcp.json (+ profiles/*/): ~/Library/Application Support/ (macOS), %APPDATA%\Code\ (Win), ~/.config/Code/ (Linux)servers
VS CodeProject.vscode/mcp.jsonservers
CursorUser~/.cursor/mcp.jsonmcpServers
CursorProject.cursor/mcp.jsonmcpServers
WindsurfUser~/.codeium/windsurf/mcp_config.jsonmcpServers
ZedUser~/.config/zed/settings.json †context_servers
ZedProject.zed/settings.json †context_servers
Gemini CLIUser~/.gemini/settings.jsonmcpServers
Gemini CLIProject.gemini/settings.jsonmcpServers
Gemini CLISystem/etc/gemini-cli/ (Linux), /Library/Application Support/GeminiCli/ (macOS), C:\ProgramData\gemini-cli\ (Win)mcpServers
Copilot CLIUser~/.copilot/mcp-config.jsonmcpServers

Two gotchas the table encodes. VS Code’s root key is servers and Zed’s is context_servers, so a monitoring rule that greps only for mcpServers silently misses both. And paths marked † are community-reported rather than vendor-documented — verify them before you build enforcement on top. GitHub’s Copilot CLI is deliberately absent from the project tier: it ships no repo-scoped config today, only the user-scope file, so there is one fewer committed-spread surface there than the pattern would suggest — for now.


  1. “Malicious VS Code tasks.json abuse enables multi-stage infostealer deployment,” ThreatLocker, Jan 2026 — a committed tasks.json with runOn: folderOpen executing on folder open. Reported by devclass. ↩︎

  2. CVE-2025-54136 (“MCPoison”), Check Point Research: Cursor keyed MCP approval to the server name rather than its contents, allowing silent re-execution after a post-approval change. Fixed in Cursor 1.3. research.checkpoint.com ↩︎

  3. “A trusted name is not a trusted command: MCP approvals in Claude Code,” Repello AI — the approval ledger keys on server name, not command/args/hash (tested against v2.1.170). repello.ai ↩︎

  4. “TrustFall,” Adversa AI — Claude Code, Cursor CLI, Gemini CLI, and the GitHub Copilot CLI all auto-execute project-defined MCP servers once folder trust is accepted; “the payload executes on server startup, before any tool call.” adversa.ai ↩︎

  5. CVE-2025-54135 (“CurXecute”), Aim Security / Cato Networks: indirect prompt injection from external content (e.g. a Slack message read via an MCP server) causes the agent to write ~/.cursor/mcp.json, which Cursor executed before user approval. Patched in Cursor 1.3. catonetworks.com · NVD ↩︎

  6. “Microsoft Defender Antivirus exclusions,” Microsoft Learn: EDR “doesn’t adhere to the Microsoft Defender Antivirus Exclusions settings” — scan-excluded files can still trigger EDR detections. learn.microsoft.com ↩︎

  7. CrowdStrike detection/prevention exclusions suppress the block but still log activity to the cloud; only the discouraged sensor-visibility exclusion reduces collected telemetry. See Red Canary, “How to create exclusions in CrowdStrike.” ↩︎

  8. “Operation Digital Eye,” SentinelOne Labs — a Chinese APT abusing portable, Microsoft-signed code.exe and VS Code Remote Tunnels for C2. sentinelone.com ↩︎

  9. “Compromised PyTorch-nightly dependency chain,” PyTorch — the torchtriton payload read /etc/passwd, ~/.ssh, .gitconfig, and up to 1,000 home-directory files, exfiltrating from every machine that installed the nightly. pytorch.org ↩︎

  10. The Codecov Bash Uploader compromise scraped CI environment variables (cloud keys, deploy keys, tokens) from 23,000+ organizations. Rapid7 ↩︎

  11. “Shai-Hulud,” the first self-replicating npm worm (2025): steals credentials and npm publish tokens from developer machines and re-publishes itself to writable packages. Unit 42 · Wiz ↩︎

  12. Claude Code managed MCP and settings: a system managed-mcp.json with {"mcpServers":{}} takes exclusive control and disables all other servers; allowedMcpServers + allowManagedMcpServersOnly enforce an allowlist; serverName is explicitly “not a security control.” managed-mcp · settings · admin-setup ↩︎

  13. VS Code enterprise MCP controls: the ChatMCP machine policy (none / registry / all) maps to chat.mcp.access; McpGalleryServiceUrl pins a private registry; chat.mcp.discovery.enabled imports other tools’ MCP configs. enterprise/ai-settings · mcp-servers ↩︎

  14. CVE-2025-53773 — prompt-injected write of chat.tools.autoApprove: true into a workspace .vscode/settings.json drops GitHub Copilot / VS Code agent mode into approve-everything, yielding RCE. NVD · Embrace The Red ↩︎

  15. Cursor permissions.json: read from ~/.cursor/permissions.json (per-user) and <workspace>/.cursor/permissions.json (per-repo), both user-writable; allowlists evaluate in the order team admin (dashboard) > permissions.json > IDE settings, so an admin-set Run Mode policy cannot be widened locally. The mcpAllowlist field takes server:tool entries and, when set, replaces the in-app MCP allowlist. cursor.com/docs · enterprise controls ↩︎

  16. GitHub CODEOWNERS scopes required review to paths; the branch-protection option “dismiss stale pull request approvals when new commits are pushed” voids a prior approval on any new commit. About code owners · Branch protection ↩︎

  17. Anthropic’s reference devcontainer runs Claude Code as a non-root user behind a default-deny egress firewall, and warns against mounting host secrets — “prefer repository-scoped or short-lived tokens.” devcontainer ↩︎