feat: Vault System, let the AI use API keys without ever reading them - #14
Open
afu-it wants to merge 1 commit into
Open
feat: Vault System, let the AI use API keys without ever reading them#14afu-it wants to merge 1 commit into
afu-it wants to merge 1 commit into
Conversation
afu-it
force-pushed
the
feat/vault-system
branch
2 times, most recently
from
August 11, 2026 08:31
fda8518 to
c05a105
Compare
MemoryCore keeps everything in markdown. That is the design, and it is
also why credentials have nowhere to go.
So they end up in the two places we all know are wrong. Pasted into chat,
where they stay in the transcript. Or in a .env file the agent reads out
loud the first time you ask it to debug a config problem. Both leaks are
silent, and rotating the key later does not pull it back out of a log.
Hiding the file better does not help either, because the agent runs as
you. Every file you can read, it can read.
This separates knowing a secret from using one. The agent runs
`vault run --secret NAME -- <command>` and the value reaches the child
process as an environment variable without passing through the model,
the transcript, or the session file. Same principle as sudo.
Feature/Vault-System/
├── README.md what it is, and what it deliberately is not
├── TUTORIAL.md blank slate to working setup, ~20 minutes
├── SKILL.md how the AI should behave once loaded
├── install-vault-system.md install protocol in the house style
├── allowlist.example.json
└── bin/
├── jarvis_vault.py the CLI, stdlib only
├── vault_guard.py PreToolUse hook
├── vault_catch.py UserPromptSubmit hook
└── install.py installer
Three layers, each with one job. Bitwarden holds the secrets and handles
sync and recovery. The OS keystore holds the unlock token so no key
material sits on the filesystem. An allowlist decides which program may
receive which secret, deny by default, with an audit log.
Storage and biometrics resolve at runtime, so one file covers every
platform: Keychain and Touch ID, DPAPI and Windows Hello, libsecret and
fprintd, and a permission-limited file as the honest last resort.
Secrets live in a dedicated Bitwarden folder and every read is scoped to
it by id. The rest of the account stays unreachable. Every change also
rewrites a name index inside MemoryCore, so the agent can answer "do I
have a Trello key saved?" without opening the vault at all.
Two optional Claude Code hooks come with it. One blocks the shortcuts
around the broker. The other lifts a credential out of a submitted
message and erases the message before the model receives it.
Built and tested end to end on macOS with Bitwarden CLI 2026.7.0. The
Windows and Linux paths follow documented platform APIs but are untested,
which the PR says out loud rather than implying coverage that is not there.
Python 3 standard library only, no dependency beyond the Bitwarden CLI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
afu-it
force-pushed
the
feat/vault-system
branch
from
August 11, 2026 08:43
c05a105 to
bfefe9c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
MemoryCore keeps everything in markdown. That is the design, and it is also why credentials have nowhere to go.
So they end up in the two places we all know are wrong. Pasted into chat, where they stay in the transcript. Or in a
.envfile the agent reads out loud the first time you ask it to debug a config problem.Both leaks are silent. Nothing warns you, and rotating the key later does not pull it back out of a log.
Hiding the file better does not work either, because the agent runs as you. Every file you can read, it can read.
The idea
Separate knowing a secret from using one.
The agent never needs to read an API key. It needs the key to be present when a command runs. Same principle as
sudo: you do not read the root password, you borrow its authority for one command.Before and after
Before
Four copies now exist. Transcript, provider logs, local session file, and a source file one
git add .away from being public. None of them tracked.After
One copy, in Bitwarden. The agent wrote a command that names the secret and never holds it.
Same work, same number of steps. The value stopped travelling.
What is in the PR
Three layers, each with one job. Bitwarden holds the secrets and handles sync and recovery. The OS keystore holds the unlock token so no key material sits on the filesystem. An allowlist decides which program may receive which secret, deny by default, with an audit log.
Cross-platform
The backend is picked at runtime, so one file works everywhere.
The two hooks
Optional, Claude Code specific, registered by the installer. The CLI works without them.
Guard denies the obvious ways around the broker: reading Bitwarden directly, reading the OS keystore directly, piping
runinto something that only prints, andcat-ing a.envfile.Catch intercepts a submitted message, moves any credential in it into the vault, and erases the message. The model receives nothing. It fires only on vendor-specific patterns such as
sk-ant-,ghp_,AKIA,xoxb-. Anything ambiguous passes, because blocking real work is worse than missing one catch.Testing
Built and tested end to end on macOS 15 with Bitwarden CLI 2026.7.0 and Python 3.14. Verified: store, list, run with an allowed program, refusal for a program not on the list, refusal for an unknown secret, removal, biometric unlock after a lock, guard denials, and catch on a fake key.
Windows and Linux paths follow documented platform APIs but I do not have those machines to test on. I would rather say that plainly than imply coverage I do not have. Happy to adjust if anyone can run them.
What it does not do
The README says this too, because a security tool that oversells itself is worse than none.
If you allow
python3, an agent can write a Python script that prints the secret. The allowlist is a rail, not a wall. It stops the accidental leak, the wrong command, the reflex paste. It does not stop intent.The catch hook fires after your keystroke, so anything it catches has already touched local logs and should be rotated.
Biometric unlock stores the master password in the OS keystore, the same trade the Bitwarden desktop app makes.
vault forget-biometricsundoes it.The operating system is the real boundary. Everything here is defence in depth on top of that.
Note on scope
This is the first PR to bring executable code into a repo that has been pure markdown. That is a real change in character and it is your call, not mine. The code is stdlib-only Python with no dependency beyond the Bitwarden CLI, and every file is readable top to bottom in a few minutes. If you would rather this lived as a spec that the AI implements, or as a separate add-on repo, say so and I will rework it.
🤖 Generated with Claude Code