Hand API credentials to a coding agent without pasting them into the chat transcript.
Project description
CredVault
Hand API credentials to a coding agent without pasting them into the chat.
When Claude Code (or Cursor, or any agent) says "paste your Stripe key and I'll
wire it up", the key you paste is now in the conversation transcript, and in
whatever that transcript gets written to. CredVault replaces that moment. The
agent asks for a key by name; a small desktop window opens; you type the value
there; it goes straight into your project's .env. The agent gets back a list of
key names and never sees a value.
agent: credvault request --key STRIPE_SECRET_KEY --wait
│
▼
┌─────────────────────────────┐
│ CredVault │ ← you type the value here
│ STRIPE_SECRET_KEY [•••••] │
│ Will write to: ./.env │
│ [ Inject ] │
└─────────────────────────────┘
│
▼
agent: {"status":"injected","keys":["STRIPE_SECRET_KEY"]}
It also solves a smaller problem that bites harder than it should: it guarantees
the file it wrote is ignored by the nearest enclosing git repo, walking up to
find it. If your home directory is itself a repo, a .gitignore dropped next to
the .env would have done nothing.
Install
uv tool install credvault # recommended
pipx install credvault
pip install credvault
Pure standard library, no dependencies. Needs Python 3.9+ with tkinter
(bundled on Windows and macOS; apt install python3-tk on Debian/Ubuntu).
Use it with Claude Code
This repo is also a Claude Code plugin. Installing it teaches Claude the rule "never ask for a secret in chat, ask CredVault", and it will do the rest on its own.
/plugin marketplace add bigtownxyz/credvault
/plugin install credvault
For other agents, point them at skills/credvault/SKILL.md
or paste its contents into your AGENTS.md.
Use it by hand
credvault
Pick a project folder, choose .env / .env.local / a custom name, type your
key/value pairs, and hit Inject. Values are masked with a per-row show/hide.
The CLI
# Ask for credentials and block until the human deals with it.
credvault request --folder . --file .env.local \
--key X_API_KEY --key X_API_SECRET \
--hint "X_API_KEY=developer.x.com > Keys and tokens" \
--note "For the tweet poster" --wait
# Is anything waiting?
credvault status
request writes the request, opens the GUI if it is not already running, and
with --wait blocks until you inject or dismiss. It prints a keys-only ack and
exits 0 injected, 2 dismissed, 3 timed out.
Threat model
Read this before you trust the tool with anything.
What CredVault protects against
- Your credential being written into an agent's chat transcript, and from there into logs, telemetry, context windows, and anything the transcript is later fed to.
- Committing the
.envyou just created, because it gitignores it against the correct repo. - A newly created
.envbeing world-readable: it is created0600from the moment it exists, not chmod-ed afterwards.
What CredVault does not protect against, at all
- The agent reading the
.envafterwards. It can.cat .envworks fine. CredVault keeps the secret out of the transcript, not out of the agent's reach. The bundled skill instructs Claude not to read the file, and that is an instruction, not an enforcement boundary. If you need the agent to be unable to read a credential, do not put that credential on the machine the agent runs on. - A malicious or prompt-injected agent, which can read the file, exfiltrate it, or run anything else you could run.
- Any other process running as you. The request inbox is a plain directory.
- Secrets already in your shell history, your clipboard, or an existing
.env.
Trust boundary on the request channel
Any process that can write to the request inbox can put text in the CredVault banner and pre-select a target folder. Notes are treated as untrusted input: truncated, stripped of control characters, and shown quoted and attributed to the requester. Always read the "Will write to:" path before hitting Inject. That line is the thing standing between you and a request that quietly aimed at someone else's directory.
Nothing secret is stored by CredVault. There is no vault file, despite the
name. Credential values exist in the app only while the window is open, and are
written to exactly one place: the .env you chose. The state directory holds
pending requests (key names and notes), keys-only acks, and a list of recently
used folders.
How values are written
There is no single .env specification and the dialects disagree, so CredVault
picks the encoding each value is safest in:
| Value | Written as | Why |
|---|---|---|
sk-abc123 |
KEY=sk-abc123 |
Safe characters only, no quoting needed |
hunter two |
KEY='hunter two' |
Single quotes are literal everywhere |
abc#def |
KEY='abc#def' |
Bare # starts a comment in most parsers |
cost$5 |
KEY='cost$5' |
Bare $ gets interpolated |
-----BEGIN…\nline2 |
KEY="-----BEGIN…\nline2" |
Newlines escaped; double quotes are the only form that survives |
it's |
KEY="it's" |
Contains a single quote |
Verified by round-tripping through python-dotenv. A multi-line PEM key written
naively as KEY=<value> parses as several bogus keys with the real one
truncated, which is exactly the bug this replaces.
Existing comments, key order and blank lines survive an upsert, and an export
prefix is preserved.
Where state lives
| OS | Path |
|---|---|
| Windows | %APPDATA%\CredVault\ |
| macOS | ~/Library/Application Support/CredVault/ |
| Linux | $XDG_DATA_HOME/credvault/ or ~/.local/share/credvault/ |
Override with CREDVAULT_HOME. If that directory happens to sit inside a git
repo, CredVault gitignores it.
The request format
The CLI is the supported interface. If you must write a request by hand, drop a
JSON file into <state dir>/requests/ using tmp-then-rename so the poller never
reads a half-written file:
{
"note": "X API v2 access for the poster",
"target_folder": "/home/u/proj",
"target_file": ".env.local",
"fields": [
{ "key": "X_API_KEY", "hint": "developer.x.com > Keys and tokens" },
"X_API_SECRET"
]
}
fields is required; each item is a key name or {key, hint}. target_file
must be a bare filename. The ack lands in <state dir>/processed/ as
<ts>-<request>.result.json.
Development
pip install -e ".[dev]"
pytest
ruff check .
core.py is pure and holds the env parsing, quoting and gitignore logic.
ipc.py is the request channel. gui.py is the only module that imports
tkinter, so the CLI and the tests run headless.
A standalone executable
Not needed if you installed from PyPI. If you want a double-clickable app with no Python on the target machine:
pip install pyinstaller
pyinstaller --onefile --noconsole --name CredVault \
--paths src --distpath dist --workpath build --specpath build \
src/credvault/gui.py
Kill any running CredVault first: it holds a lock on the output file.
The executable attached to each GitHub Release is built this way by CI. It is not code-signed, so Windows SmartScreen will warn on first run. If that bothers you, and it reasonably might for a tool you are about to type secrets into, install from PyPI instead and read the source.
Cutting a release
Publishing uses PyPI Trusted Publishing,
so no API token is stored in this repo. One-time setup on PyPI: add a pending
publisher for owner bigtownxyz, repo credvault, workflow release.yml,
environment pypi.
Then, per release: bump version in pyproject.toml and __init__.py, tag it,
and publish a GitHub Release. CI runs the tests on three OSes, builds the wheel,
publishes to PyPI, and attaches a Windows exe.
Licence
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file credvault-0.1.0.tar.gz.
File metadata
- Download URL: credvault-0.1.0.tar.gz
- Upload date:
- Size: 25.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab2284ca0b1ff482c255104adeb4273e080d18224e77e9aef8cd8a619a6507dd
|
|
| MD5 |
dc14ed651cda6cd465cf70eb3b4d7920
|
|
| BLAKE2b-256 |
2f8e0f4d0a9f0425a33cc9696a0732c68a0ffd12af84fb7574f432f6f6404b40
|
Provenance
The following attestation bundles were made for credvault-0.1.0.tar.gz:
Publisher:
release.yml on bigtownxyz/credvault
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
credvault-0.1.0.tar.gz -
Subject digest:
ab2284ca0b1ff482c255104adeb4273e080d18224e77e9aef8cd8a619a6507dd - Sigstore transparency entry: 2137097036
- Sigstore integration time:
-
Permalink:
bigtownxyz/credvault@0398843e126246cacbc3fdef9302b7837d074037 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bigtownxyz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0398843e126246cacbc3fdef9302b7837d074037 -
Trigger Event:
release
-
Statement type:
File details
Details for the file credvault-0.1.0-py3-none-any.whl.
File metadata
- Download URL: credvault-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2bd2c13f8677bff81b9d2e642f160c34551f61131207a40953ed1f77bc82fff6
|
|
| MD5 |
8eedaadbdb7b98bb2af1b77ab37780e6
|
|
| BLAKE2b-256 |
3e9d0c2180037202e2d74ac9e59cd4bddbe0ea0b39f51fc8c337a80f8ff8fda7
|
Provenance
The following attestation bundles were made for credvault-0.1.0-py3-none-any.whl:
Publisher:
release.yml on bigtownxyz/credvault
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
credvault-0.1.0-py3-none-any.whl -
Subject digest:
2bd2c13f8677bff81b9d2e642f160c34551f61131207a40953ed1f77bc82fff6 - Sigstore transparency entry: 2137097082
- Sigstore integration time:
-
Permalink:
bigtownxyz/credvault@0398843e126246cacbc3fdef9302b7837d074037 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bigtownxyz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0398843e126246cacbc3fdef9302b7837d074037 -
Trigger Event:
release
-
Statement type: