[FEATURE] Built-in secrets management with optional third-party integrations
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Claude Code has no first-class way to handle secrets. The current reality is that users paste API keys, database URLs, and tokens directly into the chat to get things working. For experienced engineers, this is a bad habit. For the growing wave of non-engineers using Claude Code to build, it's the only workflow they know.
I lead engineering at a startup where non-technical team members use Claude Code daily. Secrets management is our single biggest bottleneck to safe, autonomous building. When a non-engineer needs to connect to an API, the natural path is: paste the key into chat → Claude writes it into a .env file or a command → the key is now persisted in conversation history, potentially in plaintext on disk, and visible to anyone with access to the account.
Multiply this across every Claude Code user. Thousands of API keys, database credentials, and service tokens are likely being pasted into Claude conversations every day, turning Claude accounts into an aggregated honeypot of sensitive credentials. This is a ticking security incident.
Workarounds like Doppler or 1Password CLI exist, but they require CLI setup, environment configuration, and shell knowledge that non-engineers don't have. And non-engineers are exactly the population Claude Code is rapidly onboarding.
Proposed Solution
A built-in secrets manager with optional third-party integrations, designed around the principle that secrets should never appear in the chat context.
Core design
- Secret input via dedicated UI, not chat. When Claude needs a secret (or the user wants to store one), a secure input form appears in the terminal/UI. The value goes directly into the secrets store and never into the conversation transcript.
- Project-level secret references. A
.claude/secrets.json(or similar) file in the project directory maps logical names to secret IDs:
``json`
{
"DATABASE_URL": {
"source": "built-in",
"id": "db-url-prod"
},
"OPENAI_API_KEY": {
"source": "doppler",
"id": "OPENAI_API_KEY",
"metadata": {
"project": "my-app",
"config": "dev"
}
},
"STRIPE_SK": {
"source": "1password",
"id": "op://Vault/Stripe/secret-key",
"metadata": {
"account": "my-team.1password.com"
}
}
}
metadata` object carries provider-specific configuration (Doppler project/config, 1Password account, AWS region, etc.) so the integration knows how to resolve the secret. This file is safe to commit since it contains references and routing info, not values.
The
- Runtime injection via shell wrapper, not context. When Claude executes a command that needs secrets, they're injected at the process level (like
doppler run --orop run --), never read into the chat context. Claude sees the name of the secret, never the value.
- Pluggable backends. Ship with a simple built-in encrypted store for getting started, plus integrations for:
- Doppler
- 1Password CLI
- AWS Secrets Manager
- GCP Secret Manager
.envfiles (read-only, for existing projects)
Example workflow
User: "Connect to our Postgres database and show me the schema"
Claude: "I need a DATABASE_URL. Please enter it securely."
→ [Secure input form appears, user pastes the connection string]
→ [Stored as secret ref "db-url-prod"]
Claude: runs `claude-secrets run -- psql $DATABASE_URL -c '\dt'`
→ [Secret injected at process level, never in transcript]
On subsequent sessions, Claude reads .claude/secrets.json, sees the secret exists, and uses it. No re-entry, no chat exposure.
Why this matters now
Claude Code is becoming a platform for non-engineers to build software. That's an incredible unlock, but it means security can't depend on users knowing best practices. The tool itself needs to make the safe path the easy path. This feature would:
- Eliminate the most common secret exposure vector in Claude Code today
- Unblock non-engineers from safely connecting to APIs and services
- Enable teams to share secret references (not values) via version control
- Align with the existing permission model. If Claude Code already gates file writes and shell commands, secrets deserve at least the same care.
---
To be clear: this is just one component of a secure coding environment. It's still up to each organization to grant access to secrets responsibly, deciding who gets access to what, rotating credentials, scoping permissions appropriately. That responsibility doesn't change. But right now, even when a company does everything right on their end, the last mile is a non-engineer staring at a Claude Code prompt, API key on their clipboard, making a judgment call about how to provide it. The tooling should make that moment safe by default.
Happy to help spec this further. This is the single highest-leverage security improvement I can think of for the Claude Code ecosystem.
Alternative Solutions
Several issues have raised adjacent concerns, but none propose a complete secrets management workflow with third-party integration and a non-engineer-friendly input path:
- #2695: Zero-Trust Architecture for Environment Variable Security. Proposes pattern-based secret detection and placeholder replacement. Focuses on preventing accidental exposure of secrets already on disk. Doesn't address the input problem (how secrets get there in the first place).
- #25053: Mark sensitive env vars and file paths. Proposes a
sensitiveconfig to redact known env var patterns from Claude's context. Defensive and protective, and a great complement to this proposal, but doesn't solve secret provisioning or third-party integration. - #23642: Support 1Password
op://references insettings.jsonenv section. Closest in spirit, but scoped narrowly to 1Password and to theenvconfig. Doesn't address runtime injection for arbitrary commands, project-level secret references, or a built-in fallback for users without a third-party manager. - #4160:
.claudeignorefor secret files. Focuses on preventing Claude from reading sensitive files. Important but orthogonal. This proposal is about giving Claude a safe way to use secrets at runtime without ever seeing them. - #28942:
envFilesupport in.mcp.json. Solves the specific pain of MCP server auth tokens. This proposal generalizes that pattern to all secret usage across any command Claude executes.
This proposal aims to unify these concerns under a single, coherent secrets management system, particularly one that works for non-engineers who don't have Doppler, 1Password, or .env workflows already set up.
Priority
High - Significant impact on productivity
Feature Category
Interactive mode (TUI)
Use Case Example
A non-engineer on our team is building an internal tool with Claude Code that needs to pull data from our CRM's API. They've been given an API key by their manager. Here's what happens today vs. what this feature would enable:
Today (unsafe):
User: "Here's my API key for the CRM: sk-live-abc123xyz. Can you use it to pull our contact list?"
Claude: "Sure, I'll use that key to..."
The key is now in the conversation transcript. It's in Claude's context. If the user later exports or shares this conversation, the key goes with it. If they hit an auth error and paste the key again in a different session, now it's in two transcripts. The user has no idea this is a problem.
With this feature (safe):
User: "I need to connect to our CRM API. I have an API key."
Claude: "I'll need that key stored securely. Please enter it here."
→ [Secure input prompt appears. User pastes sk-live-abc123xyz. Value stored as "crm-api-key", never enters chat.]
→ [.claude/secrets.json updated: { "CRM_API_KEY": { "source": "built-in", "id": "crm-api-key" } }]
Claude: runs `claude-secrets run -- node fetch-contacts.js`
→ [CRM_API_KEY injected as env var at the process level. Claude sees the variable name, never the value.]
Next week, the user opens a new session to add a feature. Claude reads .claude/secrets.json, sees CRM_API_KEY is already stored, and uses it without asking. No re-entry, no exposure. If the key gets rotated, the user updates it through the same secure input prompt.
The user never had to learn about .env files, Doppler, or environment variables. They just answered a prompt.
Additional Context
_No response_
Showing cached comments. Read the full discussion on GitHub ↗
14 Comments
Workaround today: Combination of hooks + CLAUDE.md to prevent secret exposure
While waiting for native secrets management, you can build a safety net with hooks:
1. PreToolUse hook to block secrets from being written to files:
2. CLAUDE.md rule for the team:
3. For non-engineers (the simplest path):
Add a helper script they can run:
Then in
.bashrc:source ~/.env.local 2>/dev/nullThe hook prevents the "paste key into chat → write to code" pattern even when non-engineers don't know better.
This still stores the secrets in plain text in
.env.localpreventing Claude from leaking secrets is a different problem from storing them securely (More or less) in the first place.Actually that is not at all what I am suggesting here. The secrets would be stored in an encrypted vault file (like other password managers). The secrets.json file would just contain references to secrets ids for lookup.
It looks like they are still hitting a file though? Best to just have your script define the env vars directly and
sourceit rather then execute so it can effect the current shell session.Great proposal @brianbowden, and @segevfiner raised the key point: preventing leaks is a different problem from secure storage. You need both.
I was already building an MCP server for this when Claude wiped around 25K docs from my Firebase project because it had access to production credentials. Thankfully it was my own side project, but that incident made me stop questioning the idea and just focus on finishing it.
What I built:
Setup: claude mcp add securecode -- npx -y @securecode/mcp-server
It's live at securecodehq.com with a free tier.
I'm looking for early users to try it and help me improve it. If you're dealing with this problem, I'd love your feedback.
You can build basic secrets management with hooks today:
Three layers of protection:
I don't want to build a hack around it,... And I don't want to store secrets in env vars like this to begin with...
The chances of correctly blocking the agent from all possible ways of reading secrets from env vars are slim to put it mildly when you let it execute arbitrary shell commands and not the point here to begin with, its about storing them securely locally to begin with...
For the VS Code extension specifically, ${input:id} + SecretStorage API could be a pragmatic first step toward the broader secrets management described here - small surface area, fully backward-compatible, and reuses a pattern VS Code developers already know from tasks.json and launch.json.
Detailed implementation proposal in #44158. Ready to submit a PR if the team is aligned.
A hook compatible with Claude was just added on Lade.
Secrets management built into Claude Code seems like a reasonable feature, but especially until a first-party feature exists (and thus Anthropic is on the hook to do whatever is necessary to manage those secrets correctly and prevent disclosure), I wanted strict isolation between Claude and my secrets. Leaving this note here in case this approach is useful to others looking into the same thing who might feel similarly.
My solution was to only run Claude inside a devcontainer, use SOPS along with Age to encrypt all secrets, but keep the private key completely out of the devcontainer. My use case is primarily for personal and homelab use where I am prioritizing simplicity and minimizing maintenance, so adjust as appropriate for your environment and preferred tools. In my case I used 1Password to hold the private key for Age since I already use 1Password heavily for SSH keys, and I like that I get an explicit UI prompt to approve each usage, but there are many different approaches possible here.
Specifically, I set SOPS_AGE_SSH_PRIVATE_KEY_CMD through direnv(/.envrc) to read from the 1Password vault, and have SOPS configured to encrypt against that key by default through creation_rules in .sops.yaml. The devcontainer and Claude is thus completely unable to even request the private key to decrypt secrets; the 1Password CLI isn't even installed inside the devcontainer, and AFAIK would not have the host connections to work even if it was installed.
I can give more details if anyone is interested, and certainly you might need to adapt this approach. For example, I'm currently doing this for Ansible playbooks, so I set up my project to support separate venvs for host and devcontainer and just run my playbooks on the host. If you have a more complex devcontainer setup or only having secrets access when on the host is not ideal, you might want to instead create variants of the devcontainer; one for development that has Claude but no secrets access, and one for production/deployment that excludes Claude but gets the key needed to decrypt SOPS secrets.
I really need something like this. The user experience for secrets is nonexistant. Adding hooks and such to make claude resistant to storing secrets is not the same as having a way users can add secrets to claude and have him use them to log into things and do things an a repeatable way. Without secret management, pretty much every enterprise use case won't pass a security inspection.
Strong +1 on the core principle — secrets entering the chat context is exactly the
thing to eliminate, and the dedicated-UI / name-reference / value-blind model you
describe is spot on.
I want to add a separate, complementary implementation of the same principle,
scoped to authoring and propagating secret files rather than consuming them at
runtime. Where your proposal centers on using a secret (
claude-secrets run --,process-level injection), this covers getting values into
.envand CI safely in thefirst place. Same value-blind guarantee, different surface — and deliberately a
different implementation so it can ship on its own.
Concretely, two MCP tools routed through the harness so their inputs never reach
the model:
manage_env— create, write, overwrite, and sync.envfiles.each value). The user enters the actual values via a dedicated masked input surface.
Values are written straight to
.env; only a masked confirmation returns to themodel.
.env.example: rebuild.envusing.env.exampleas canonical. Theharness (not the model) reads the existing
.envand carries forward values forkeys that still exist. Keys in the old
.envbut not in.env.exampleare kept andannotated (
# (not in example)). New keys with no value are collected via the maskedsurface. The model receives only a structural report (counts and key names, never
values).
push_git_secrets— register selected keys as GitHub Actions secrets.excluding local-only values. Values are piped into
gh secret set(which encryptslocally before sending to GitHub), bypassing the model entirely. Only registered key
names return.
These are also relatively cheap to ship: the one genuinely new piece is the
value-blind input surface itself — a masked entry UI whose input is routed through the
harness so it never reaches the model context, transcript, or logs. That part does need
to be built. But everything downstream of it is existing machinery:
manage_envisfile writes,
push_git_secretsjust wrapsgh secret set(which already handles localencryption and transport), and
.env.examplesync is a pure text/structure operationwith zero external dependencies. No backend plugins, no
.claude/secrets.jsonreferenceformat, no runtime-injection wrapper. So once the input surface exists, these tools sit
on top of it as a small, self-contained unit that can land well ahead of the full
pluggable-backend system.
<img width="1443" height="831" alt="Image" src="https://github.com/user-attachments/assets/998b8150-1a0d-4241-a522-268b5bfdc72f" />
This UX is already validated in production — Manus surfaces an
inline input card where the user enters the secret directly; in the conversation it
then appears masked (
••••) next to its key name, while the value remains viewable andmanageable in settings. Secrets are kept centrally and referenced by name in later
steps, with the value never transiting the model.
(Manus describes its secrets handling in its
third-party integrations docs,
though that page documents the general model rather than this specific input-card flow,
which is observed in the product itself.)
+1, and I think there are really two groups here with different needs.
Engineers will set up a proper secrets platform. Powerful, but a fair amount of work to wire up. Non-engineers using agents (wiring up PostHog, filing Linear issues, configuring Stripe) usually just paste the key into chat or drop it in a .env, because the existing tools assume DevOps knowledge they don't have.
That second group is the real gap. Whatever Claude Code ends up building in here, the thing that would actually help them is low friction by default: the agent gets scoped access to the one service it needs, you can see what it used, you have an overview of secrets grants, and you never have to learn a technical secrets platform.
Disclosure: that is the niche I am building for (ScopeHold), so I am biased. Happy to share what we have learned about the non-engineer flow either way.