Skip to content

feat: Vault System, let the AI use API keys without ever reading them - #14

Open
afu-it wants to merge 1 commit into
Kiyoraka:mainfrom
afu-it:feat/vault-system
Open

feat: Vault System, let the AI use API keys without ever reading them#14
afu-it wants to merge 1 commit into
Kiyoraka:mainfrom
afu-it:feat/vault-system

Conversation

@afu-it

@afu-it afu-it commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

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 .env file 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

You:   here is my key sk-proj-hT8vQ... put it in the script
Agent: done, I wrote it to config.py

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

You:   $ vault add OPENAI_API_KEY          (your terminal, hidden input)
You:   use the OpenAI key and run the script
Agent: $ vault run --secret OPENAI_API_KEY -- python3 script.py
       done, 40 rows processed

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

Feature/Vault-System/
├── README.md                  what it is, and what it deliberately does not do
├── 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.

Cross-platform

The backend is picked at runtime, so one file works everywhere.

Platform Key storage Biometric unlock
macOS Keychain Touch ID
Windows DPAPI, bound to the user account Windows Hello
Linux libsecret (GNOME Keyring, KWallet) fprintd
fallback file with owner-only permissions, and the CLI says so out loud none

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 run into something that only prints, and cat-ing a .env file.

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-biometrics undoes 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

@afu-it
afu-it force-pushed the feat/vault-system branch 2 times, most recently from fda8518 to c05a105 Compare August 11, 2026 08:31
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
afu-it force-pushed the feat/vault-system branch from c05a105 to bfefe9c Compare August 11, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant