Skip to content

All posts

AI

AI coding agent security risks (and the controls for each)

Ritesh Patel

AI: AI coding agent security risks (and the controls for each)

AI coding agents carry four kinds of risk: instructions hidden in content they read, commands that destroy or send data out, secrets they can reach on disk, and tools nobody reviewed. Each needs its own control. Permission prompts, deny rules, sandboxing, hooks and managed settings each stop some of these risks, and none of them stops all of them.

The risk and control table

The table pairs each risk with the controls that address it and the limit the vendor docs state. Claude Code is the worked example because its docs describe each mechanism and its limits in detail. Where a row is general practice rather than a product feature, it says so. The checks were made against the Claude Code security, permissions, sandboxing, hooks and settings docs as of 2026-10-30.

Risk Controls Where the control stops
Prompt injection: a file, web page or tool result tells the agent to act against you Manual mode asks before edits and commands. Review each command. Keep untrusted content away from the agent’s input. Run untrusted work in a virtual machine A prompt protects you only if someone reads it. The docs say no system is immune to all attacks
Destructive commands such as rm -rf or git reset --hard Bash deny rules, plus a PreToolUse hook for the forms the rules miss Rules match the command text. /bin/rm and sh -c 'rm ...' are not matched
Data sent out through curl or wget Deny rules for the tools. Sandbox network allowlist The allowlist starts empty. Non-TCP traffic and tools that ignore proxy variables fail inside the sandbox
Secrets on disk: .env, ~/.ssh, ~/.aws/credentials Read deny rules for file tools. Sandbox denyRead for shell commands Sandbox reads are broad by default and still allow credential files. A denyRead entry does not stop the Read tool
Writes outside the project, including config files Sandbox writes limited to the working directory. Protected paths such as .claude/settings* and .mcp.json stay write-denied In Manual mode an approved Bash command can write anywhere your account can. Native Windows runs without the sandbox
Unreviewed MCP servers and plugins Managed MCP configuration to restrict which servers run. Review plugin-supplied servers .mcp.json shows project servers only. Anthropic does not security-audit MCP servers
Someone edits the controls themselves Managed settings, the top precedence level. ConfigChange hooks can audit or block changes Managed settings apply only on machines your organization deploys them to
Sandbox bypassed by a retry Set allowUnsandboxedCommands to false Commands in excludedCommands run with full access, so list only tools you trust that far
Shared API key, so no owner for spend or actions One credential per person. Monitor usage with OpenTelemetry metrics Keys are only as safe as their storage: Keychain on macOS, a 0600 file on Linux

Why approval prompts are not enough

In Manual mode, Claude Code starts read-only and asks before it edits files or runs commands. That protects you from a surprise, but only if the prompt is read. The docs make a narrower point too: the working directory boundary is a permission prompt, so “a Bash command you approve can still write anywhere your user account can.” The sandbox closes that gap at the operating system level.

Auto mode replaces you with a classifier that reviews each action, and your deny rules still apply. Claude Code also asks before a Bash command it cannot fully analyze. An allow rule for part of a command, such as Bash(git *), does not skip that prompt. None of these layers is a substitute for the others. Use them together, and treat any prompt you approve as a decision you own.

Layer the controls in this order

  1. Turn on the sandbox for shell commands with /sandbox, or set sandbox.enabled. Platform support is macOS (Seatbelt), and Linux and WSL2 (bubblewrap plus socat). Native Windows runs commands unsandboxed, so run Claude Code in WSL2 there.
  2. Scope the filesystem. The docs’ example blocks reads of the home directory and re-opens the project.
  3. Add deny rules for commands you never want, and for files the agent must not read.
  4. Add a PreToolUse hook for the call forms that rules cannot match.
  5. Deploy the set as managed settings, then check what loaded.
{
  "sandbox": {
    "enabled": true,
    "allowUnsandboxedCommands": false,
    "filesystem": {
      "denyRead": ["~/"],
      "allowRead": ["."]
    }
  }
}
{
  "permissions": {
    "deny": [
      "Bash(rm *)",
      "Bash(git reset --hard *)",
      "Read(./.env)",
      "Read(./.env.*)"
    ]
  }
}

Settings files are strict JSON, so a comment or a trailing comma stops the file from loading. For a working hook script, see the destructive commands post. The exit code matters. The hooks docs say exit code 2 blocks the call and the stderr text becomes the reason. Any other non-zero exit is non-blocking, so the call goes ahead. A PreToolUse hook that fails or times out does not block by default. Setting "onFailure": "block" makes failures block, and the docs say that needs Claude Code v2.1.295 or later.

Precedence runs managed settings, then the command line, then project local, shared project and user settings. A key set higher overrides the same key below it. Lists such as permissions.allow merge across the non-managed scopes. A deny rule in any scope blocks the call, and no other scope can allow it.

Check the result by running /status in Claude Code. Its Setting sources line names each file that loaded. Run claude doctor to see entries that were dropped.

Gotchas from the docs

The sandbox covers shell commands only. The docs say Claude’s file tools, MCP servers and hooks run outside it, so a sandbox alone does not stop a file read through the Read tool.

The unsandboxed retry is an escape hatch. Claude can retry a failed command with dangerouslyDisableSandbox, and approval depends on the permission mode. Set allowUnsandboxedCommands to false to close it.

Project allow rules wait for each teammate to trust the folder, but deny and ask rules apply at once. A -p session shows neither the workspace trust dialog nor the approval prompt for project MCP servers, so headless runs skip both prompts.

Starting Claude Code in your home directory holds trust for that session only, and the prompt returns on each launch. Start from a project subdirectory so trust is saved for that directory.

Enforcing this with AIControls

The steps above live on each machine, and they hold only where the file is deployed. The destructive-commands post and the managed-settings post go deeper on two of the rows above. AIControls covers the parts that need a central point. It routes Claude Code through the AIControls gateway, in the CLI and in VS Code. It issues per-user tokens instead of shared keys. It applies per-user and per-session cost and budget limits. It holds PII for approval, or redacts or blocks it before a prompt reaches a model. It integrates with your identity provider.

Those controls work alongside the endpoint settings above, not in place of them. If you want to see how they fit together for coding agents, start with the AIControls coding agents page, or read the AIControls download page to set it up.

Frequently asked questions

Is a permission prompt enough to stop prompt injection?

No. A prompt helps only when a person reads the command before approving it. The Claude Code security docs say no system is immune to all attacks, and they recommend reviewing suggested commands, avoiding piping untrusted content straight into the agent, and running scripts in a virtual machine when they touch external services. Pair prompts with the sandbox and deny rules so one careless approval has less reach.

Do deny rules stop rm -rf in every form?

No. A Bash deny rule such as Bash(rm *) matches the command text as Claude writes it. The permissions docs say a deny rule does not match the same program called as /bin/rm or wrapped in sh -c, and that a Bash rule is not a security boundary around the program. Add a PreToolUse hook for those forms, and turn on the sandbox so the operating system limits what the command can write.

Can a hook allow a command that a deny rule blocks?

No. Claude Code evaluates deny and ask rules whatever a PreToolUse hook returns, so a matching deny rule blocks the call even when the hook answers allow. A hook can add restrictions. It cannot remove a deny rule, and a hook that exits with code 2 blocks the call even when an allow rule would have let it run.

Does reviewing .mcp.json show every MCP server an agent can use?

No. Project servers live in .mcp.json, but servers at other scopes, claude.ai connectors and servers added by plugins are configured outside the repository. Use managed MCP configuration to restrict which servers run across an organization. Anthropic reviews connectors against its listing criteria but does not security-audit or manage MCP servers.

Should every developer have their own credential for an AI coding tool?

Yes, where you can. A shared key gives you spend with no owner and actions with no person behind them. Claude Code stores API keys in the macOS Keychain when it is available, and on Linux in a file with mode 0600, so the storage itself matters too. AIControls issues per-user tokens, which is one way to get a credential per person.

Ritesh Patel, Founder & VP Product, Nirmata

Ritesh Patel is a founder of Nirmata and leads its products. He writes about governing AI coding agents, MCP security and Kubernetes policy as code.

LinkedIn

Govern AI agents with AIControlsControl what AI agents do, and prove it.
ShareXLinkedIn

Stay up-to-date with the latest in AI, governance, and infrastructure

Subscribe to our free monthly newsletter to get industry news, DevOps tips, and articles right to your inbox on Kyverno, containerization, Kubernetes deployment & management and more.