Skip to content
Founders$49 once → 2 years of Pro ($98 value)Become Founder →
ClauLock

Blog

Vault vs ClauLock: why traditional secrets managers fail for AI agents

HashiCorp Vault is the best secrets infrastructure the services era produced — and it's the wrong shape for agents. A technical walkthrough of where the fit breaks, why the natural retrofits leak, and how the two tools coexist.

By Jesús E. Viera · · 12 min read

HashiCorp Vault is the best piece of secrets infrastructure the services era produced. If you have ever bootstrapped a Kubernetes cluster with a Vault operator, written a vault kv put against a dev-mode listener at 11pm trying to ship a patch, or argued with a platform team about AppRole vs Kubernetes auth, you know this. Vault works. It solves the problem it was built for.

The problem it was built for is not, however, the problem AI agents create. This post is about where exactly the fit breaks, why the natural retrofits leak, and what a secrets layer for agents has to do differently. It is not a hit piece; I've run Vault in production and I have no desire to replace it for the services case. The argument is narrower: the moment your consumer is a model instead of a service, Vault's model flips from "asset" to "liability," and the reasons are architectural rather than cosmetic.

What Vault's threat model actually is

Vault's canonical deployment makes five assumptions. Each is reasonable for services, and each fails for agents.

  1. The caller is trusted code you wrote. Your service authenticates to Vault, gets a token, and uses the token internally. What happens to the token inside the service process is your business; Vault's job ends at issuance.
  2. Authentication identifies a workload. AppRole, Kubernetes auth, AWS IAM auth, JWT auth — all of these bind Vault's trust decision to a machine identity. "This pod, this role, this cluster." Humans auth differently (OIDC, LDAP, token), but the shape is still "known principal."
  3. The API surface returns plaintext values to authenticated callers. vault kv get on a leaf path returns the value. vault write database/creds/<role> returns a username and password. This is the point of Vault — ask for a secret, get the secret. Any tool built on top of Vault gets the value back in its own process memory.
  4. Short-lived dynamic credentials mitigate leaks. A 15-minute database password that rotates is "safe enough" because even if it appears in a log, it is stale by the time anyone reads it. Vault's PKI, database, AWS, and GCP secret engines are built around this idea.
  5. The secret travels to a known audience. The service that asked for it. The orchestration layer that injects it. Maybe an APM tool that redacts on ingest. The audience is a small, enumerable set of processes under your administrative control.

How each assumption breaks for agents

Assumption 1 breaks: the caller is not code you wrote

In an agent deployment, the effective caller is Claude. Claude composes the request to the secrets layer at inference time; the request exists because the model emitted it. You did not author the code path and you cannot pre-audit it. Saying "but the model's tool call goes through my wrapper" does not change the trust model — the wrapper is a transparent relay, not an author.

Vault has nothing against this; it is not Vault's concern. But it means Vault's first assumption — that whatever is on the other side of the auth token is trusted internals — is violated by construction.

Assumption 2 breaks: authentication identifies nothing useful

Who is Claude? What principal should Claude present to Vault? "The user who launched Claude Code" is the best available answer, but it's a weak one: it grants Claude everything the user has access to, bounded only by scope tokens the user remembered to configure. There is no per-turn, per-project, per-destination identity; there is just "the user, mediated by a model the user doesn't audit."

You can narrow this with scoped tokens, and you should. But narrowing only goes as far as the user remembers to scope. In practice, a developer at 4pm on a Friday grants Claude a broad token and ships the thing.

Assumption 3 breaks: returning the value to the caller is the vulnerability

This is the core. Vault's API is verb-shaped around return the value. For services that is correct; for agents it is the defect. The moment Vault returns ghp_live_a1b2… to Claude Code's execution environment, that value is one echo $GITHUB_TOKEN away from the transcript, one curl-with-verbose-flag away from the model's context, and one tool-output-rendered-to-the-agent away from the Anthropic API payload.

You cannot fix this with Vault policy. Policy controls who can read; it does not control what happens to the bytes after they are read. The consumer — the agent runtime — is the wrong place for that enforcement because it is the thing you are trying to blind.

Assumption 4 breaks: rotation doesn't matter if the leak is live

A short-lived token is useful when the risk is "log scrapes happen six hours late." Agent transcripts are scraped in the same request. The model reads its own prior tool output on the very next turn. A 15-minute TTL does not help if the token is visible to the model for the 15 minutes it is valid; the attacker (prompt injection on turn n+1) has the token the moment the legitimate turn n used it.

Short-lived tokens are still worth having for agents, but as a damage limiter, not as a primary defence. They answer "how long is the blast radius" not "did the leak happen."

Assumption 5 breaks: the audience is uncountable

With an agent, the audience of a secret-bearing byte stream includes: the Claude Code CLI, the model vendor's API, the model's next-turn context, any transcript replay the user kicks off, the MCP servers observing the call, whatever editor is rendering the transcript live, plus all of the destinations the model's command reached. That is not an enumerable set you can redact at ingest. Redaction has to happen before it reaches any of them.

The natural Vault-for-agents patterns, and where each leaks

People try these in order, from "least work" to "most work," and each has a specific failure mode. If you've attempted one of these and it felt off, this section is to confirm that the off feeling was correct.

Pattern A: vault kv get directly from Claude Code

The simplest. Claude needs a secret, so Claude runs vault kv get -field=token secret/github. Vault returns the value to stdout. Stdout goes through Claude Code's Bash tool. The tool result goes to Claude.

Result: the plaintext is in the transcript by the time the curl runs. Broken on turn one, no further analysis needed.

Pattern B: vault kv get piped to a consumer

"OK, we'll use command substitution." curl -H "Authorization: Bearer $(vault kv get -field=token secret/github)" Claude writes the shell that both fetches and uses the value.

Result: the pipe itself is the transcript entry. The vault kv get subcommand appears in Claude's tool input. It runs, produces plaintext, gets inlined into the curl. If curl runs with -v — or if any step echoes its own environment — the value is in the tool output. The transcript records the command shape but not the value, which is better than Pattern A, but the moment any child echoes anything the value appears.

More importantly: there is no scrubber between child output and the model. PostToolUse hooks in Claude Code can scrub, but they need to know which bytes to scrub. With command substitution, nothing tells the scrubber what the substituted value was. It has no dictionary. Pattern B can leak on any verbose subprocess and there is no defence.

Pattern C: Vault Agent sidecar with template injection

The HashiCorp-recommended production pattern. Vault Agent runs alongside your workload, maintains a lease, renders secrets into a templated file via consul-template-style templates, and your workload reads the file.

This is a real improvement for services. For agents, it moves the problem: Claude Code can still cat the rendered file. The model knows how to do this. Prompt injection can ask for the file's path. The protection — a local file — is accessible to the exact runtime you are trying to blind.

You can tighten this with filesystem ACLs, but Claude Code runs as your user. Your user owns the file. The permission boundary is at the wrong layer.

Pattern D: AppRole with response wrapping

The "zero-trust" pattern. Vault issues a single-use cubbyhole token to an intermediary; the intermediary unwraps it once and gets the real secret inside.

Response wrapping solves "secret must not be observable in transit to a specific consumer." It does not solve "secret must not be observable to the consumer itself." Once the intermediary unwraps the value, the value is in the intermediary's memory — and in the agent deployment, the intermediary is Claude's execution environment. We are back to the Pattern A failure with extra steps.

Pattern E: Vault plus a homegrown MCP server

The hybrid many teams end up building. Write an MCP server that calls Vault on Claude's behalf and returns... what? If it returns the value, we are back to Pattern A. If it returns a handle, and the MCP server intercepts Claude's tool calls to substitute the handle into child process memory, you have built ClauLock. The cheap version of ClauLock, probably, but the shape has to converge because the constraint is structural.

This is the pattern I started with in 2024. I wrote it, used it, had a near-miss with a scrubber edge case that took a week to find, and ended up generalising it. That is why ClauLock exists.

What a correct agent-secrets layer has that Vault doesn't

A layer that fits the agent threat model has four features Vault does not have because Vault was not built for this shape:

  1. An API with no "return value" verb. Not "return-value-but-short-lived." Not "return-value-but-audit-it." No return-value verb. Vault without kv get is not Vault.
  2. A placeholder substitution boundary between the model and the executing child. The model writes {{GITHUB_TOKEN}}; the PreToolUse hook resolves it into the child's environment. The transcript and the child never see the same byte stream. Vault Agent's template injection is the closest analogue, and it fails because the rendered file is readable by the agent.
  3. A scrubber that knows the dictionary. The output of every tool call has to be scanned for the bytes of the values that were substituted into that call. This requires a per-session dictionary of "values in flight." Vault does not know what the consumer did with what it returned; it cannot provide this dictionary.
  4. Local-first trust posture. Vault wants to be the authority. For agents, the authority should be the device. The developer's laptop is where the agent runs, and a cloud round-trip for every secret use is a liveness liability without a corresponding security gain.

The retrofit argument, honestly

Could HashiCorp build this? Sure. They could add a "no-reveal" engine that returns handles, ship a PreToolUse-equivalent hook spec for agent runtimes, and ship a scrubber plugin. It would be a large product; the core Vault API is the thing that would need to change, and changing a stable API is not what Vault has done well historically. More likely: a new service, perhaps called "Vault Agent for AI," that runs alongside Vault and handles the agent path. That service would be a direct competitor to ClauLock, and I would welcome it; there is room.

Until that service exists, teams running Claude Code on laptops with access to production credentials do not have a production Vault pattern that closes the leak class. They have patterns A–E, each of which leaks somewhere.

The coexistence story

This is not replacement. ClauLock does not want to be your PKI. It does not want to rotate your database passwords on a 15-minute cycle. It does not want to federate your Kubernetes auth. Vault is better at all of these than anything that is going to exist for years. Keep Vault.

The coexistence looks like this:

  • Services → Vault. Your backend services authenticate to Vault, get short-lived credentials, and use them. No change. Vault's existing policy, audit, and rotation story applies.
  • Humans → 1Password / Bitwarden. For personal credentials and browser logins. No change there either.
  • Agents → ClauLock. The credentials the developer's Claude Code session needs — GitHub, Stripe, Vercel, whatever — go in a local ClauLock vault. Pattern: developer issues a scoped PAT once, puts it in ClauLock, Claude uses it via placeholder substitution for weeks.
  • Agents that need Vault-managed credentials: two options. (a) ClauLock has a Vault-backed secret kind planned for a future release that performs the vault read on demand and substitutes the result into the child without ever exposing it to the model. (b) A human-in-the-loop flow: developer runs vault read once per session, paste the result into ClauLock's elicitation UI, Claude uses it via placeholder. Option (b) works today, requires no new infrastructure, and preserves Vault's rotation semantics.

Migration, for a team that already runs Vault

If you have Vault in production and you are about to roll out Claude Code to your dev team, here is the short version of the migration.

  1. Inventory the secrets your developers use interactively — the ones that end up in a .env, an ~/.netrc, or an exported env var. These are the agent-secrets candidates.
  2. Install ClauLock on developer machines.
  3. For each secret, have the developer request it from Vault once (via vault read) and put the response into their ClauLock vault via the elicitation UI. Scope it per-project; tag it with a destination allowlist.
  4. Configure Claude Code to use ClauLock's MCP server and hooks. See the docs.
  5. Rotate at Vault's normal cadence. When the developer's cached ClauLock value expires, the pre-hook refuses to substitute; the developer re-requests from Vault. Vault's rotation story is preserved; the agent never sees a value.

Services keep using Vault directly. Only agents route through ClauLock. This is the split we recommend and the split we've seen work in the teams that have piloted it.

When Vault is still the right answer

To be explicit: if your Claude deployment is on a server, not a developer's laptop — e.g., a background agent running in your cluster — and the agent's scope is narrow enough that you can give it a workload identity and a short-lived dynamic credential with no transcript visibility, Vault plus careful redaction may be enough. The failure modes in patterns A–E are tied to interactive agent deployments where a human reads the transcript. Server-side agents that never expose transcripts to a human are a different threat model, and Vault's services-era strengths apply there.

Most of the agent deployments I've seen in 2025–2026 are the developer-laptop kind. For those, the gap is real. That's the gap ClauLock was built to close.

ClauLock is local-first, open-source (Apache-2.0 core · BUSL-1.1 product), signed binaries. See the full comparison or install it. Run bash tests/leak_test.sh against it yourself.