6 minute read

Run env in your terminal. Everything you see there, your AI agent can see too.

Claude Code, Cursor, Codex, Windsurf, whatever you use. These tools run as processes on your machine with the same permissions as your shell. If you have AWS_SECRET_ACCESS_KEY in your .zshrc, the agent has it. If you have a .env file with database credentials, the agent can read it. If you exported a Stripe API key three months ago and forgot about it, the agent still knows.

This isn’t a bug. It’s how processes work on Unix.

Agents are processes, not people

When you run an AI coding agent, you’re launching a process that inherits your shell environment. It can execute commands, read files, and make network requests. It does all of this to be useful, and mostly it is.

But these agents also accept instructions from the content they read. A malicious comment in a GitHub issue, a crafted docstring in a dependency, a weird README in a package you’re evaluating. This is prompt injection, and it’s not theoretical.

In December 2025, a security researcher disclosed over 30 vulnerabilities across Cursor, GitHub Copilot, Windsurf, and other popular AI coding tools, collectively dubbed “IDEsaster.” The flaws enable data theft and remote code execution through prompt injection. Around the same time, someone compromised Amazon Q’s VS Code extension and planted prompts that could wipe files and disable AWS infrastructure. In May 2025, an attacker crafted a malicious GitHub issue that hijacked an AI assistant connected to an MCP server, leaking private repo contents and personal financial information to a public repository.

The agent reads something that tells it to do something else. Maybe it curls your env vars to an external URL. Maybe it reads ~/.aws/credentials and stuffs the contents into a commit message. The attack surface is your entire environment.

What you should actually do

There’s no single fix. Defense in depth applies here the same way it does everywhere else. You should:

  • Run agents in sandboxed containers. Docker sandboxes, DevContainers, and Claude Code’s native sandboxing all limit filesystem and network access. This is the single biggest thing you can do. If the agent can’t read /Users/you/.aws/credentials or reach the internet, most exfiltration attacks are dead on arrival.
  • Use short-lived, least-privilege credentials. A GitHub fine-grained PAT scoped to one repo with read-only access is a lot less dangerous than a classic PAT with full repo scope. AWS STS session tokens, OAuth tokens with short expiry, scoped API keys. If a credential leaks, you want it to be useless by the time anyone tries to use it.
  • Restrict agent permissions at the tool level. Most agents have permission controls. Claude Code has permission modes and deny rules that can block file access and network requests to specific paths and domains. Use them. An agent that can’t write to .mcp.json or ~/.zshrc can’t tamper with its own credential pipeline.

These are all good practices. You should do all of them.

But security is always about finding the right balance. Not everyone has time to implement all of this at once, especially if you’re a solo developer or a small team moving at light speed. And that’s fine. Security isn’t binary. It’s about understanding the risk you’re comfortable with and adding layers as you go. Most of us start with export API_KEY=sk-... in .zshrc and move on.

Here’s something practical you can do right now, in about an hour, that adds a meaningful layer on top of whatever else you’re already doing (or not doing).

Process-scoped secrets with 1Password CLI

1Password CLI lets you store secrets in your vault and reference them with op:// URIs. You can test this right now if you have 1Password CLI installed:

$ op read "op://Vault/GitHub/token"
ghp_a1b2c3d4e5f6...

That fetches a single secret from your vault, authenticated via biometrics or a cached session. The interesting part is op run, which uses the same references to inject secrets into a child process:

  1. You define environment variables using op:// references that point to items in your vault.
  2. op run resolves those references at launch time.
  3. It passes the resolved values as env vars to the child process only.
  4. The secrets never enter your shell environment, never hit disk, and aren’t visible to env or /proc.

The child process gets exactly the secrets it needs. Nothing else on your machine can see them.

Where this actually matters: MCP servers

Say you’re using GitHub’s official MCP server so your coding agent can create issues, review PRs, and search code. It needs a Personal Access Token. The typical approach is to put it in .env or export it in your shell profile. But then every process on your machine, including the AI agent itself, can see that token. And a GitHub PAT with repo scope is keys to the kingdom.

Here’s the problem: if the agent falls victim to prompt injection, it already has shell access. If your GitHub PAT is sitting in an env var or a dotfile, the hijacked agent can grab it with a single echo or cat command. Same for your AWS keys, Stripe tokens, database passwords, anything that’s ambient in the environment. The more credentials are lying around, the more an attacker can scoop up in one shot.

You can check MCP configurations into git with op:// references instead of actual secrets. Here’s what the GitHub MCP server looks like with this approach:

{
  "mcpServers": {
    "github": {
      "type": "stdio",
      "command": "op",
      "args": [
        "run", "--", "docker", "run", "-i", "--rm",
        "-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
        "ghcr.io/github/github-mcp-server"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "op://Vault/GitHub/token"
      }
    }
  }
}

Notice the command isn’t docker. It’s op. The actual server command comes after -- in the args. When the agent launches this MCP server, op run authenticates with 1Password, resolves the op:// URI to your real GitHub PAT, and injects it into that one Docker process. The same pattern works for any MCP server that needs credentials: Slack bots, Sentry, Stripe, whatever. Swap in the right command and point the env vars at the right vault items.

What this gets you

The config file is safe to commit. It contains vault paths, not secrets. You can check .mcp.json into git, clone the repo on a new machine, and everything works as long as you have 1Password access. No .env.example files, no “which API keys do I need” notes.

Secrets are scoped per process. If you run multiple MCP servers this way, each one only sees its own credentials. If one process gets compromised through prompt injection, the attacker gets one set of keys, not your entire keychain.

Nothing lands in the shell. Run env | grep GITHUB and you get nothing. The agent’s main process can’t see the token either. Only the specific child process that op run launched has it.

This is not a panacea

I want to be clear about what this does and doesn’t protect against.

An agent with write access to your filesystem can modify the .mcp.json file itself. It could replace the docker command with a wrapper that prints env vars before forwarding them. It could add "echo $GITHUB_PERSONAL_ACCESS_TOKEN &&" before the real command. If the agent can edit config files and trigger a server restart, it can intercept the secrets that op run injects. This isn’t a hypothetical. Any agent that can write files and run shell commands has this capability.

op run protects against passive leakage: the case where secrets sit in your shell environment and any process, compromised or not, can read them at any time. It makes credential access explicit and auditable rather than ambient. But it doesn’t stop a determined agent (or attacker controlling one) from actively working around it.

That’s why this is one layer, not the whole stack. You still want sandboxed containers, least-privilege credentials, and agent permission rules as described above.

Each of these layers catches a different class of attack. None of them is sufficient alone. Together they add up.

What op run gives you is the easiest layer to add. Instead of every secret being available to every process at all times, each secret is available to exactly one process for the duration of its lifetime. It takes about an hour to set up, and it meaningfully shrinks the window of exposure for the most common case: secrets leaking because they were just sitting there, visible to everything.

Stay safe.

Updated:

Comments