Secrets for AI coding agents

Give your AI agent secrets without putting them in the chat

Claude Code, Cursor, Aider, Codex: sooner or later the agent needs a real API key to run a command. Today you paste it into the chat or let the agent read your .env, and from then on the plaintext sits in the transcript, in the model's context, and in whatever logs sit behind it. With envmanager run the agent writes a placeholder instead, we fill in the real value when the command starts, and we scrub it out of the output again.

what the agent runs

$ envmanager run --only STRIPE_KEY -- \

curl -H "Authorization: Bearer {{STRIPE_KEY}}" https://api.stripe.com/v1/charges

{ "object": "list", "data": [ ... ] }

$ envmanager run --only STRIPE_KEY -- sh -c 'echo $STRIPE_KEY'

***

Where the key ends up today

In the transcript

You paste the key into the chat so the agent can run one command. The provider stores that conversation, it sits in your history, and months later you can still search for it, and so can anyone who gets into your account.

In the .env the agent reads

You tell the agent to "check the config", it cats the .env, and every value in the file is now in the model's context for the rest of the session, whether the task needed it or not.

In the logs

Agent frameworks log every tool call, and a curl with a bearer token in the header is a tool call like any other. That log gets shipped somewhere, and in our experience rarely with the header redacted.

In a commit

The agent helpfully hardcodes the key it just saw into a script to make the test pass. Git history keeps it long after the line is removed, and bots scan public repos for exactly this within minutes.

How envmanager run works

Three things happen together, every time.

1. You scope the run

Every run names the variables it may use: --only DATABASE_URL,STRIPE_KEY, or an allow-list in envmanager.json. No scope means the command refuses to start rather than quietly exposing everything.

2. We inject at the last moment

The values go into the child process environment and replace any {{VAR}} placeholder inside the command, right before it starts. The command runs directly, not through a shell, so a value can never be read as a shell instruction.

3. We scrub the output

Anything the command prints is filtered and every secret value is replaced with ***. An accidental echo, a verbose log, a stack trace: none of it hands the value back to the agent.

Set it up once for Claude Code

Drop this in your project's CLAUDE.md and the agent stops asking for keys. It asks for a placeholder instead.

## Secrets
Never ask for or read secret values. Never read .env.
To run anything that needs a secret, use:
  envmanager run --only KEY1,KEY2 -- <command>
Refer to values as {{KEY}} inside the command.
Allowed keys are listed in envmanager.json under agent.allowed_keys.

Or pin the allow-list in envmanager.json

With an allow-list in place the agent doesn't need --only every time, and it can never widen the scope on its own.

{
  "project_id": "your-project-id",
  "environment": "development",
  "agent": {
    "allowed_keys": ["DATABASE_URL", "STRIPE_KEY"]
  }
}

What this does and doesn't protect against

This stops accidental leaks, which is the everyday problem: a cooperative agent or tool surfacing a value into its context, its logs, or a commit. It is not a sandbox though. The value is still decrypted to the CLI on your machine, so a program that is actively trying to get it out and holds your CLI credential could fetch it another way. That is why we make you scope every run: give the command the two keys it needs instead of --all, and the worst case stays limited to those two.

One more edge case: values shorter than six characters are not scrubbed, because masking those would garble normal output. So don't rely on run to hide a four-digit PIN.

What the rest of your team gets

run is one command in a secrets manager we built for teams, so the human side is covered by the same tool.

Encrypted at rest, revealed on purpose

Secret values are AES-256-GCM encrypted in Supabase Vault and decrypted server-side only for people and tokens you gave access to. Every reveal is logged.

Roles per environment

Developers see staging, leads see production. An agent gets a CLI token scoped the same way, so the blast radius of a bad run is whatever that token can see.

Sync where you deploy

Vercel, Railway, Render, Dokploy, Coolify, GitHub Actions, AWS, Azure and GCP. Change a value once and the next sync pushes it everywhere it belongs.

One audit trail, humans and agents

Every pull, run and change lands in the same log with who, what and when, so "which agent used the Stripe key on Tuesday" has an answer.

Questions we get

Does this work with Claude Code, Cursor, Aider and Codex?

Yes. run is a normal CLI command, so anything that can execute a shell command can use it. The CLAUDE.md snippet above is the Claude Code version; the same instruction works in .cursorrules or any agent instruction file.

Why {{KEY}} and not $KEY?

Your own shell expands $KEY before EnvManager ever sees the command, usually to nothing. The double-brace form passes through the shell untouched, and we fill it in. Programs that read the environment themselves, like npm or psql, just work with plain injection and need no placeholder at all.

Can the agent still run envmanager pull and read everything?

If it holds your CLI credential, technically yes, which is why this is not a sandbox. In practice you tell the agent not to, you scope the token to the environment it needs, and every pull shows up in the audit log so you would see it.

What does it cost?

run is included in Pro, which is one flat price for the whole team, no per-seat or per-agent charges. There is a 14-day trial with no card.

Give your agent placeholders instead of keys

Import your .env, install the CLI, and your agent runs with placeholders from the next command on. 14 days free, no card. 49 founding spots left at $9 a month for the whole team.