@safekb/mcp 0.1.0 → 0.3.0
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/README.md +100 -8
- package/dist/safekb-mcp.js +1862 -194
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -15,6 +15,12 @@ This opens the browser and prints a **pairing code**. Sign in on the page, choos
|
|
|
15
15
|
pairing code, and the client saves a service-account token to `~/.config/safekb/mcp.json` (mode 0600).
|
|
16
16
|
It then prints the account it was authorized as; check that it is yours. There is no default URL.
|
|
17
17
|
|
|
18
|
+
Last, it chooses where this machine keeps the keys of new seal roots (see [Where secrets are kept](#where-secrets-are-kept)).
|
|
19
|
+
With a system keyring that is the choice. Without one, as on most servers, it warns and asks before it
|
|
20
|
+
uses key files. `npx -y @safekb/mcp --configure` makes the same choice again without re-authorizing.
|
|
21
|
+
A client authorized before this choice existed makes it by itself on its next start when the system
|
|
22
|
+
keyring works; only key files need `--configure`, because they need your agreement.
|
|
23
|
+
|
|
18
24
|
How the token gets to the client: the page never sees it. The page hands the client a single-use code
|
|
19
25
|
(valid for two minutes), and the client redeems that code itself, proving with a secret it never wrote
|
|
20
26
|
anywhere that it is the process that started the flow. The URL in the browser launcher's arguments is
|
|
@@ -41,10 +47,15 @@ Claude Desktop and Cursor (`mcpServers` in their MCP config file):
|
|
|
41
47
|
| --- | --- | --- |
|
|
42
48
|
| `--url <api url>` | `KB_API_URL` | API base URL. Otherwise the URL saved by `--authorize`. When serving, it must have the origin the token was issued for: the token is never sent anywhere else. |
|
|
43
49
|
| `--config <path>` | `SAFEKB_MCP_CONFIG` | Token file. Default `~/.config/safekb/mcp.json`. Use one per project for per-project access. |
|
|
44
|
-
| `--keyring <dir>` | `SAFEKB_SEALED_KEYRING` | Directory of sealed key files (`*.json`).
|
|
50
|
+
| `--keyring <dir>` | `SAFEKB_SEALED_KEYRING` | Directory of sealed key files (`*.json`). Otherwise the one `--configure` chose, if any. |
|
|
45
51
|
| `--sealed-only` | | Expose only the sealed tools; the hosted tools are not proxied. |
|
|
46
|
-
| `--authorize` | | Authorize in the browser and write the token file. |
|
|
47
|
-
|
|
|
52
|
+
| `--authorize` | | Authorize in the browser and write the token file, then choose the key store. |
|
|
53
|
+
| `--configure` | | Choose where new sealed keys are kept. Writes `stores.json` next to the token file. |
|
|
54
|
+
| `--sidecar` | | Run the sidecar in this terminal. With `--confirm` it asks before every release of a secret. |
|
|
55
|
+
| `--sidecar-socket <path>` | `SAFEKB_SIDECAR_SOCKET` | The sidecar's socket. Default `sidecar.sock` next to the token file. |
|
|
56
|
+
| `--export-keys <file>` | | Write every sealed key this machine keeps to a new passphrase-protected file. See [Moving keys to another machine](#moving-keys-to-another-machine). |
|
|
57
|
+
| `--import-keys <file>` | | Read such a file and keep its keys on this machine. |
|
|
58
|
+
| | `SAFEKB_SEALED_IDLE_TTL` | Seconds an unlocked seal root may stay idle. Default 900. `unlock_sealed` can set it per root with `idle_ttl_seconds` (60 to 3600). |
|
|
48
59
|
| | `SAFEKB_HOSTED_TIMEOUT` | Seconds a hosted tool call may take. Default 300. |
|
|
49
60
|
|
|
50
61
|
The client refuses to start when the token file or a keyring file is readable by group or others,
|
|
@@ -57,16 +68,95 @@ confirmed*: it may have run, and it is not sent again. Check the state before re
|
|
|
57
68
|
## Sealed documents
|
|
58
69
|
|
|
59
70
|
A **seal root** is a sealed folder (one secret opens everything in it) or a standalone sealed document.
|
|
60
|
-
Its secret is a generated key
|
|
71
|
+
Its secret is a generated key or a passphrase.
|
|
61
72
|
|
|
62
|
-
- `create_sealed_folder` / `insert_sealed_document` with `key_out_path`
|
|
63
|
-
The
|
|
64
|
-
|
|
73
|
+
- `create_sealed_folder` / `insert_sealed_document` with `store` or `key_out_path` make a new seal root
|
|
74
|
+
and keep its secret on this machine. The secret is never returned to the model.
|
|
75
|
+
**Back it up: a lost secret cannot be recovered.**
|
|
76
|
+
- `unlock_sealed` with only `root_id` takes the secret from the store that has it; `secret_file` reads
|
|
77
|
+
it from a file.
|
|
65
78
|
- Then `search_sealed`, `list_sealed`, `read_sealed_document`, `get_sealed_section`,
|
|
66
79
|
`update_sealed_document`, `replace_in_sealed_document`, and the move, archive, delete, export,
|
|
67
80
|
attachment and deposit tools.
|
|
68
81
|
- `lock_sealed`, the idle timeout, or the end of the process forget the keys.
|
|
69
82
|
|
|
83
|
+
## Where secrets are kept
|
|
84
|
+
|
|
85
|
+
Every seal root has its own secret, and each can be kept in a different place. All three work side by
|
|
86
|
+
side: `unlock_sealed` with only `root_id` asks each store and uses the one that has the secret. If two
|
|
87
|
+
have it, it stops and asks for `store`.
|
|
88
|
+
|
|
89
|
+
| `store` | What is kept | Where | At rest |
|
|
90
|
+
| --- | --- | --- | --- |
|
|
91
|
+
| `system` | a generated key | the operating system's keyring: Keychain (macOS), Credential Manager (Windows), Secret Service (Linux) | encrypted by the system |
|
|
92
|
+
| `file` | a generated key | a file (mode 0600) in the keyring directory | not encrypted |
|
|
93
|
+
| `sidecar` | a passphrase you type | the memory of a separate process, for a time you choose | nowhere |
|
|
94
|
+
| `default` | | whichever of `system` and `file` `--configure` chose | |
|
|
95
|
+
|
|
96
|
+
`key_out_path` still writes a key file to a path of your choice, and `passphrase` still takes one as
|
|
97
|
+
text (which then is in the conversation transcript).
|
|
98
|
+
|
|
99
|
+
The system keyring is used only where `--configure` found it working. On Linux that means a running
|
|
100
|
+
Secret Service (GNOME Keyring, KWallet, KeePassXC); the kernel keyring is never used, because it
|
|
101
|
+
forgets its content at logout. Keys are kept there with [`@napi-rs/keyring`](https://github.com/Brooooooklyn/keyring-node).
|
|
102
|
+
|
|
103
|
+
### The sidecar
|
|
104
|
+
|
|
105
|
+
The sidecar keeps passphrases out of files, out of the keyring and out of the conversation. Run it in
|
|
106
|
+
a terminal of its own:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
npx -y @safekb/mcp --sidecar
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- `add <name or id> [minutes]` asks for the root's secret without showing it and holds it for that long
|
|
113
|
+
(default 15 minutes). The time is fixed when you type the secret: use does not extend it.
|
|
114
|
+
- `list`, `drop <name or id>`, `drop all`, `quit`.
|
|
115
|
+
- A seal root can be named by its name instead of its id (`add Tax papers`), see
|
|
116
|
+
[Names of seal roots](#names-of-seal-roots).
|
|
117
|
+
- A new seal root with `store: "sidecar"` makes the sidecar ask you for a new passphrase, twice, while
|
|
118
|
+
the tool call waits.
|
|
119
|
+
- With `--confirm` it asks for your approval before every release of a secret.
|
|
120
|
+
|
|
121
|
+
Several roots can be held at once, each with its own passphrase and time. The sidecar listens on a Unix
|
|
122
|
+
socket only your user can open; it is not available on Windows. Any program running as you can still
|
|
123
|
+
ask it for a held secret, which is what `--confirm` is for.
|
|
124
|
+
|
|
125
|
+
### Names of seal roots
|
|
126
|
+
|
|
127
|
+
The server cannot read the name of a sealed folder, and an id like `6acdb031893533e0ae1f5456` is no
|
|
128
|
+
name to type. So the client remembers the name of every seal root it creates or unlocks, in
|
|
129
|
+
`roots.json` next to the token file (mode 0600), and follows a rename or a delete. The sidecar and
|
|
130
|
+
the key export show a root as `"Tax papers" (6acd…)` and take the name wherever they take an id.
|
|
131
|
+
|
|
132
|
+
- The names are in plaintext in that file: readable on this machine by programs running as you,
|
|
133
|
+
never by the server. Delete the file to forget them; they come back as roots are unlocked.
|
|
134
|
+
- A name is never put into a tool result or an error, so unlocking a root does not tell the model
|
|
135
|
+
what it is called.
|
|
136
|
+
- When two roots have the same name, the name stands for neither: use the id.
|
|
137
|
+
|
|
138
|
+
### Moving keys to another machine
|
|
139
|
+
|
|
140
|
+
A generated key lives only where it was made: in the system keyring or in a key file. To carry all of
|
|
141
|
+
them to another machine, or to back them up:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
npx -y @safekb/mcp --export-keys keys.safekb # on the machine that has the keys
|
|
145
|
+
npx -y @safekb/mcp --import-keys keys.safekb # on the other machine, after --authorize there
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- Export asks for a passphrase (typed twice, not shown; 12 or more characters are recommended, a
|
|
149
|
+
shorter one is pointed out and taken) and writes one file
|
|
150
|
+
with every key of the system keyring and of the key file directory, and the names of their roots.
|
|
151
|
+
The content is encrypted with AES-256-GCM under a key derived from the passphrase with Argon2id.
|
|
152
|
+
Without the passphrase the file opens for nobody.
|
|
153
|
+
- Import asks for that passphrase and keeps the keys in the store `--configure` chose on that
|
|
154
|
+
machine. A key the machine already keeps is left as it is, so an import can be repeated.
|
|
155
|
+
- Both need a terminal: the passphrase is never an argument, so it is in no shell history, no process
|
|
156
|
+
list and no conversation.
|
|
157
|
+
- A root that is opened by a passphrase has no key on any machine. There is nothing to export: type
|
|
158
|
+
the passphrase at the other machine's sidecar.
|
|
159
|
+
|
|
70
160
|
## Sealed credentials
|
|
71
161
|
|
|
72
162
|
A sealed credential belongs to a seal root. Its password, TOTP seed, `sealed_notes` and the values of
|
|
@@ -104,7 +194,9 @@ Old database and storage backups keep the server-encrypted copies until they age
|
|
|
104
194
|
## What sealing does not protect
|
|
105
195
|
|
|
106
196
|
- Whatever a tool returns enters the local conversation transcript and is sent to the model provider.
|
|
107
|
-
The same holds for a secret passed as text; use
|
|
197
|
+
The same holds for a secret passed as text; use a store or `secret_file`.
|
|
198
|
+
- A program running as your user can read a key file, ask the system keyring or the sidecar for a
|
|
199
|
+
secret, and read the memory of this client while a root is unlocked.
|
|
108
200
|
- A session that has unlocked a root can be tricked by content it reads into copying it elsewhere.
|
|
109
201
|
- A compromised local machine, or a malicious server serving stale data.
|
|
110
202
|
- Metadata: existence, sizes, timestamps, tree position.
|