Vault 🇬🇧 🇩🇪 🇫🇷
  • Send
  • Request
  • How it works
  • Security & Trust
  • Pricing
  • How to give an AI coding agent an API key without it ever seeing the value

    Coding agents like Claude Code, Cursor or Codex need database passwords and API keys to deploy, migrate and test. The usual ways of handing them over put the value into the agent's context, and from there into session logs on disk and into the requests sent to the model provider. This guide shows where the value leaks, which rules actually keep it out, and how to set that up in a few minutes.

    In short

    • Whatever a tool prints, the agent reads, and whatever the agent reads stays in its session log.
    • A secret stays out of the context only if it goes straight to the process that needs it and never to standard output.
    • vault-ask does this for any agent with a shell or MCP: you enter the value in your browser, it reaches exactly one command or file, and the output is masked.

    Why the context is the wrong place for a secret

    An agent works by running tools and reading their output. That output becomes part of the conversation: it is sent to the model with every following request and written to the session log on your machine. A value that appears there once stays there, long after the task is done. In our own measurement, 68 of 70 deployment secrets ended up in plain text in the session logs of a coding agent within four weeks, and none of them had been typed into the chat.

    How the 68 out of 70 were measured

    Four common ways, and where each one leaks

    1. Pasting the key into the chat: the value is in the conversation from the first second, in the log and in every later request to the model.
    2. A .env file in the project: sooner or later the agent reads it while debugging, with cat, grep or a search across the project, and the file content is in the output.
    3. Exporting the variable in your shell before starting the agent: the agent inherits it, and env, printenv, a debug log or a stack trace prints it.
    4. Fetching it with a secret manager CLI such as op read or vault kv get: the command writes the value to standard output, which is exactly where the agent reads.

    The pattern is always the same: the agent does nothing wrong, a tool prints the value and the agent reads what tools print.

    Three rules that actually keep the value out

    1. The value goes to the process, not to the output. It is passed in the environment of one child process or written to one file with restrictive permissions, and no step prints it.
    2. The output is masked. If the command itself prints the value, for example in an error message, it is replaced before the agent sees it, including encoded forms such as Base64.
    3. The value is bound to its purpose. You see which command or file it is meant for before you enter it, and it cannot be redirected to another command afterwards.

    If you already use a secret manager

    Run commands such as 1Password's op run or doppler run already follow the first rule: they inject the value into one process instead of printing it, and op run also masks it in the output. Use those instead of read or get commands. What they do not cover is the moment the value is missing: the agent cannot ask you for it without the chat, and nothing binds the value to the command it was meant for.

    Setting it up with vault-ask

    vault-ask is a small open source command line tool and MCP server for Node.js 20 or newer, with no dependencies.

    npm install -g @erseni/vault-ask

    The agent requests the value for exactly one command:

    vault-ask DB_PASSWORD -- psql -h db.example.com -U app

    vault-ask prints a link. You open it in your browser, see the command the value is meant for and enter the value. It arrives end-to-end encrypted, goes only into the environment of that command, and the agent sees nothing but a line like "DB_PASSWORD received, 32 characters, passed to the command".

    For tools that read a file, the value goes into a .env file or a single file with permissions 600:

    vault-ask STRIPE_KEY SENTRY_DSN --env-file .env vault-ask TLS_KEY --file certs/server.key

    In Claude Code, add the MCP server once. From then on the agent asks for secrets on its own instead of asking you to paste them:

    claude mcp add vault-ask -- npx -y @erseni/vault-ask mcp

    In Cursor or any other MCP client, add the server to the MCP configuration (for Cursor in ~/.cursor/mcp.json):

    { "mcpServers": { "vault-ask": { "command": "npx", "args": ["-y", "@erseni/vault-ask", "mcp"] } } }

    A guard against reading the file afterwards

    A value written to a file can still be read later. For Claude Code, vault-ask therefore ships vault-ask-guard, a PreToolUse hook that refuses to read the files vault-ask wrote and refuses commands like env or echo as a target. Add it to .claude/settings.json:

    { "hooks": { "PreToolUse": [ { "matcher": "Bash|Read|Edit|MultiEdit|Write|Grep|Glob|NotebookEdit|mcp__vault-ask__run_with_secret|mcp__vault-ask__send_secret", "hooks": [{ "type": "command", "command": "vault-ask-guard" }] } ] } }

    Where this stops protecting

    These rules catch accidents, and they make a manipulated agent visible, because it would have to ask you again for every new command. They cannot stop an agent that runs under your own operating system user with a shell and deliberately reads the memory or environment of a process it started. Complete protection needs privilege separation: the secret is held by a service under another user and the agent only holds a reference to it.

    More about vault-ask Source code on GitHub

    Product

    Security

    Legal

    Cancel contracts here © 2026 Erseni Ltd. Zero-knowledge by design.