Secrets for coding agents: CLI skill vs MCP serverTwo doors to the same vault
Keeper, 1Password, and Doppler all ship a way to hand secrets to your coding agent, and most give you two: a CLI wrapper and an MCP server. They split on the one thing that matters, whether the raw key ever enters the model's context.
-
01
Source
Vault
Keeper, 1Password, Doppler resolve the credential
same secret either way -
02
Channel
Two doors
CLI exec injection or MCP tool response
the demos look identical -
03
Exposure
Context
exec keeps the key out, MCP can pull it in
this sets your blast radius
TL;DR· the answer, in twenty seconds
What happened: Keeper shipped Agent Kit on April 30, 2026, joining 1Password and Doppler in selling coding agents a secrets front end. Each kit offers exec injection, MCP retrieval, or both.
The minimum fix: Prefer the exec wrapper (ksm exec, op run, doppler run). It places the credential in the child process and never returns the value to the model. Reach for the MCP path only when the server brokers the action instead of handing back the key.
The lesson: The packaging is new. The question is old. Does the secret reach the context window? Pick the door by that answer, not by the vendor's logo.
Odds are your secrets vendor shipped an agent kit in the last two months, and it handed your coding agent two ways to fetch a credential that behave nothing alike.
Keeper Security shipped Agent Kit on April 30, 2026, per its press release out of Chicago. It packages Keeper Secrets Manager and Keeper Commander as skills for Claude Code, Cursor, Codex, and GitHub Copilot, released on GitHub under Apache 2.0. 1Password got there in March with SDKs and runtime retrieval for agents. Doppler, Bitwarden, and CyberArk each have their own version. The category has a name now. It is the agent kit, and the pitch repeats the line Keeper used: the agent resolves secrets at runtime "without ever seeing the raw credential."
That line holds for one of the two mechanisms these kits ship. It is a hope for the other. The job is telling them apart before you wire one into an agent that runs unattended.
Two front doors, one vault
Strip the branding off any 2026 agent kit and you find one of two delivery mechanisms, sometimes both behind the same install.
The first is exec injection. The agent runs a wrapper command. The wrapper authenticates to the vault, resolves the named secrets, and places them in the environment of a child process at exec time. The model produces the command string. It never receives the secret value back. Keeper's ksm exec, 1Password's op run, and Doppler's doppler run all work this way.
# The agent emits this. The shell runs it. The model sees the command, not the value.
ksm exec -- ./migrate.sh
op run --env-file=.env.tpl -- npm run deploy
doppler run -- pytest tests/integration
The second is MCP retrieval. The agent calls a tool on a Model Context Protocol server. The server resolves the secret and returns a response over the protocol. Keeper ships its MCP server in Docker and Node configurations for hosted or orchestrated setups, where a wrapper process on the agent's own shell is not an option.
// The agent calls a tool. What comes back decides everything.
{ "method": "tools/call",
"params": { "name": "get_secret", "arguments": { "ref": "prod/DB_PASSWORD" } } }
In a thirty-second demo these look the same. You ask the agent to run a migration, it runs, no key appears in the chat. The difference shows up in the failure modes, and the failure modes are where credentials leak.
Where the secret ends up
A coding agent has a context window. Everything the model reads or writes during a task passes through it: your prompt, the files it opens, the commands it runs, and the output of those commands. That window gets serialized to your provider's servers on every turn. It lands in request logs. It can be retained, sampled for evaluation, and under some plan tiers used to improve models. The whole point of a secrets front end is to keep credentials out of that pipe.
Exec injection keeps them out by construction. The model writes op run -- ./deploy.sh. After the process forks, op resolves the value of STRIPE_SECRET_KEY into the child's environment, and deploy.sh reads it from there. The model never holds it, so it cannot copy it into a follow-up command or paste it into a file it writes later. The credential and the context window stay in separate worlds.
MCP retrieval depends on what the tool returns. If the get_secret tool returns {"value": "sk_live_..."}, that string is a tool result, which means it sits in the context window. The model reads it. On the next turn the full transcript, key included, goes back to the provider. The agent might then write the key into a config file because that is what you asked it to do with the secret it now holds. You have rebuilt the exposure the kit was sold to prevent, with an extra hop and a vault logo on it.
A careful MCP server does not return the value. It either performs the privileged action itself (open the database connection, sign the request) and returns a handle, or it injects the secret into a sandboxed process and returns the process result. Keeper's stated design goal is the former, resolving the credential without the raw value reaching the model. The trouble is that the obvious, easiest-to-build MCP tool is get_secret -> value, and many community MCP servers ship that. The protocol does not stop a tool from returning a plaintext key. The MCP specification defines the transport and the tool-call shape. It says nothing about what belongs in a tool result, because it cannot know.
So the question to ask a kit is narrow and answerable: when the agent asks for a secret, does the plaintext value cross the protocol boundary back into the model? Exec injection answers no by construction. MCP retrieval answers "it depends on this specific tool," and you have to go read the tool.
A failure that looks like success
The dangerous case is not the obvious one. No vendor ships an agent that prints STRIPE_SECRET_KEY=sk_live_... to the chat and calls it secure. The dangerous case is the MCP server that returns the value without flagging it, the agent that uses it as intended, and the task that finishes green. No error, no warning, no key visible in the rendered chat UI. The leak is in the serialized transcript, not the screen.
Here is how it plays out. You ask the agent to point the staging app at the production read replica for a one-off backfill. The agent calls get_secret for the replica password. The MCP server returns the value. The agent writes a correct connection string into a temp config, runs the backfill, and reports success. The task looks clean. The credential rode through the model's context on three turns, got posted to your provider on each one, and sits in request logs under whatever retention your plan carries. The chat UI showed none of it because the model had no reason to echo the value. You did not see a leak. You had one.
This is why "no key appeared in chat" is not a test. The render is not the record. The record is the transcript the provider received, and you have to inspect that, not the pretty output.
The mechanisms side by side
| Mechanism | Plaintext reaches model context? | Audit granularity | Scope after delivery | Where it breaks |
|---|---|---|---|---|
Exec wrapper (ksm exec, op run, doppler run) |
No | Per command invocation | The child process and everything it forks, until exit | Env var is readable by every subprocess for the process lifetime |
| MCP tool returning the value | Yes | Per tool call | Wherever the model copies it next | Value sits in the transcript, provider logs, and any retry |
| MCP server that acts or injects, returns a reference | No | Per tool call and per brokered action | The server's action only | You depend on the server's own process isolation |
Process-tree broker (hasp run, CyberArk Secretless) |
No | Per process grant | The launched process tree, revoked on exit | No cross-host policy without a central service |
The table has one column that decides most stacks: the second one. If a mechanism can put a live key into the model's context, treat it as if it will, because over a few thousand agent turns it will. Everything else is a tradeoff you can manage. That one is a leak you cannot.
Audit granularity and blast radius
The exec wrapper logs a coarse event. Keeper, 1Password, and Doppler all record that a session resolved a named secret at a timestamp, tied to the authenticated identity. That is real audit, governed by the same role-based controls a human gets, which is the genuine improvement these kits deliver over a flat .env file. What it does not tell you is what the child process did with the secret afterward. Once the value is in the environment, the wrapper is blind. Any subprocess deploy.sh spawns can read STRIPE_SECRET_KEY for as long as the parent lives.
MCP retrieval logs finer. Each tools/call is a discrete, attributable event, and a broker-style server can log the action it took rather than the access it granted. The price is the exposure risk above. You buy per-call audit and you may pay with a key in the context window. That is a bad trade for most coding-agent work, where you want the credential nowhere near the model and you are content to audit at the command level.
Blast radius follows the same line. The exec wrapper scopes a secret to one process tree, and the secret dies when that tree exits. A broker that watches the process tree, like CyberArk's Secretless or a local process-tree broker, tightens this by revoking the grant on exit rather than waiting for the OS to reap the environment. An MCP server that returns values has no scope at all once the value is loose. The model carries it for the rest of the task and into every provider round trip.
How to tell which door your kit uses
You do not have to trust the marketing. Read three things.
First, the tool list. If the kit's MCP server exposes a tool whose output is a secret value, that is your answer. Look for tool names like get_secret, read_credential, or fetch_key, then read the tool's declared output. A tool that returns { "value": ... } puts plaintext in context. A tool named run_with_secret or inject that returns an exit code and stdout does not.
Second, the skill definition. Keeper's keeper-secrets skill drives ksm exec and templates, which is the exec-injection path. Check what the skill invokes:
# Trace it. Does the skill resolve into your shell, or hand a value to the model?
# Exec injection looks like this in the skill's commands:
ksm exec --config "$KSM_CONFIG" -- "$@"
# Retrieval-into-context looks like this, and is the pattern to avoid for keys:
ksm secret get --uid abc123 --field password # prints the value to stdout the model reads
The same vendor ships both. ksm exec keeps the secret out of the model. ksm secret get prints it to stdout, and if the agent runs that, the value is in the transcript. Audit your skill files for the second pattern.
Third, run it once and watch the transcript. Give the agent a task that needs a real secret, let it run, then read the full conversation log including tool results. Search the log for the first few characters of the key. If you find it, the kit is feeding plaintext to the model regardless of what the data sheet claims. This is a ten-minute test and it settles the argument.
What this means for your stack
Default your coding agents to the exec wrapper. ksm exec, op run, and doppler run resolve the credential into a child process and never return it to the model, which is the property you want from an agent kit. Use the MCP path only when you have read the server and confirmed it brokers the action rather than returning the key, and only when a wrapper on the agent's own shell is not available.
The pattern underneath the right choice is runtime brokering scoped to a process. A broker resolves the secret at the moment of use, delivers it to the process that needs it, keeps it off every other surface, and revokes it when the process exits. The vendor's logo on the front door matters less than whether the door opens onto the model's context window or away from it. Ask that question of every kit before you let an unattended agent hold the keys.
hasp is one working implementation of the process-scoped pattern. hasp run -- your-agent injects resolved secrets into the launched process tree and revokes them on exit, with no value crossing into the model. Source-available (FCL-1.0), local-first, macOS and Linux, no account.
Whatever you run, the test is the same one you can do today. Start a real task, let the agent fetch a secret, and grep the transcript for the key. If it is there, the kit failed at the only job that counts, and the brand on it does not change that.
Sources· cited above, in one place
Stop handing the agent your real keys.
hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.
- Local, encrypted vault — no account, no cloud, no telemetry by default.
- Brokered run — agent gets a reference, the child process gets the value.
- Pre-commit + pre-push hooks catch managed values before they ship.
- Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.