lockbox-mcp 0.0.0-stage → 0.1.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.
Files changed (79) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +466 -2
  3. package/SPEC.md +1652 -0
  4. package/dist/adapters/base.d.ts +76 -0
  5. package/dist/adapters/base.js +91 -0
  6. package/dist/adapters/base.js.map +1 -0
  7. package/dist/adapters/custom.d.ts +33 -0
  8. package/dist/adapters/custom.js +271 -0
  9. package/dist/adapters/custom.js.map +1 -0
  10. package/dist/adapters/registry.d.ts +29 -0
  11. package/dist/adapters/registry.js +59 -0
  12. package/dist/adapters/registry.js.map +1 -0
  13. package/dist/broker.d.ts +177 -0
  14. package/dist/broker.js +797 -0
  15. package/dist/broker.js.map +1 -0
  16. package/dist/config.d.ts +65 -0
  17. package/dist/config.js +139 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/custom/command.d.ts +20 -0
  20. package/dist/custom/command.js +101 -0
  21. package/dist/custom/command.js.map +1 -0
  22. package/dist/custom/definition.d.ts +132 -0
  23. package/dist/custom/definition.js +270 -0
  24. package/dist/custom/definition.js.map +1 -0
  25. package/dist/custom/examples.d.ts +37 -0
  26. package/dist/custom/examples.js +38 -0
  27. package/dist/custom/examples.js.map +1 -0
  28. package/dist/custom/file.d.ts +18 -0
  29. package/dist/custom/file.js +42 -0
  30. package/dist/custom/file.js.map +1 -0
  31. package/dist/custom/http.d.ts +25 -0
  32. package/dist/custom/http.js +198 -0
  33. package/dist/custom/http.js.map +1 -0
  34. package/dist/custom/localFile.d.ts +92 -0
  35. package/dist/custom/localFile.js +278 -0
  36. package/dist/custom/localFile.js.map +1 -0
  37. package/dist/custom/render.d.ts +22 -0
  38. package/dist/custom/render.js +168 -0
  39. package/dist/custom/render.js.map +1 -0
  40. package/dist/custom/store.d.ts +35 -0
  41. package/dist/custom/store.js +138 -0
  42. package/dist/custom/store.js.map +1 -0
  43. package/dist/errors.d.ts +58 -0
  44. package/dist/errors.js +65 -0
  45. package/dist/errors.js.map +1 -0
  46. package/dist/ids.d.ts +12 -0
  47. package/dist/ids.js +24 -0
  48. package/dist/ids.js.map +1 -0
  49. package/dist/index.d.ts +24 -0
  50. package/dist/index.js +150 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/logging.d.ts +50 -0
  53. package/dist/logging.js +179 -0
  54. package/dist/logging.js.map +1 -0
  55. package/dist/mcp/server.d.ts +52 -0
  56. package/dist/mcp/server.js +161 -0
  57. package/dist/mcp/server.js.map +1 -0
  58. package/dist/mcp/tools.d.ts +72 -0
  59. package/dist/mcp/tools.js +472 -0
  60. package/dist/mcp/tools.js.map +1 -0
  61. package/dist/model.d.ts +147 -0
  62. package/dist/model.js +226 -0
  63. package/dist/model.js.map +1 -0
  64. package/dist/policy.d.ts +18 -0
  65. package/dist/policy.js +81 -0
  66. package/dist/policy.js.map +1 -0
  67. package/dist/redaction.d.ts +39 -0
  68. package/dist/redaction.js +155 -0
  69. package/dist/redaction.js.map +1 -0
  70. package/dist/secretBuffer.d.ts +45 -0
  71. package/dist/secretBuffer.js +99 -0
  72. package/dist/secretBuffer.js.map +1 -0
  73. package/dist/ui/render.d.ts +67 -0
  74. package/dist/ui/render.js +572 -0
  75. package/dist/ui/render.js.map +1 -0
  76. package/dist/ui/server.d.ts +63 -0
  77. package/dist/ui/server.js +644 -0
  78. package/dist/ui/server.js.map +1 -0
  79. package/package.json +65 -4
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Eduardo Rosostolato
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,467 @@
1
- # Temporary Holding Version
1
+ # Lockbox
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Let your AI agent set up API keys without ever seeing them.**
4
+
5
+ [![CI](https://github.com/rosostolato/lockbox-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/rosostolato/lockbox-mcp/actions/workflows/ci.yml)
6
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
7
+ [![Node 20+](https://img.shields.io/badge/node-20%2B-blue.svg)](https://nodejs.org/)
8
+
9
+ You ask Claude, Cursor or any MCP client to *"put the Stripe key in our .env"* — or in Secret
10
+ Manager, Vercel, your own API. The agent describes **where** it goes. You read that in Lockbox's
11
+ own window and paste the value there. The key goes straight to the destination and never
12
+ enters the chat, the prompt or the logs.
13
+
14
+ <p align="center">
15
+ <picture>
16
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/paste-dark.png">
17
+ <img src="docs/images/paste-light.png" width="420" alt="Lockbox's window: a red PRODUCTION band, 'Paste STRIPE_SECRET_KEY', the destination 'Production env · .env.production', a masked field confirming a live key, a Continue button and a closed 'See exactly what will be sent'">
18
+ </picture>
19
+ </p>
20
+
21
+ 1. **The agent describes the destination.** A file, a program such as `gcloud`, or an HTTPS
22
+ request, with `{{secret}}` where the value goes. The tool has no field that could carry a
23
+ value.
24
+ 2. **You check and paste.** Lockbox's local window shows where the key goes in one line, warns
25
+ about anything new or odd, and checks the key's format. The exact request is one click
26
+ away.
27
+ 3. **Lockbox sends it.** The agent only learns `STORED` and a reference. It never sees the key.
28
+
29
+ ```bash
30
+ claude mcp add lockbox -e LOCKBOX_ENV_ALLOWED_ROOTS="$PWD" -- npx -y lockbox-mcp serve
31
+ ```
32
+
33
+ Then ask your agent: *"store my Stripe test key in .env"*.
34
+
35
+ ---
36
+
37
+ ## Install
38
+
39
+ Lockbox is a stdio MCP server, so you do not run it yourself — your MCP client starts it, the
40
+ same way it starts any other npm-published server. Requires Node 20+.
41
+
42
+ ### Claude Code
43
+
44
+ ```bash
45
+ cd ~/some/project
46
+ claude mcp add lockbox -e LOCKBOX_ENV_ALLOWED_ROOTS="$PWD" -- npx -y lockbox-mcp serve
47
+ ```
48
+
49
+ Add `-s project` to record it in the repository's `.mcp.json` instead of your own config.
50
+
51
+ ### Any other client (Claude Desktop, Cursor, Windsurf, VS Code, Zed…)
52
+
53
+ Drop this into the client's MCP configuration — the `mcpServers` block is the same shape
54
+ everywhere:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "lockbox": {
60
+ "command": "npx",
61
+ "args": ["-y", "lockbox-mcp", "serve"],
62
+ "env": {
63
+ "LOCKBOX_ENV_ALLOWED_ROOTS": "/absolute/path/to/your/project"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ Prefer a pinned install to an ephemeral one:
71
+
72
+ ```bash
73
+ npm install -g lockbox-mcp
74
+ # then use `lockbox-mcp serve` as the command, with no npx
75
+ ```
76
+
77
+ The package is published to npm, unscoped, as `lockbox-mcp`.
78
+
79
+ **Set `LOCKBOX_ENV_ALLOWED_ROOTS`.** File destinations refuse to write outside those
80
+ directories, and they default only to the server's working directory. Everything else is
81
+ optional — see [Configuration](#configuration).
82
+
83
+ Destinations behind a login (Google Secret Manager, Vercel, GitHub) are reached through their
84
+ own CLI, which uses the login you already have — `gcloud`, `vercel`, `gh`. See
85
+ [Destinations](#destinations).
86
+
87
+ ### First run
88
+
89
+ Ask your agent for something like *"store my Stripe test key in .env"*. Lockbox opens its
90
+ window on your machine; the agent does not receive that link. The band at the top tells you
91
+ the environment at a glance, and the field tells you whether the key looks right before you
92
+ send it.
93
+
94
+ <table>
95
+ <tr>
96
+ <td width="33%" valign="top">
97
+ <picture>
98
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/warning-dark.png">
99
+ <img src="docs/images/warning-light.png" alt="A test key pasted for a production destination: 'Looks like a test key, but this goes to production.'">
100
+ </picture>
101
+ <p><strong>Catches the wrong key.</strong> A <code>test</code> key headed for
102
+ production gets flagged in the window. The check runs locally and never shows the
103
+ value.</p>
104
+ </td>
105
+ <td width="33%" valign="top">
106
+ <picture>
107
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/confirm-dark.png">
108
+ <img src="docs/images/confirm-light.png" alt="Second step: 'Save to .env.production?' with a field where .env.production:STRIPE_SECRET_KEY has been typed">
109
+ </picture>
110
+ <p><strong>Confirms before sending.</strong> Nothing is sent until you type where it
111
+ goes: the host, the program or the file.</p>
112
+ </td>
113
+ <td width="33%" valign="top">
114
+ <picture>
115
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/saved-dark.png">
116
+ <img src="docs/images/saved-light.png" alt="'Saved' with the destination and the reference .env.production:STRIPE_SECRET_KEY">
117
+ </picture>
118
+ <p><strong>Tells the agent only "stored".</strong> Lockbox wipes the value and hands back
119
+ a reference such as the file and variable.</p>
120
+ </td>
121
+ </tr>
122
+ </table>
123
+
124
+ <kbd>Enter</kbd> continues and <kbd>Esc</kbd> cancels. The link works once: if anything
125
+ opened it before your browser did, Lockbox shows an alarm and a Cancel button instead of the
126
+ request. Lockbox's stderr carries structured audit JSON; nothing else is expected of you in the
127
+ terminal.
128
+
129
+ ---
130
+
131
+ ## What Lockbox solves
132
+
133
+ It removes an entire class of failures caused by the agent **knowing** the secret.
134
+ With Lockbox in the loop, a credential does not pass through:
135
+
136
+ - LLM prompts or conversation history
137
+ - MCP tool arguments or tool results
138
+ - agent memory or generated code
139
+ - shell command arguments or process argv
140
+ - logs, debug traces or telemetry
141
+ - URLs
142
+ - model-visible command output
143
+
144
+ ## What Lockbox does **not** solve
145
+
146
+ Lockbox does not make an AI agent trustworthy, and it is not "safe AI". It does not
147
+ guarantee that the agent picked the right destination, that it understood you, that it
148
+ is free of prompt injection, that the destination is itself secure, that your machine is
149
+ uncompromised, or that a credential cannot be misused later by software that legitimately
150
+ receives it.
151
+
152
+ There are two separate problems here:
153
+
154
+ | Question | Lockbox's answer |
155
+ | --- | --- |
156
+ | Should the agent know the secret? | No. |
157
+ | Should the agent decide alone where the secret goes? | Not without human authorization. |
158
+
159
+ Lockbox answers those two. It does not claim to answer the rest.
160
+
161
+ ---
162
+
163
+ ## Trust model
164
+
165
+ ```text
166
+ Trusted with the credential value:
167
+
168
+ The human at the keyboard
169
+ Lockbox's secure input UI (loopback only, in your control)
170
+ Lockbox's secure input broker (this process)
171
+ The destination you approved (a file, a program, an HTTPS endpoint)
172
+
173
+ NOT trusted with the credential value:
174
+
175
+ The LLM
176
+ The agent / MCP client
177
+ The conversation
178
+ The prompt and any repository content it read
179
+ Generated code
180
+ Logs, telemetry, crash reports
181
+ ```
182
+
183
+ Lockbox's runtime dependency list is one package (`zod`, for input validation). The MCP
184
+ protocol is implemented in-tree rather than through the official SDK, which would add ~90
185
+ packages — including HTTP servers and an OAuth stack — to a process that holds plaintext
186
+ credentials.
187
+
188
+ This diagram does not claim the trusted components are invulnerable. It says where the
189
+ credential is *allowed* to exist. Lockbox is security-sensitive software: if Lockbox itself is
190
+ malicious or compromised, the boundary is gone. Its source, dependencies and releases
191
+ deserve the scrutiny you would give any credential-handling tool.
192
+
193
+ ---
194
+
195
+ ## The two flows
196
+
197
+ **The secret flow** — the human's path, which the model cannot observe:
198
+
199
+ ```text
200
+ Human ─▶ Lockbox secure UI (127.0.0.1) ─▶ Broker ─▶ approved definition ─▶ Destination
201
+ ```
202
+
203
+ **The agent flow** — everything the model sees:
204
+
205
+ ```text
206
+ LLM ─▶ MCP client ─▶ Lockbox MCP server ─▶ non-sensitive result metadata
207
+ ```
208
+
209
+ The MCP tool schema has no property capable of carrying a credential. That is structural,
210
+ not a prompt instruction: there is no `value`, `secret_value`, `password`, `token`,
211
+ `content` or `raw_secret` field to abuse, closed schemas reject unknown properties, and
212
+ arguments are screened for credential-shaped values before they are parsed.
213
+
214
+ ### What the agent calls
215
+
216
+ ```json
217
+ {
218
+ "destination": "custom",
219
+ "name": "STRIPE_SECRET_KEY",
220
+ "target": {
221
+ "definition": {
222
+ "kind": "command",
223
+ "label": "Google Secret Manager",
224
+ "executable": "/opt/homebrew/bin/gcloud",
225
+ "args": ["secrets", "versions", "add", "stripe-secret-key",
226
+ "--project", "acme-prod", "--data-file=-"],
227
+ "stdin": "{{secret}}"
228
+ }
229
+ },
230
+ "environment": "production",
231
+ "description": "Stripe production API key"
232
+ }
233
+ ```
234
+
235
+ Lockbox replies with a `request_id`, a risk classification and the normalized destination —
236
+ and opens its own authorization window on your machine. The agent polls `secret_status`.
237
+ `destination: "saved:<name>"` reuses a destination the human saved earlier.
238
+
239
+ **The agent does not get the authorization link.** That link is a capability: anything
240
+ holding it can complete the human's half of the flow, and an agent with a shell or an HTTP
241
+ tool is precisely the threat model. Lockbox hands it straight to your browser. Set
242
+ `LOCKBOX_DISCLOSE_AUTHORIZATION_URL=true` if your setup needs the agent to relay the link (for
243
+ example, a remote or headless session) — and understand that this lets a compromised agent
244
+ authorize its own request.
245
+
246
+ **The link works once.** Opening it trades it for an `HttpOnly; SameSite=Lax` session cookie
247
+ scoped to that request and redirects to an address with no token in it; every later page and
248
+ form needs the cookie. A second opening of the same link gets an alarm page ("This link was
249
+ already opened") with a Cancel button, never the request. So a copy left in browser history,
250
+ a process list or a log is dead once you open it, and one used by someone else first is
251
+ visible to you.
252
+
253
+ **Logs never hold the identity phrase, and hold the link only as a last resort.** MCP hosts
254
+ save server stderr to files any process running as you can read (Claude Desktop under
255
+ `~/Library/Logs/Claude/`, Claude Code under its cache directory), so the audit record carries
256
+ a short fingerprint of the link instead. The full link is logged only when no browser could
257
+ be opened (`LOCKBOX_OPEN_BROWSER=false`, too many windows, or the opener failed), because the
258
+ operator then has no other way in.
259
+
260
+ | Tool | Purpose |
261
+ | --- | --- |
262
+ | `secret_store` | Create a credential request. Returns non-sensitive metadata and a request id. |
263
+ | `secret_status` | Poll a request. Never returns credential material. |
264
+ | `secret_cancel` | Cancel a pending request; any entered value is destroyed. |
265
+ | `secret_revise` | Invalidate an authorization and start a new one. Nothing is edited in place. |
266
+ | `secret_destinations` | The definition contract, worked examples, and the user's saved destinations. |
267
+
268
+ ### What the human sees
269
+
270
+ Stage A shows the credential name and **one destination line** — the definition's label and
271
+ its host, program or file — plus a line for anything worth a second look: a host never
272
+ used before, a host spelled with non-ASCII characters, an IP address instead of a name, a
273
+ program outside the system folders, a saved destination that changed. The full request (the
274
+ URL, every header and the body; or the program, each argument and stdin; or the file and
275
+ its content) sits behind **"See exactly what will be sent"**, closed by default, with the
276
+ canonical JSON and its hash. Every custom destination needs a second confirmation in Stage
277
+ B, after entry and before anything is sent: the human types the host, the program name or
278
+ the file (`.env:API_TOKEN` for a variable), and the server checks it. This is an attention
279
+ check, not authentication: the expected text is on the page. What keeps a second party out
280
+ of Stage B is the session cookie. The value is never displayed back.
281
+
282
+ POSTs are refused unless they come from Lockbox's own page (`Origin` and `Sec-Fetch-Site`), and
283
+ every response sets `Cross-Origin-Opener-Policy` and `Cross-Origin-Resource-Policy` to
284
+ `same-origin`. The credential field asks browser and third-party password managers not to
285
+ save it.
286
+
287
+ The page runs one inline script under a per-response CSP nonce. It describes the value's
288
+ shape (length, a `live`/`test` marker against the destination's environment, stray
289
+ whitespace), counts down the expiry and maps <kbd>Esc</kbd> to Cancel. The CSP gives it no
290
+ network access, and Cancel is a separate form, so cancelling never sends the value. Without
291
+ JavaScript the page still works, minus those three aids.
292
+
293
+ The page the human reads and the operation the executor performs are the *same immutable
294
+ object* — there is no separate "display destination". Any change to destination, project,
295
+ secret name, operation, write mode or adapter invalidates the authorization and requires a
296
+ new one.
297
+
298
+ ---
299
+
300
+ ## Destinations
301
+
302
+ There are no built-in destinations. The agent writes a **definition** — JSON with
303
+ `{{secret}}` exactly once where the value goes, and optionally `{{name}}` for the credential
304
+ name — and the human approves it as rendered. Three kinds:
305
+
306
+ | Kind | What it does | Rules |
307
+ | --- | --- | --- |
308
+ | `file` | Writes a local file. Mode `env` sets one variable in a `.env` and keeps every other line; `create` and `replace` write the whole template. | Inside `LOCKBOX_ENV_ALLOWED_ROOTS`; `.env` / `.env.<suffix>` names only by default; no symlinks; atomic `0600`; git-tracked files refused, and a new file must be git-ignored. |
309
+ | `command` | Runs a program with fixed arguments and hands it the value on **stdin**. | Absolute path, pinned by real path and SHA-256 at approval and re-checked before it runs; no shell; minimal environment; `{{secret}}` never in an argument; timeout. The agent learns only the exit status. |
310
+ | `http` | Sends one HTTPS request. | `https` only; `POST`/`PUT`/`PATCH`; `{{secret}}` only in the body or a header value, never the URL; host resolved once and pinned; private and metadata addresses refused by default; redirects not followed. The agent learns the status and an optional reference read from the response, never the response. |
311
+
312
+ ```jsonc
313
+ // A .env variable
314
+ { "kind": "file", "label": "Project .env", "path": ".env",
315
+ "template": "{{name}}={{secret}}", "mode": "env" }
316
+
317
+ // Google Secret Manager, through gcloud's own login
318
+ { "kind": "command", "label": "Google Secret Manager", "executable": "/opt/homebrew/bin/gcloud",
319
+ "args": ["secrets", "versions", "add", "stripe-secret-key", "--project", "acme-prod", "--data-file=-"],
320
+ "stdin": "{{secret}}" }
321
+
322
+ // An HTTPS API that needs no other token
323
+ { "kind": "http", "label": "Internal secrets API",
324
+ "url": "https://secrets.example.com/v1/projects/acme/secrets", "method": "POST",
325
+ "headers": { "Content-Type": "application/json" },
326
+ "body": { "type": "json", "value": { "name": "{{name}}", "value": "{{secret}}" } },
327
+ "reference": "$.id" }
328
+ ```
329
+
330
+ **Saved destinations.** At Stage B the human can tick *Save as a trusted destination* and
331
+ name it; it is saved only if the write succeeds. Next time the agent asks for
332
+ `saved:<name>`, and the window reads "Saved · name · host". Each entry is pinned to the
333
+ SHA-256 of its canonical JSON: if the file is edited, the window says *Changed since you saved
334
+ it* and reviews it as new. The store lives at `$XDG_CONFIG_HOME/lockbox/destinations.json`
335
+ (mode `0600`), written only by Lockbox; the window can forget an entry.
336
+
337
+ **What is not covered yet.** An HTTPS API that needs its own token besides the credential
338
+ (Vercel, GitHub, Doppler REST) is reached through its CLI instead: the agent cannot supply
339
+ that token, and Lockbox does not ask for two values in one request. Firestore has no CLI that
340
+ writes a single field cleanly, so it has no recipe today.
341
+
342
+ ---
343
+
344
+ ## Security assumptions and limitations
345
+
346
+ Stated plainly, because a security tool that oversells itself is worse than none:
347
+
348
+ - **The broker process sees the secret.** That is the point: something must, or storage is
349
+ impossible. The guarantee is that only the minimal trusted transport and destination
350
+ components do.
351
+ - **Memory erasure is best-effort.** `SecretBuffer` wipes the exact `Buffer` it owns, and
352
+ the HTTP body is percent-decoded into buffers Lockbox wipes too. But any conversion to a
353
+ JavaScript string — which writing a `.env` line requires — creates an immutable copy that
354
+ V8 may keep until garbage collection, and that copy cannot be wiped. Lockbox minimizes those
355
+ conversions rather than pretending they do not happen.
356
+ - **The UI is loopback HTTP.** Any process running as your user on your machine can reach
357
+ it, and any such process could also imitate it. Lockbox refuses to bind anywhere but
358
+ loopback. Request pages show a per-process identity phrase, but it is no longer logged,
359
+ so today there is no second channel to compare it against: treat it as decoration, not
360
+ as anti-spoofing. Withholding the link and making it single-use raise the bar; they do
361
+ not stop a process that wins the race to the link, reads the browser's memory, or binds
362
+ its own lookalike port.
363
+ - **An agent with an unrestricted shell is out of reach.** Running as you, it can read
364
+ files Lockbox writes (including a `.env` destination), browser state and MCP logs. No page
365
+ design fixes that. Run the agent sandboxed — for example Claude Code's sandboxed Bash,
366
+ with loopback and the MCP log directories outside what it may reach.
367
+ - **Lockbox sends where you approved, not where it is wise to.** An agent writes the
368
+ definition, so a hijacked agent can propose an attacker's endpoint. Lockbox shows the host
369
+ first, flags it when it is new or disguised, and makes you type it — but a human who
370
+ approves what they did not read has approved it.
371
+ - **A definition is only as safe as what it runs.** A `command` destination runs the
372
+ program you approved with the value on stdin; what that program then does with it is
373
+ outside Lockbox.
374
+ - **Timeouts are enforced by Lockbox.** HTTP requests and commands are cut off at
375
+ `LOCKBOX_ADAPTER_TIMEOUT_SECONDS`, and a command is killed with `SIGKILL`.
376
+ - **Preflight is best-effort.** A provider that is unreachable at preflight is reported as
377
+ unavailable rather than guessed at.
378
+ - **Crash semantics.** A crash between the provider write and the response can leave a
379
+ credential written with no local record of success. Lockbox reports the request as failed;
380
+ the destination is the source of truth.
381
+
382
+ ---
383
+
384
+ ## Local development
385
+
386
+ ```bash
387
+ git clone https://github.com/rosostolato/lockbox-mcp && cd lockbox-mcp
388
+ npm install
389
+ npm run check # typecheck + lint + format + tests
390
+ npm run build
391
+ ```
392
+
393
+ To point a client at your checkout, use `node /path/to/lockbox-mcp/dist/index.js` as the
394
+ command instead of `npx`.
395
+
396
+ ## Configuration
397
+
398
+ Configuration is read from Lockbox's own environment — never from tool arguments, so an agent
399
+ cannot relax a policy:
400
+
401
+ | Variable | Default | Meaning |
402
+ | --- | --- | --- |
403
+ | `LOCKBOX_REQUEST_TTL_SECONDS` | `300` | Request expiry. |
404
+ | `LOCKBOX_ADAPTER_TIMEOUT_SECONDS` | `30` | Upper bound on one destination write. |
405
+ | `LOCKBOX_STAGE_B_FOR_MEDIUM` | `true` | Require confirmation for medium-risk operations. |
406
+ | `LOCKBOX_UI_HOST` / `LOCKBOX_UI_PORT` | `127.0.0.1` / ephemeral | Secure UI bind address. Loopback only; anything else refuses to start. |
407
+ | `LOCKBOX_OPEN_BROWSER` | `true` | Open the authorization window automatically. |
408
+ | `LOCKBOX_DISCLOSE_AUTHORIZATION_URL` | `false` | Return the authorization link to the agent. |
409
+ | `LOCKBOX_ENV_ALLOWED_ROOTS` | current directory | Roots `file` destinations may write inside. |
410
+ | `LOCKBOX_ALLOW_GIT_TRACKED_ENV` | `false` | Permit writing into a git-tracked, or new and un-ignored, file. |
411
+ | `LOCKBOX_ENV_ALLOW_ANY_FILENAME` | `false` | Permit file names other than `.env` / `.env.<suffix>`. |
412
+ | `LOCKBOX_CUSTOM_DESTINATIONS` | `save` | `save` lets the human save approved destinations; `once` approves every request on its own. |
413
+ | `LOCKBOX_CUSTOM_KINDS` | `http,command,file` | Kinds an agent may define. |
414
+ | `LOCKBOX_CUSTOM_HTTP_HOSTS` | any | Comma-separated allowlist of HTTPS hosts; empty means any host, still reviewed. |
415
+ | `LOCKBOX_CUSTOM_HTTP_ALLOW_PRIVATE` | `false` | Permit hosts that resolve to loopback, private or link-local addresses. |
416
+ | `LOCKBOX_CUSTOM_STORE_PATH` | `$XDG_CONFIG_HOME/lockbox/destinations.json` | Where saved destinations live. |
417
+
418
+ ## Tests
419
+
420
+ ```bash
421
+ npm test # everything
422
+ npm run test:security # the adversarial suite only
423
+ npm run check # what CI runs
424
+ ```
425
+
426
+ The security suite is a product requirement, not a nicety. It contains canary-leakage
427
+ detection across every observable channel, malicious-agent tests, prompt-injection
428
+ fixtures, TOCTOU and replay tests, 100-way concurrency stress, race conditions, crash
429
+ paths (including a real `SIGKILL` mid-write), provider-failure simulation, UI checks and
430
+ fuzzing. A release is blocked if any canary leaks, any authorization bypass succeeds, any
431
+ post-approval mutation succeeds, any completed request is replayable, any secret crosses a
432
+ request boundary, any raw provider error reaches MCP, or any high-risk operation skips
433
+ confirmation.
434
+
435
+ See [`docs/SECURITY_MODEL.md`](docs/SECURITY_MODEL.md) for the invariant-to-test map.
436
+
437
+ ## Project status
438
+
439
+ Version 0.1.1, built to [`SPEC.md`](SPEC.md), which stays in the repository as the
440
+ authoritative description of the intended behaviour. The implementation is TypeScript on
441
+ Node; it was ported from an equivalent Python implementation, which remains in the git
442
+ history. Every substantial module and test
443
+ cites the section it implements, so a reviewer can check the code against the requirement
444
+ rather than against a summary of it.
445
+
446
+ The MVP is complete and the full suite — including the adversarial one — passes. What
447
+ remains before anyone should rely on it in anger: independent review, human-factor testing
448
+ of the confirmation UI (SPEC.md §35), and signed release artefacts (§43).
449
+
450
+ ## Contributing
451
+
452
+ Security is the product here, so the bar for changes is specific rather than bureaucratic:
453
+
454
+ - A change that touches credential handling, authorization or the MCP surface needs a test
455
+ that *attempts to break* the invariant it affects, not only one that shows it working.
456
+ - Never weaken a security test to make a suite pass. If a test reveals an architectural
457
+ flaw, the architecture is what changes.
458
+ - New runtime dependencies in the core are opposed by default. The broker is the trusted
459
+ computing base for credential material; provider SDKs belong behind an optional extra.
460
+ - Run `npm run check` before opening a pull request.
461
+
462
+ Found a vulnerability? Please report it privately through GitHub's security advisories
463
+ rather than opening a public issue.
464
+
465
+ ## License
466
+
467
+ [Apache License 2.0](LICENSE) © 2026 Eduardo Rosostolato.