Skip to content
sobolewski.proAI today, every eveningSubscribe

Once a day. Unsubscribe with one click. Privacy policy

AI agent security: API keys, sandboxing and spend limits

AI agent security in practice: protect API keys, set permissions and the sandbox in Claude Code, add spend limits and scan for secrets. Step by step.

7 min read

This article in other languages:العربيةDeutschEspañolFrançaisItaliano日本語Polski简体中文

In this article
  1. Why has AI agent security become urgent?
  2. Step 1: how do you set permissions in Claude Code?
  3. Step 2: how do you turn on the sandbox?
  4. Step 3: where should you keep API keys?
  5. Step 4: how do you set spend limits?
  6. Step 5: how do you catch secrets before they leak?
  7. What should you watch out for with MCP servers?
  8. FAQ

AI agent security comes down to three things: the agent sees only what it needs, API keys don’t sit where the agent (or an attacker) can read them, and every key has a spend limit. Below are five steps we can get done in an afternoon: permissions, the sandbox, key hygiene, limits and repository scanning.

Why has AI agent security become urgent?

The last few weeks have given us more than enough examples. After the OpenCode leak, users posted bills for other people’s use of their keys. In the AI cyberattack on more than 100 companies, agents broke in with almost no human involvement, and hundreds of secrets were sitting on exposed Cairn panels. On top of that, the Meta Muse leak showed that even a “share chat” feature can give away more than it seems.

On October 21 came another case: according to SafeDep, version 1.107.0 of the popular LiteLLM library landed on PyPI with a stealer that collected, among other things, SSH keys, cloud credentials, CI/CD secrets and keys for LLM providers, plus a backdoor. The attackers had the GitHub CLI credentials of three developers. PyPI removed the version the same day, and version 1.106.2, among others, is clean.

What sets an agent apart from a chat is that it takes actions: it reads files, runs commands, visits websites. OWASP describes two main risks:

  1. Prompt injection. According to OWASP, in the indirect variant the model takes instructions “from external sources, such as websites or files”. A README or a web page with a hidden instruction is enough to make the agent do something we never asked for.
  2. Excessive agency. OWASP lists three causes: too much functionality, too many permissions and too much autonomy. In the preview of the 2026 list, this risk moved up to third place.

OWASP also maintains a separate Top 10 for agentic applications, where first place goes to “Agent Goal Hijack”, meaning someone takes over the agent’s goal.

The golden rule from both lists is the same: the least privilege that gets the job done, and human approval for risky actions.

Step 1: how do you set permissions in Claude Code?

I’ll show this on Claude Code (I covered it from scratch in my Claude Code tutorial), but the same logic applies to other agent harnesses.

Permissions live in settings.json as allow, ask and deny rules. According to the documentation, the order is fixed: “deny, then ask, then allow”, so a deny always wins. Let’s start by blocking access to secrets. This example comes straight from the settings reference:

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Read(./config/credentials.json)",
      "Bash(curl *)"
    ]
  }
}

Importantly, deny rules have limits, and the documentation states them plainly:

  1. They don’t apply to commands that read files without naming them, such as grep -r pattern ., or to arbitrary subprocesses.
  2. Bash(curl *) matches the command literally, so it won’t stop /usr/bin/curl or sh -c 'curl …'.
  3. A .claudeignore file has no effect at all; its entries need to move into Read rules under deny.

That’s why rules are the first layer, not the whole defense. We can check the current permissions with the /permissions command.

The second thing is the permission mode. Claude Code has the modes default, acceptEdits, plan, auto, dontAsk and bypassPermissions. About the last one, the documentation says: “Only use this mode in isolated environments like containers or VMs”. On a shared computer we can switch it off entirely by setting permissions.disableBypassPermissionsMode to "disable".

Step 2: how do you turn on the sandbox?

The sandbox is a boundary enforced by the operating system itself, so it works where deny rules can’t reach. According to the sandbox documentation, it is off by default. We turn it on with a command in the session:

/sandbox

or by setting sandbox.enabled to true in ~/.claude/settings.json. On macOS it works out of the box; on Linux and WSL2 it needs two packages:

sudo apt-get install bubblewrap socat

On Windows without WSL2 there is no sandbox, and commands run without isolation.

What the sandbox gives us once it’s on:

  1. Commands can write only to the working directory and temporary files.
  2. Network traffic goes through a local proxy, and the list of allowed domains (network.allowedDomains) starts out empty.
  3. They can still read most of the disk, though, including ~/.ssh and ~/.aws/credentials. We have to block those paths ourselves via filesystem.denyRead or the credentials section, because, in the docs’ words, “There is no built-in credential deny list”.

Two notes from the documentation. First, the sandbox covers only shell commands: the Read, Edit, Write and WebFetch tools, hooks and local MCP servers run outside it. Second, broad domains like github.com on the allow list can open a path for data exfiltration. If you want everything isolated at once, run the whole agent in a container or a virtual machine.

In OpenAI’s Codex, the equivalents are the read-only, workspace-write and danger-full-access modes, with the network off by default. The --yolo flag (an alias for --dangerously-bypass-approvals-and-sandbox) turns off both approvals and the sandbox, so let’s keep it for containers.

Step 3: where should you keep API keys?

The simplest rule: a key never goes into code or into the repository. Anthropic recommends not putting the key directly in code or config files and adding .env files to .gitignore. OpenAI advises passing keys through environment variables or a secrets manager and rotating them regularly.

In practice:

  1. Separate keys for development, testing and production (this is also from Anthropic’s recommendations). One leak then doesn’t take everything down.
  2. The agent’s key is a separate key with its own limit, not our main one.
  3. After a leak, we revoke the key immediately, and only then look for the cause.

Interestingly, GitHub catches some leaks on its own. If a Claude key lands in a public repository, GitHub notifies Anthropic, which deactivates the key automatically.

Step 4: how do you set spend limits?

A limit won’t stop a leak, but it caps the bill when a key ends up in the wrong hands.

  1. Anthropic. In the Claude Console, go to the Billing page (platform.claude.com/settings/billing) and, in the Spend limits section, click Adjust limit or Set limit. Limits can also be set per workspace, except for the default one.
  2. OpenAI. In the project settings, go to Limits, set a Monthly spend limit and turn on Enforce a hard limit. Note: spend alerts (“Spend alerts do not enforce a cap”) don’t block anything, and the limit itself kicks in with a delay, so spending may slightly exceed it.

A good habit is one project (or workspace) per agent, each with its own key and limit.

Step 5: how do you catch secrets before they leak?

Agents write a lot of code and are happy to commit, so it pays to have automation that stops a key before the push.

  1. Push protection on GitHub. According to the documentation, it blocks pushes containing secrets before they reach the repository. For user accounts it’s on by default for public repositories, while at the repository level it’s off by default and requires GitHub Secret Protection.
  2. Gitleaks locally. Install it with brew install gitleaks and scan a directory or the git history (README):
gitleaks dir -v .
gitleaks git -v .
  1. TruffleHog also checks whether a secret it finds still works (README). This is the tool NorForge used to verify that some of the secrets on the Cairn panels were live:
trufflehog filesystem path/to/dir

What should you watch out for with MCP servers?

An MCP server is code that runs with our permissions. The Claude Code documentation recommends writing your own servers or using only those from trusted providers, and notes that Anthropic does not audit MCP servers.

The MCP security best practices (a draft version of the specification) add a few specifics:

  1. The client should show the full command that starts a local server, without truncation. The attack example in the specification is an innocent-looking npx hiding a curl that sends ~/.ssh/id_rsa to someone else’s server.
  2. Local servers are best run in a sandbox with minimal permissions and over the stdio transport.
  3. A server must not accept tokens that were not issued for it (so-called “token passthrough” is forbidden).
  4. Grant OAuth permissions incrementally, without broad scopes like *, all or full-access.

FAQ

Are deny rules in Claude Code enough to protect a .env file?

Not entirely. They block the built-in tools and recognized commands like cat, but not grep -r or arbitrary subprocesses. Full protection at the system level only comes with the sandbox plus filesystem.denyRead.

What should you do if an API key leaks?

Revoke it immediately in the provider’s dashboard, generate a new one, swap it in all applications and review recent usage on the account. More in the post about the OpenCode leak.

What is prompt injection in AI agents?

Hidden instructions in content the agent reads (web pages, files, emails). The agent may carry them out as if they were ours. That’s why risky actions should require our approval, and the agent shouldn’t have access to secrets.

Does the sandbox work on Windows?

In Claude Code, only through WSL2. On native Windows, commands run without the sandbox.

  • AI
  • AI agents
  • Claude Code
  • API
  • tutorial

AI today, every evening

The day's biggest AI news and new posts from the blog, every evening. Short, with sources.

Once a day. Unsubscribe with one click. Privacy policy