Two agents, one limit, one refused.
All guides
Credentials
Updated
6 min read
by Anlyon Team

AI agent secrets management: how to keep API keys away from the model

Where AI agents leak API keys (context window, tool arguments, environment, MCP config, logs), the four ways to hold a credential for an agent, and how to give an agent API access without it ever holding the key.

credentialssecretssecurityactions

Short answer: an AI agent should never hold a credential that can write to production. Put the key in a vault the agent cannot read. Define each production call once, outside the agent, with the key referenced by name. The agent asks for the call by name and passes input, and a system that is not the agent attaches the key and sends the request.

The rest of this guide covers where agents actually leak keys, the four ways teams hold credentials for agents, and how to implement the fourth.

Where agents leak API keys

Most leaks are not clever attacks. They follow from where the key was put.

Where the key livesHow it gets out
System promptThe model repeats it, summarises it, or passes it to a tool. Everything in the context window is readable by anything that can steer the model.
Tool parameterA tool schema with an apiKey argument means the model has to know the key to call the tool. It is now in the context for the whole session.
Agent's environmentAn agent that can run shell commands or read files is one env or cat .env away from every variable in its process.
MCP client configMCP server definitions often carry tokens in plain JSON, which get committed, synced, and shared.
Logs and tracesTool-call logging that records full request headers writes the key into your observability stack.

The first two put the key in the model's context. The last three put it within reach of any agent that has file, shell, or network tools. Prompt injection turns either into a real exposure: the model reads a web page, an email, or a PDF with instructions in it, and it cannot reliably tell instructions from data. Further reading: OWASP's Top 10 for Agentic Applications.

Four ways to hold a credential for an agent

ApproachKey stays out of the repoKey stays out of the agent's processKey stays out of the context window
1. Environment variable in the agent processNo, usually a .env fileNoOnly if no tool echoes it
2. Secrets manager, injected at runtimeYesNo, it is injected into the processOnly if no tool echoes it
3. Short-lived tokens (OAuth, workload identity)YesNo, but the token expiresOnly if no tool echoes it
4. Brokered execution: the agent names the call, something else holds the keyYesYesYes

Options 2 and 3 are real improvements. Rotation and expiry shrink how long a stolen key is useful. But in both, the agent's process holds a working credential when the tool runs. If the agent can read its own memory, environment, or files, it can read the key.

Option 4 is the only one where the agent never has the key at all. The agent's side of the boundary contains an operation name and some JSON. The key is attached by a separate system, after validation, at the moment the request is sent.

Options 1-3   agent -> tool code in the agent's process -> API key -> api.stripe.com
Option 4      agent -> "refund-order" + input -> Anlyon -> api.stripe.com

Brokered execution with Anlyon

Anlyon is option 4. Your agent declares what it wants to do. Anlyon executes it against production, with the credential outside the model.

1. Put the key in the vault, once, with an operator key

import { Client } from '@anlyonhq/sdk';

const ops = new Client({ apiKey: process.env.ANLYON_OPERATOR_KEY! });

await ops.secrets.put('STRIPE_KEY', { value: process.env.STRIPE_KEY! });

The vault is write-only. secrets.get() returns metadata and when the secret was last used, never the value. Calling secrets.put() again with the same name rotates it.

2. Define the call, referencing the secret by name

await ops.actions.create({
  name: 'refund-order',
  description: 'Refund a Stripe charge, in full or in part.',
  method: 'POST',
  urlTemplate: 'https://api.stripe.com/v1/refunds',
  headers: {
    Authorization: 'Bearer {{secret:STRIPE_KEY}}',
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  inputSchema: {
    type: 'object',
    properties: {
      charge: { type: 'string', pattern: '^ch_' },
      amount: { type: 'integer' },
    },
    required: ['charge', 'amount'],
  },
});

{{secret:STRIPE_KEY}} is a reference, not a value. Anlyon decrypts it inside the request it is about to send. An action that references a secret must have a literal scheme and host, so input cannot redirect the key to another server.

3. Give the agent a key that can only ask

The agent's Anlyon key needs actions:invoke. It does not need actions:write, so it cannot change where a credential goes, and it does not need secrets:read or secrets:write. Check that at startup so a misconfigured key fails loudly:

const anlyon = new Client({ apiKey: process.env.ANLYON_AGENT_KEY! });

await anlyon.auth.requireScopes(['actions:invoke'], {
  forbidden: ['actions:write', 'secrets:read', 'secrets:write', 'approvals:decide'],
});

const { data } = await anlyon.actions.invoke('refund-order', {
  charge: 'ch_3P9x',
  amount: 12000,
});

If you expose this to a model as a tool, the tool's whole implementation is the invoke call. The examples show it wired into the OpenAI Agents SDK and the Vercel AI SDK.

4. Remove the key from the agent's environment

This step is the one that makes the rest true. While STRIPE_KEY is still in the agent's environment, there is a second path to Stripe for the agent to find. Delete it from the agent's .env, its container secrets, and its MCP config.

What this does not cover

  • Keys your own process still holds. Anlyon governs the actions routed through its hosted executor. A tool your code calls directly, with a key in your process, is outside it.
  • The Anlyon key itself. The agent still holds its own Anlyon key. That is the point: it is a key that can only ask for named actions, can be scoped, budgeted, and revoked, and cannot read the vault.
  • What the action is allowed to do. The vault stops the key leaking. It does not decide whether a particular refund should happen. That is what approvals and policies are for.

Checklist

  1. List every credential the agent's process can read, not just the ones it uses.
  2. For each one that performs a write, define the write as an action and move the secret into the vault.
  3. Remove the secret from the agent's environment, container, and MCP config.
  4. Give the agent a key with actions:invoke and assert its scopes at startup.
  5. Put an approval in front of the writes you cannot undo.

Free during Early Beta Access, no credit card. Start building →

Frequently asked questions

Should an AI agent have direct access to API keys?

No, not for any key that can write. Anything in the agent's context window or process can be read or misused by the model, including through prompt injection. Keep the key in a vault the agent cannot read, and let a separate system attach it to the request after the agent has decided what it wants to do.

Is a secrets manager like Vault or Doppler enough for an AI agent?

A secrets manager fixes storage and rotation, but if it injects the key into the agent's own process at runtime, the agent can still reach it with a shell command, a file read, or a debug tool. It keeps keys out of your repository. It does not keep them away from the agent.

How do I stop an API key from ending up in the LLM context window?

Never put the key in the system prompt, and never make it a tool parameter. The tool schema should describe the operation and its inputs only. The credential is attached by code that runs after the model has chosen the tool, in a process the model cannot inspect.

What does Anlyon do with the credential?

An action references a vaulted secret as {{secret:NAME}}. Anlyon resolves it at dispatch, inside the request it is about to send, and never returns it to the caller. The secret is bound to a destination and a placement, which are checked again at fire time. This covers credentials in the Anlyon vault, not a key your own process already holds.

Free tier, no credit card. One command if you use Claude or Cursor.

$ claude mcp add anlyon -- npx -y @anlyonhq/mcp-server