The Awkward Moment Every Developer Knows
You're pair programming with Claude Code, and it asks:
"I need your database credentials to run this migration."
And then you do the thing. You paste your password right into the chat. π€¦
We've all been there. But here's the problem:
- That secret is now in your conversation history
- It might end up in logs or training data
- You can't easily rotate it
- There's no audit trail
What If AI Could Use Secrets Without Seeing Them?
This is exactly what I built secretctl to solve.
Instead of exposing credentials, secretctl injects them as environment variables. The AI assistant can use your secrets to run commands, but never actually sees the plaintext values.
How It Works with Claude Code
Add secretctl as an MCP server in your Claude Code config:
{
"mcpServers": {
"secretctl": {
"command": "secretctl",
"args": ["mcp-server"],
"env": {
"SECRETCTL_PASSWORD": "your-master-password"
}
}
}
}
Now Claude Code can:
β
List your available secrets
β
Run commands with injected credentials
β
See masked values (e.g., ****WXYZ)
β Never see actual plaintext values
Example: Database Migration
You: "Run the database migration using my prod-db credentials"
Claude: I'll run the migration with your credentials injected.
[Executes: secretctl run prod-db -- npm run migrate]
Migration completed successfully!
The AI executed the command with real credentials, but never saw password123 in the chat.
Why This Matters
| Traditional Approach | secretctl Approach |
|---|---|
| Paste secrets in chat | Secrets injected via env vars |
| Visible in history | Never exposed to AI |
| No audit trail | Full audit logging |
| Hard to rotate | Single source of truth |
| Risk of leakage | Zero plaintext exposure |
Multi-Field Secrets
Real credentials are complex. secretctl supports multi-field secrets with templates:
# Database credentials
secretctl set prod-db --template database
# Stores: host, port, database, username, password
# SSH configurations
secretctl set bastion --template ssh
# Stores: host, port, username, private_key
# API credentials
secretctl set stripe --template api
# Stores: api_key, api_secret, endpoint
Desktop App for Visual Management
Prefer a GUI? secretctl includes a full-featured desktop app:
- Visual secret management
- Sensitive field masking
- Audit log viewer
- Cross-platform (macOS, Windows, Linux)
Quick Start
macOS (Homebrew)
brew install forest6511/tap/secretctl
Windows (Scoop)
scoop bucket add secretctl https://github.com/forest6511/scoop-bucket
scoop install secretctl
Linux / Manual
curl -LO https://github.com/forest6511/secretctl/releases/latest/download/secretctl-linux-amd64
chmod +x secretctl-linux-amd64
sudo mv secretctl-linux-amd64 /usr/local/bin/secretctl
Then initialize:
secretctl init
secretctl set MY_API_KEY
Key Features
- π Local-first: Your secrets never leave your machine
- π€ AI-Safe Access: MCP integration without plaintext exposure
- π‘οΈ Strong encryption: AES-256-GCM + Argon2id
- π Audit logging: Track all secret access
- π¦ Single binary: No dependencies, no server required
- π₯οΈ Desktop App: Full GUI for visual management
Try It Out
GitHub: https://github.com/forest6511/secretctl
Documentation: https://forest6511.github.io/secretctl/
The project is open source (MIT license). Star it if you find it useful!
Have questions or feedback? Drop a comment below or open an issue on GitHub.


Top comments (2)
in
do I need to come up with a value I choose arbitrarily or should I use ChatGPT API key or Claude API key ?
Hi @baudouin! Great question - let me clarify:
You should use your actual API key from your AI service provider.
Where to get your API key:
How to store it:
Option 1: Interactive (prompts for value)
Option 2: Pipe (one-liner)
The key name (like
OPENAI_API_KEY) can be anything you choose - it's just an identifier. The value is your actual secret key from the provider.Once stored, you can use it with Claude Code without exposing the plaintext:
Hope this helps! Feel free to ask if you have more questions.