Skip to main content

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 .env you just created, because it gitignores it against the correct repo.
  • A newly created .env being world-readable: it is created 0600 from the moment it exists, not chmod-ed afterwards.

What CredVault does not protect against, at all

  • The agent reading the .env afterwards. It can. cat .env works 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

credvault-0.1.0.tar.gz (25.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

credvault-0.1.0-py3-none-any.whl (21.5 kB view details)

Uploaded Python 3

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

Hashes for credvault-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ab2284ca0b1ff482c255104adeb4273e080d18224e77e9aef8cd8a619a6507dd
MD5 dc14ed651cda6cd465cf70eb3b4d7920
BLAKE2b-256 2f8e0f4d0a9f0425a33cc9696a0732c68a0ffd12af84fb7574f432f6f6404b40

See more details on using hashes here.

Provenance

The following attestation bundles were made for credvault-0.1.0.tar.gz:

Publisher: release.yml on bigtownxyz/credvault

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for credvault-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2bd2c13f8677bff81b9d2e642f160c34551f61131207a40953ed1f77bc82fff6
MD5 8eedaadbdb7b98bb2af1b77ab37780e6
BLAKE2b-256 3e9d0c2180037202e2d74ac9e59cd4bddbe0ea0b39f51fc8c337a80f8ff8fda7

See more details on using hashes here.

Provenance

The following attestation bundles were made for credvault-0.1.0-py3-none-any.whl:

Publisher: release.yml on bigtownxyz/credvault

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page