When enterprises run AI coding agents on their own infrastructure — air-gapped environments, private cloud, self-hosted CI — they typically assume that their existing secrets management tooling covers them. They have HashiCorp Vault, or Kubernetes secrets, or Doppler, or systemd credentials. These are mature, well-audited systems.
They were not designed for a world where the process requesting credentials is an AI agent that can read its own environment.
This is not a criticism of any of these tools. It’s a structural gap that emerges from how AI agents work differently from the workloads these tools were built for.
How every existing tool works
Every major secrets management solution injects credentials at the environment level. The mechanics differ, but the model is the same:
HashiCorp Vault issues short-lived tokens and credentials. Applications authenticate to Vault (via AppRole, Kubernetes auth, AWS IAM, etc.) and receive secrets they then load into environment variables or a config file. The application process holds the credential in its environment from startup.
Kubernetes secrets are mounted as environment variables or volume files into Pod containers. The application reads process.env.DATABASE_URL or opens /var/run/secrets/DATABASE_URL. The value is in the process environment from the moment the container starts.
Doppler and Infisical wrap your process with doppler run -- <cmd> or infisical run -- <cmd>. They fetch secrets from their APIs and inject them into the subprocess environment before it starts. Same model — the process environment holds the credentials.
systemd credentials (LoadCredential=, SetCredential=) make secrets available to service processes as files in a tmpfs directory. The service reads the file. The credential value is accessible to the running process.
AWS Secrets Manager, Azure Key Vault, GCP Secret Manager — all follow the same pattern. A service authenticates and retrieves a secret value. The value is then held in the application’s environment, memory, or configuration.
This works correctly for the workloads these tools were designed for. A Node.js API process, a Python data pipeline, a Go microservice — they retrieve credentials at startup, use them, and the credentials are scoped to that process’s lifetime. If you compromise the process, you get the credentials it holds. That’s an acceptable risk model because you’re in control of what the process does.
What changes with an AI agent
An AI coding agent is not a conventional application process. Claude Code, for example:
- Runs interactively across extended sessions
- Has access to your shell and filesystem
- Can execute arbitrary commands (
bash,python,node,curl) as part of normal operation - Inherits your full shell environment
The last point is where the structural gap appears. If you set credentials in your shell environment before launching Claude Code — whether via export VAR=value, via Vault’s env injection, via doppler run, or via any other environment-level injection — Claude Code inherits those variables. When Claude Code executes a subprocess, that subprocess also inherits them.
More concretely: if Claude Code runs env, printenv, bash -c 'echo $DATABASE_URL', or any script that happens to inspect or log its environment, the credential values appear in that command’s output. That output is returned to Claude’s context. The values are now in the AI’s context window for the duration of the session.
This is not theoretical. In October 2025, security researcher Aonan Guan demonstrated exactly this attack against Anthropic’s official Claude Code Security Review GitHub Action. By embedding malicious instructions in a pull request title, he caused Claude to execute a whoami command and return the output in a public PR comment. When asked to demonstrate further, he escalated to stealing GitHub access tokens and Anthropic API keys from the runner environment — the same environment Claude Code had inherited. HackerOne initially rated the vulnerability CVSS 9.3, later upgraded to 9.4. Anthropic resolved the issue on November 25, 2025 with a $100 bounty. The Register published the full details in April 2026. Anthropic’s documentation for the action now carries an explicit warning that it “is not hardened against prompt injection attacks and should only be used to review trusted PRs.”
OWASP’s Top 10 for LLM Applications (2025) lists prompt injection as the #1 risk for AI systems with tool access. The Guan demonstration is the clearest published proof of the threat model: attacker embeds instructions in something the agent reads → agent executes a command the attacker shaped → command returns credentials from the inherited environment. The credentials didn’t need to be directly accessible — they just needed to be present in the runner’s environment, which they were.
The subprocess injection gap
What every existing tool is missing is per-subprocess, per-call credential injection where the agent itself never holds the plaintext. (When using vault_run, the agent process does not receive plaintext in its context window. Direct shell execution or misconfigured tool calls can reintroduce plaintext exposure.)
The pattern looks like this:
AI agent says: "run this command"Secret manager decrypts credential locallySecret manager spawns subprocess with credential in environmentSubprocess runs, output capturedOutput scanned for secret patterns before returning to agentAgent receives output — not the credential valueThis is different from:
Shell environment: DATABASE_URL=postgresql://...AI agent launches (inherits DATABASE_URL)AI agent runs command (subprocess inherits DATABASE_URL)Command completes, output returnedAgent now has output — and had DATABASE_URL the entire sessionIn the first pattern, the agent is a broker. It coordinates what runs, but the credential never enters its context. In the second pattern, the agent is a principal. It holds the credential and passes it downstream. The distinction matters enormously when the agent is also the attack surface.
No major secrets manager implements the first pattern for AI agents. Vault, Doppler, Infisical, K8s, systemd — they all implement the second. This is not because their developers missed something; it’s because the first pattern didn’t have a use case until AI agents became common.
What on-premise AI deployments actually need
For teams running AI agents in their own infrastructure, the requirements are:
1. Credentials must not live in the agent’s inherited environment.
The agent should not start with credentials pre-loaded. Each command execution should request exactly the credentials it needs, at that moment, from a local broker.
2. The broker must run locally, not as a remote API call.
If the credential broker is a network service (Vault over HTTPS, Doppler API, AWS Secrets Manager), the agent must retrieve the credential and hold it before passing it to a subprocess. That retrieval puts the value in the agent’s context. The broker must run as a local process that can inject directly into a subprocess without the agent receiving the value.
3. Output must be scanned before returning to the agent.
Even with subprocess injection, a command can echo its own environment (env, printenv, set). The broker should scan subprocess output for known secret patterns before returning it to the agent, as a second layer of defense.
4. The remote store must be zero-knowledge.
If you’re storing credentials in a central service that your local broker fetches from, that service should store only ciphertext. If the service is compromised, an attacker should get encrypted blobs, not usable credentials.
5. Audit logs must not reveal what credentials you hold.
Audit log entries for credential access should use HMAC references, not plaintext secret names. A compromised audit log should tell an attacker that a credential was accessed at a given time, not which credential or what its name suggests about your infrastructure.
How OpaqueVault addresses this for on-premise teams
OpaqueVault is built around the broker pattern. The ov mcp serve process runs locally alongside your AI agent. It holds your Key Encryption Key in memory after you authenticate — the KEK is derived client-side via Argon2id and never transmitted. The remote component (api.opaquevault.com) stores only ciphertext; it has no decryption capability.
When your AI agent needs to run an authenticated command, it calls vault_run:
Agent → vault_run(command=["psql", "-c", "SELECT count(*) FROM users"], secret_names=["DATABASE_URL"]) → ov mcp serve decrypts DATABASE_URL locally (never transmitted) → Spawns psql subprocess with DATABASE_URL in env → Captures stdout/stderr → Scans output for secret patterns (output interceptor) → Returns query results to agent → DATABASE_URL never entered agent's contextThe zero-knowledge constraint holds regardless of where the ciphertext store runs — the API server has no decryption capability by design. The encrypted blobs it stores are useless without the KEK, which never leaves the client machine.
For air-gapped environments or CI pipelines, the ov agent daemon provides machine-key-based authentication. The machine key is a 32-byte key stored at a path you specify (via --machine-key), scoped via HKDF-SHA256, and used to derive per-request HMAC-SHA256 authentication tokens for the local socket. The agent socket is a Unix domain socket with chmod 600/chmod 700 permissions enforced via fstat (TOCTOU-safe) and O_NOFOLLOW symlink protection. This allows CI jobs and automated pipelines to call vault_run without interactive authentication, without storing credentials in environment variables, and without those credentials being accessible to the agent process itself.
Comparing the models
| Tool | Injection point | Agent holds plaintext? | Per-call scoping | ZK remote store |
|---|---|---|---|---|
| HashiCorp Vault | Process environment | Yes (inherits env) | No | No (Vault decrypts)* |
| Kubernetes secrets | Pod env / volume | Yes (inherits env) | No | No |
| Doppler | Subprocess env via doppler run | Yes (inherits env) | No | No (Doppler decrypts) |
| Infisical | Subprocess env via infisical run | Yes (inherits env) | No | No (Infisical decrypts) |
| systemd credentials | tmpfs file | Yes (reads file) | No | No |
| OpaqueVault | Per-subprocess, via MCP broker | No | Yes | Yes (ciphertext only) |
The “agent holds plaintext” column is the critical one. For AI agents, this is the attack surface that prompt injection exploits. Eliminating it requires the broker pattern — which none of the existing tools implement.
*Vault’s Transit secrets engine is an exception — it performs encryption-as-a-service without storing plaintext. The comparison applies to Vault’s KV engine and HCP Vault Secrets.
Practical steps for on-premise teams
If you’re deploying AI agents in your own infrastructure today:
Audit what’s in your agent’s inherited environment. Before your AI agent starts, what environment variables does it inherit? If any of those variables are credentials (database URLs, API keys, signing keys), they’re in scope for prompt injection exfiltration.
Don’t use export VAR=value to “safely” provide credentials to an AI agent session. The agent inherits that environment. Running env returns the values. Setting credentials in the shell before launching an agent is not isolation — it’s exposure.
Use vault_run for any command that requires credentials. This is the only pattern that keeps the credential out of the agent’s context while still allowing the agent to run authenticated commands.
Treat your AI agent’s execution environment as untrusted. Assume that any value in the agent’s inherited environment, any file the agent can read, and any command output the agent receives is potentially visible to an attacker who can influence what the agent reads. Design your secrets management accordingly.
Audit logs belong in your SIEM. OpaqueVault’s audit log entries use HMAC-hashed secret references — an attacker with the audit log gets timing data, not a secrets inventory. Export these to your existing security tooling.
The gap between “existing secrets management” and “AI-agent-safe secrets management” is real and currently unaddressed by every major tool in the category. That’s not an indictment of those tools — they solved the problems they were designed for. It’s a signal that AI agent infrastructure needs a new layer, and that layer needs to be built around the broker pattern rather than environment inheritance.
OpaqueVault is the MCP-native, zero-knowledge secret manager built for AI coding agents. It implements the broker pattern: credentials are injected into subprocesses by a local broker, and when using vault_run, the AI agent never holds plaintext values. Get started free →
Related: MCP Secret Manager — How OpaqueVault Works · Why Your AI Coding Assistant Is a Secret Leak Waiting to Happen
Corrections (April 2026):
- Clarified Aonan Guan disclosure timeline: reported October 17, 2025; resolved November 25, 2025 with $100 bounty and CVSS 9.4 rating. (Guan’s writeup, The Register)
- Added Vault Transit engine qualification to comparison table. (Vault Transit docs)
- Qualified “agent never holds plaintext” — this holds when vault_run is used correctly; direct shell execution can reintroduce exposure.