@shieldfive/mcp 0.2.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/CHANGELOG.md +35 -0
- package/README.md +187 -59
- package/SECURITY.md +27 -8
- package/package.json +22 -12
- package/server.json +34 -0
- package/src/server.mjs +206 -30
- package/src/tools/vault.mjs +548 -0
- package/src/vault/api.mjs +190 -0
- package/src/vault/cli.mjs +115 -0
- package/src/vault/content.mjs +89 -0
- package/src/vault/credential.mjs +69 -0
- package/src/vault/namePool.mjs +90 -0
- package/src/vault/nameWorker.mjs +23 -0
- package/src/vault/session.mjs +170 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,41 @@ All notable changes to `@shieldfive/mcp` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 0.3.0 — unreleased
|
|
9
|
+
|
|
10
|
+
Vault tools. The server can now work on a ShieldFive vault through an **agent
|
|
11
|
+
grant**: a connection the user creates in ShieldFive → Settings → AI
|
|
12
|
+
assistants, limited to chosen folders and permissions, expiring (at most 90
|
|
13
|
+
days), revocable, audited, and enforced by the server on every request.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `vault_list_files`, `vault_search_files`, `vault_storage_stats`,
|
|
18
|
+
`vault_find_duplicates` and `vault_read_file` (read), and `vault_rename`,
|
|
19
|
+
`vault_move`, `vault_create_folder` and `vault_trash` (organize). They are
|
|
20
|
+
registered only when a connection is configured.
|
|
21
|
+
- `npx @shieldfive/mcp login | logout | status`. The connection string is
|
|
22
|
+
stored in the OS keychain through `@napi-rs/keyring`, with `SHIELDFIVE_GRANT`
|
|
23
|
+
as the fallback for headless use.
|
|
24
|
+
- Decryption of all three vault formats (post-quantum hybrid, AES-GCM v1 and
|
|
25
|
+
legacy v0) in memory, through `@shieldfive/crypto`. Names are decrypted on a
|
|
26
|
+
worker pool and cached in memory.
|
|
27
|
+
- Progress notifications for name decryption and duplicate hashing.
|
|
28
|
+
- Every vault result is marked as data rather than instructions. File contents
|
|
29
|
+
are fenced with a random marker the file cannot close.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **The security boundary is restated, not removed.** 0.2.0 held no credential
|
|
34
|
+
and made no network request. That still holds for the local tools, which
|
|
35
|
+
import nothing from the vault half, so a server with no connection behaves as
|
|
36
|
+
before. The vault half talks to one https origin with one scoped credential.
|
|
37
|
+
`test/boundary.test.mjs` asserts the new lines: network access only in
|
|
38
|
+
`vault/api.mjs`, no filesystem access in any vault module, no cipher outside
|
|
39
|
+
`@shieldfive/crypto`, no account credential anywhere.
|
|
40
|
+
- A server started with a connection and no roots registers only the vault
|
|
41
|
+
tools.
|
|
42
|
+
|
|
8
43
|
## Unreleased
|
|
9
44
|
|
|
10
45
|
## 0.2.0 - 2026-09-16
|
package/README.md
CHANGED
|
@@ -1,28 +1,156 @@
|
|
|
1
1
|
# @shieldfive/mcp
|
|
2
2
|
|
|
3
|
-
A [Model Context Protocol](https://modelcontextprotocol.io) server that lets
|
|
4
|
-
|
|
5
|
-
what is large or stale, and move, rename or trash them.
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server that lets
|
|
4
|
+
Claude, ChatGPT, Cursor or a local model tidy two things:
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
- **your ShieldFive vault**: find duplicates, see what takes the space, rename,
|
|
7
|
+
move, and move to the Bin. It works on the folders you grant, decrypts on your
|
|
8
|
+
machine, and every change can be undone;
|
|
9
|
+
- **folders on your own disk**: the same jobs, with no network access at all.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
ShieldFive's servers never see a file name or a byte of content in the clear,
|
|
12
|
+
and that holds with this server running too. Decryption happens inside this
|
|
13
|
+
process, on your computer. What the assistant then does with what it reads is a
|
|
14
|
+
separate question, answered under [Security model](#security-model).
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
<!-- DEMO: a 40-second screen recording of a duplicate-cleanup session in Claude
|
|
17
|
+
Desktop: "find duplicates in my Photos" → groups with sizes → "trash the copies"
|
|
18
|
+
→ preview → confirm → the items appear in ShieldFive's Bin → one is undone from
|
|
19
|
+
Settings → AI assistants. Record against a demo vault, never a real one. -->
|
|
20
|
+
|
|
21
|
+
## Connect your vault in 60 seconds
|
|
16
22
|
|
|
17
23
|
Requires Node 20 or newer.
|
|
18
24
|
|
|
25
|
+
1. In ShieldFive, open **Settings → AI assistants → Connect an assistant**.
|
|
26
|
+
Choose the folders, *Read only* or *Read and organize*, and an expiry (1 hour
|
|
27
|
+
to 90 days). Copy the connection string. It is shown once.
|
|
28
|
+
2. Store it in your system keychain:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npx -y @shieldfive/mcp login
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
3. Add the server to your assistant. For Claude Desktop, add this to
|
|
35
|
+
`claude_desktop_config.json` (Cursor uses the same block in `~/.cursor/mcp.json`):
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"shieldfive": { "command": "npx", "args": ["-y", "@shieldfive/mcp"] }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For Claude Code: `claude mcp add shieldfive -- npx -y @shieldfive/mcp`
|
|
46
|
+
|
|
47
|
+
Restart the assistant and ask it to *find duplicates in my vault*.
|
|
48
|
+
|
|
49
|
+
`npx @shieldfive/mcp status` shows which connection is configured and whether
|
|
50
|
+
ShieldFive still accepts it. `npx @shieldfive/mcp logout` removes it from the
|
|
51
|
+
keychain. Revoking it in ShieldFive is what cuts off access everywhere.
|
|
52
|
+
|
|
53
|
+
For CI or a machine without a keychain, set `SHIELDFIVE_GRANT` to the connection
|
|
54
|
+
string instead. Anything that can read the server's environment can then read
|
|
55
|
+
the connection, so prefer the keychain wherever there is one.
|
|
56
|
+
|
|
57
|
+
## Security model
|
|
58
|
+
|
|
59
|
+
In plain terms:
|
|
60
|
+
|
|
61
|
+
- **The connection string holds two things.** A *token* the server checks on
|
|
62
|
+
every request, and a *secret* that never leaves your machine. ShieldFive stores
|
|
63
|
+
only a hash of the token and has never seen the secret.
|
|
64
|
+
- **The secret opens only the folders you chose.** When you create a connection,
|
|
65
|
+
your browser wraps those folders' keys under a key derived from the secret. It
|
|
66
|
+
wraps nothing else: not your vault root key, not your password, not your
|
|
67
|
+
post-quantum secret key. Subfolders open through the vault's normal folder-key
|
|
68
|
+
chain. A folder you did not choose cannot be opened with anything this server
|
|
69
|
+
holds.
|
|
70
|
+
- **ShieldFive enforces scope, expiry and revocation on every request.** The
|
|
71
|
+
checks in this process only produce clearer errors; the server is the
|
|
72
|
+
boundary. Revoking a connection makes its next request fail. Nothing is
|
|
73
|
+
cached that would outlive a revocation.
|
|
74
|
+
- **Decryption happens here, in memory.** No plaintext, key or ciphertext is
|
|
75
|
+
written to disk. The vault modules do not import the filesystem, and a test
|
|
76
|
+
asserts that.
|
|
77
|
+
- **Nothing is deleted.** `vault_trash` moves items into a folder in your Bin
|
|
78
|
+
that belongs to the connection. There is no permanent-delete tool, and the
|
|
79
|
+
API a connection can reach has no delete route. Every rename, move and trash
|
|
80
|
+
appears in *Settings → AI assistants → Activity* with an Undo button.
|
|
81
|
+
- **Every change is previewed first.** Mutating tools report a plan, and the
|
|
82
|
+
confirmed call must carry that plan's token and is refused if the items
|
|
83
|
+
changed in between.
|
|
84
|
+
|
|
85
|
+
What this does not protect:
|
|
86
|
+
|
|
87
|
+
- **Your AI provider sees what the assistant reads.** File names, and the
|
|
88
|
+
contents of files the assistant opens, go to the assistant, and for a cloud
|
|
89
|
+
assistant that means to its provider, like the rest of your conversation. The
|
|
90
|
+
only way to avoid that is a local model.
|
|
91
|
+
- **Revoking cannot un-read.** Anything the assistant has already read stays
|
|
92
|
+
read. Nothing else survives it: file contents are streamed through
|
|
93
|
+
ShieldFive on each request, so there is no download link to outlive a
|
|
94
|
+
revocation.
|
|
95
|
+
- **A connection is built from what the server shows your browser when you
|
|
96
|
+
create it.** Every later extension is checked against your own keys, so a
|
|
97
|
+
compromised server cannot widen a connection afterwards. At the moment of
|
|
98
|
+
creation, though, a compromised server could mislabel which folder you
|
|
99
|
+
picked.
|
|
100
|
+
- **A copied connection string is a live key** to the folders it covers until
|
|
101
|
+
it expires or you revoke it. Keep it in the keychain.
|
|
102
|
+
- **Files can contain instructions aimed at the assistant.** This server marks
|
|
103
|
+
every name and file content as data, fences file contents in a block the file
|
|
104
|
+
cannot close, caps `vault_trash` at 50 items per call, requires a preview for
|
|
105
|
+
every change, and keeps every change undoable. A model can still be talked
|
|
106
|
+
into a reversible mistake inside the folders you granted.
|
|
107
|
+
|
|
108
|
+
The full design, including the threat model and the reasoning behind each
|
|
109
|
+
decision, is in
|
|
110
|
+
[`docs/mcp-grants-design.md`](https://github.com/shieldfive/web/blob/main/docs/mcp-grants-design.md).
|
|
111
|
+
|
|
112
|
+
## Vault tools
|
|
113
|
+
|
|
114
|
+
Registered only when a connection is configured. Everything below names things
|
|
115
|
+
by id; paths are for people.
|
|
116
|
+
|
|
117
|
+
| Tool | Needs | What it does |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `vault_list_files` | read | files and folders in scope, with decrypted names, paths, sizes, dates |
|
|
120
|
+
| `vault_search_files` | read | by name, path, extension, size or date, run locally over decrypted names |
|
|
121
|
+
| `vault_storage_stats` | read | totals, the biggest folders and files, a breakdown by type |
|
|
122
|
+
| `vault_find_duplicates` | read | same-size files decrypted in memory and compared by SHA-256; budgeted, and says when a result is a lower bound |
|
|
123
|
+
| `vault_read_file` | read | text files as fenced, untrusted content (up to 1 M characters); other types return details only |
|
|
124
|
+
| `vault_rename` | organize | rename a file or folder |
|
|
125
|
+
| `vault_move` | organize | move into another folder in scope |
|
|
126
|
+
| `vault_create_folder` | organize | create a folder in scope |
|
|
127
|
+
| `vault_trash` | organize | up to 50 items into the connection's folder in the Bin |
|
|
128
|
+
|
|
129
|
+
Limits a user may meet:
|
|
130
|
+
|
|
131
|
+
- **Post-quantum files uploaded from a phone or the CLI** show as
|
|
132
|
+
`readable: false` until you next open ShieldFive on the web, which adds the key
|
|
133
|
+
the connection needs. Files uploaded in the web app are ready straight away.
|
|
134
|
+
- **The first listing of a large vault takes a while.** Each name costs about
|
|
135
|
+
70 ms of Argon2id, spread over your CPU cores (about 20 seconds for 2,000
|
|
136
|
+
names on 8 cores). Names are cached in memory for the rest of the session.
|
|
137
|
+
- **Items at the very top of a whole-vault connection** can be read and moved
|
|
138
|
+
into a folder, but not renamed in place, and nothing can be moved to the top.
|
|
139
|
+
Their names are sealed under your vault root key, which a connection never
|
|
140
|
+
holds.
|
|
141
|
+
- **Uploads are not available yet.** The design allows them, but they ship
|
|
142
|
+
after the read and organize tools have been in use for a while.
|
|
143
|
+
|
|
144
|
+
## Local files
|
|
145
|
+
|
|
146
|
+
Every path after the package name is a **root**. The local tools can read and
|
|
147
|
+
write inside those directories and nowhere else, and make no network request.
|
|
148
|
+
|
|
19
149
|
```sh
|
|
20
|
-
|
|
150
|
+
npx @shieldfive/mcp ~/Documents ~/Downloads
|
|
21
151
|
```
|
|
22
152
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Add to `claude_desktop_config.json`:
|
|
153
|
+
In `claude_desktop_config.json`:
|
|
26
154
|
|
|
27
155
|
```json
|
|
28
156
|
{
|
|
@@ -35,10 +163,9 @@ Add to `claude_desktop_config.json`:
|
|
|
35
163
|
}
|
|
36
164
|
```
|
|
37
165
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
you so.
|
|
166
|
+
With a connection configured and no roots, only the vault tools are registered.
|
|
167
|
+
With roots and no connection, only the local tools are, and the server behaves
|
|
168
|
+
exactly as 0.2.0 did. With both, you get both.
|
|
42
169
|
|
|
43
170
|
`SHIELDFIVE_MCP_ROOTS` adds roots as well — the two are combined, not
|
|
44
171
|
alternatives — as a list separated by your platform's path separator (`:` on
|
|
@@ -51,7 +178,7 @@ SHIELDFIVE_MCP_ROOTS="/Users/you/Documents:/Volumes/Archive" npx @shieldfive/mcp
|
|
|
51
178
|
Whitespace around a root is ignored. In a path given to a tool it is not: there,
|
|
52
179
|
every character is part of the path.
|
|
53
180
|
|
|
54
|
-
|
|
181
|
+
### See it work first
|
|
55
182
|
|
|
56
183
|
```bash
|
|
57
184
|
npm run demo
|
|
@@ -63,33 +190,19 @@ a two-year-old PDF — runs the read tools over them, previews a trash call, the
|
|
|
63
190
|
confirms it and shows the manifest. It touches nothing outside that directory
|
|
64
191
|
and removes it at the end (`--keep` leaves it in place).
|
|
65
192
|
|
|
66
|
-
## What
|
|
67
|
-
|
|
68
|
-
**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
would be *declining* to read your files rather than being *unable* to, with the
|
|
78
|
-
difference resting on a client-side denylist and on nothing else on your machine
|
|
79
|
-
reading the token file. A server holding no token cannot read them at all.
|
|
80
|
-
|
|
81
|
-
When a scoped, metadata-only key exists, vault tools can be added behind it.
|
|
82
|
-
Until then this is a local file manager that happens to be published by the
|
|
83
|
-
people who make an encrypted vault.
|
|
84
|
-
|
|
85
|
-
**It cannot stop the results from reaching your AI provider.** This server
|
|
86
|
-
makes no network request, and that is worth exactly what it says and no more:
|
|
87
|
-
everything it returns — paths, file names, sizes, dates, the digests it
|
|
88
|
-
reports — goes back to the AI client that called it, and if that client is a
|
|
193
|
+
## What the local tools cannot do
|
|
194
|
+
|
|
195
|
+
**They cannot tell you whether a local file is already in your vault.** Matching
|
|
196
|
+
a local name and size against a vault listing is how a tool deletes the only
|
|
197
|
+
copy of something, and this server will not guess.
|
|
198
|
+
|
|
199
|
+
**They cannot stop the results from reaching your AI provider.** The local
|
|
200
|
+
tools make no network request, and that is worth exactly what it says and no
|
|
201
|
+
more:
|
|
202
|
+
everything they return — paths, file names, sizes, dates, the digests they
|
|
203
|
+
report — goes back to the AI client that called it, and if that client is a
|
|
89
204
|
cloud assistant, those names travel to the assistant's provider like the rest
|
|
90
|
-
of your conversation.
|
|
91
|
-
that basis: point it at the folders you would be willing to describe out loud,
|
|
92
|
-
and it will never see anything else.
|
|
205
|
+
of your conversation. Choose roots on that basis.
|
|
93
206
|
|
|
94
207
|
**It will never infer that two files are the same from their names and sizes.**
|
|
95
208
|
Duplicate detection reads both files and compares a full SHA-256 of their
|
|
@@ -143,7 +256,7 @@ naming what changed. A token performs one change and expires after ten minutes.
|
|
|
143
256
|
So a directory that grew, a destination that appeared, or a path that now points
|
|
144
257
|
at a different file stops the call instead of silently widening it.
|
|
145
258
|
|
|
146
|
-
##
|
|
259
|
+
## Local tools
|
|
147
260
|
|
|
148
261
|
| Tool | Reads | Writes |
|
|
149
262
|
|---|---|---|
|
|
@@ -290,7 +403,7 @@ renames, and a file created in that instant would be replaced.
|
|
|
290
403
|
|
|
291
404
|
## What the tests assert
|
|
292
405
|
|
|
293
|
-
`npm test` runs
|
|
406
|
+
`npm test` runs 183 tests. The ones worth knowing about:
|
|
294
407
|
|
|
295
408
|
- A symlink pointing out of a root is refused, on both the read and the write
|
|
296
409
|
side, and so is a dangling symlink on a write path.
|
|
@@ -310,18 +423,33 @@ renames, and a file created in that instant would be replaced.
|
|
|
310
423
|
replaced.
|
|
311
424
|
- A cancelled request moves nothing, and a trash batch cancelled midway says
|
|
312
425
|
exactly what it moved.
|
|
313
|
-
-
|
|
314
|
-
|
|
315
|
-
`
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
426
|
+
- Only `src/vault/api.mjs` calls `fetch`, and only to an https ShieldFive
|
|
427
|
+
origin. No local-tool module imports anything from the vault half, no file
|
|
428
|
+
under `src/` spawns a subprocess, and the only environment variables read are
|
|
429
|
+
`SHIELDFIVE_MCP_ROOTS`, `SHIELDFIVE_GRANT` and `SHIELDFIVE_API_URL`.
|
|
430
|
+
- The vault modules import no filesystem module, so decrypted data cannot be
|
|
431
|
+
written to disk. No module uses a cipher, HMAC or KDF of its own. All
|
|
432
|
+
cryptography comes from `@shieldfive/crypto`.
|
|
433
|
+
- Against an in-memory ShieldFive that serves real ciphertext in all three vault
|
|
434
|
+
formats, over a real MCP client:
|
|
435
|
+
- files decrypt through the grant's keys only, and nothing outside the scope
|
|
436
|
+
is listed, read or even requested;
|
|
437
|
+
- a revoked or expired connection fails the very next call;
|
|
438
|
+
- renames and moves re-seal names and keys so the owner's own keys still
|
|
439
|
+
open them;
|
|
440
|
+
- the grant secret and token never appear in any request body or path.
|
|
441
|
+
- A file whose contents tell the assistant to trash everything comes back
|
|
442
|
+
inside a fence it cannot close. Reading it issues only reads, and a 51-item
|
|
443
|
+
trash call is refused.
|
|
444
|
+
- A real MCP client over a real stdio transport sees exactly the nine local
|
|
445
|
+
tools when no connection is configured, including when the server is started
|
|
446
|
+
through a symlink the way npm installs it.
|
|
447
|
+
|
|
448
|
+
The network assertion has a limit worth stating: it proves what `src/` does,
|
|
449
|
+
not what the dependency tree could do. `@modelcontextprotocol/sdk` ships HTTP
|
|
450
|
+
transports for other people's servers. What closes that gap is that
|
|
451
|
+
`server.mjs` imports the stdio transport and no HTTP one, which is also
|
|
452
|
+
asserted.
|
|
325
453
|
|
|
326
454
|
## Limits
|
|
327
455
|
|
package/SECURITY.md
CHANGED
|
@@ -40,17 +40,21 @@ shipped the fix.
|
|
|
40
40
|
## Scope
|
|
41
41
|
|
|
42
42
|
In scope: this package's own source — path containment, the walk, the mutating
|
|
43
|
-
tools, the trash, the
|
|
43
|
+
tools, the trash, the vault tools and how they handle grant keys, names and
|
|
44
|
+
decrypted content, the MCP surface, and the claims its README makes.
|
|
44
45
|
|
|
45
46
|
Out of scope for this repository, with the right destination:
|
|
46
47
|
|
|
47
48
|
- The ShieldFive vault, web application and API — `security@shieldfive.com`,
|
|
48
49
|
same address, different codebase.
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
- The agent-grant API and its enforcement (scope, expiry, revocation, the
|
|
51
|
+
audit log) — `shieldfive/web`, same address.
|
|
52
|
+
- `@shieldfive/crypto` — its own repository. The vault half depends on it for
|
|
53
|
+
every cryptographic operation; a test asserts no module here implements a
|
|
54
|
+
cipher, HMAC or KDF of its own.
|
|
55
|
+
- `@modelcontextprotocol/sdk`, `zod`, `libsodium-wrappers-sumo` and
|
|
56
|
+
`@napi-rs/keyring` — report upstream. A vulnerability in how *this* package
|
|
57
|
+
uses them is in scope.
|
|
54
58
|
|
|
55
59
|
## Threat model
|
|
56
60
|
|
|
@@ -76,8 +80,13 @@ has.
|
|
|
76
80
|
| An assistant acting without the user seeing the plan | Mutating tools are inert without `confirm: true` and report what they would displace as well as what they would move | yes |
|
|
77
81
|
| An oversized argument | `limit`, `max_files`, `max_files_hashed`, `paths`, path length and `new_name` are capped at the MCP schema and again in the handler; values echoed in a refusal are cut short | yes |
|
|
78
82
|
| A cancelled request still changing files | Nothing starts once a request is cancelled; a trash batch stops between items and says what moved; the outcome is logged, because the SDK sends no response to a cancelled request | yes |
|
|
79
|
-
| Credential exposure in this code |
|
|
80
|
-
|
|
|
83
|
+
| Credential exposure in this code | The only credential is an agent grant, read from `SHIELDFIVE_GRANT` or the OS keychain; never logged, never returned, never written to a file. The only environment variables read are `SHIELDFIVE_MCP_ROOTS`, `SHIELDFIVE_GRANT` and `SHIELDFIVE_API_URL`. No account password, service key or vault-key route is referenced anywhere | yes |
|
|
84
|
+
| Network access | Only `vault/api.mjs` calls `fetch`, only to an https ShieldFive origin (or localhost for development). Local tools import nothing from the vault half; the transport is stdio | yes, for this package's source |
|
|
85
|
+
| A grant opening more than its scope | Keys come only from the grant's own wraps (AAD-bound to grant, kind and object) and the folder chain below them. The server enforces scope on every request; local checks only produce clearer errors | yes, against an in-memory server with real ciphertext |
|
|
86
|
+
| Revocation not taking effect | Every tool call re-fetches the grant and its listing; file contents are streamed through the API per request (no storage URL is ever held); nothing that would outlive a revocation is cached | yes |
|
|
87
|
+
| Decrypted data reaching disk | Vault modules import no filesystem module; plaintext exists only in memory for the duration of a call; decrypted names are cached in memory only | yes |
|
|
88
|
+
| Prompt injection through names or file contents | Names are stripped of control and bidi characters; every result is marked as data; file contents are fenced with a random marker the file cannot close; every change needs a preview and its plan token; `vault_trash` is capped at 50 items; nothing is ever deleted and every change is undoable by the owner | yes, with an injection fixture |
|
|
89
|
+
| A write the owner cannot read back | Names are re-sealed as v6 envelopes under the destination folder key and content keys re-wrapped under it; a name that failed to decrypt is never re-sealed | yes, the owner's keys re-open every write |
|
|
81
90
|
| Credential exposure via a subprocess | No subprocess is spawned at all. Spawning `sf` would inherit `SF_PASSWORD` from the environment whether or not this code named it | yes |
|
|
82
91
|
|
|
83
92
|
## Known limits
|
|
@@ -139,6 +148,16 @@ has.
|
|
|
139
148
|
import would evade it.
|
|
140
149
|
- **File paths reach the model.** If a path is itself sensitive, do not give
|
|
141
150
|
this server the root it sits in.
|
|
151
|
+
- **Everything the vault tools return reaches the model, and so its provider.**
|
|
152
|
+
A grant limits which folders the assistant can open; it cannot limit what the
|
|
153
|
+
assistant's provider sees of what was opened. Revocation cannot un-read.
|
|
154
|
+
- **A connection string is a bearer credential plus its key.** Held by anyone,
|
|
155
|
+
it gives that connection's access until it expires or is revoked. In the
|
|
156
|
+
keychain it is protected like any other stored secret; in `SHIELDFIVE_GRANT`
|
|
157
|
+
it is readable by anything that can read the process environment.
|
|
158
|
+
- **Instruction-following is the model's, not this server's.** The fencing and
|
|
159
|
+
caps above bound the damage of an injected instruction to reversible changes
|
|
160
|
+
inside the granted folders; they do not make a model ignore text.
|
|
142
161
|
- **Windows is untested.** No POSIX-only API is used and paths go through
|
|
143
162
|
`node:path`, but nobody has run it there, and the cross-device tests run only
|
|
144
163
|
on macOS.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shieldfive/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Model Context Protocol server for
|
|
5
|
+
"description": "Model Context Protocol server for your ShieldFive vault and local files: find duplicates, reclaim space and reorganise, with decryption on your machine and scoped, revocable, audited access.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Cho Garcia <security@shieldfive.com>",
|
|
8
8
|
"homepage": "https://github.com/shieldfive/mcp",
|
|
@@ -14,13 +14,17 @@
|
|
|
14
14
|
"url": "https://github.com/shieldfive/mcp/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
|
-
"shieldfive",
|
|
18
|
-
"mcp",
|
|
19
|
-
"model-context-protocol",
|
|
20
17
|
"claude",
|
|
21
|
-
"
|
|
18
|
+
"disk-space",
|
|
22
19
|
"duplicate-files",
|
|
23
|
-
"
|
|
20
|
+
"encryption",
|
|
21
|
+
"end-to-end-encryption",
|
|
22
|
+
"filesystem",
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"post-quantum",
|
|
26
|
+
"shieldfive",
|
|
27
|
+
"vault"
|
|
24
28
|
],
|
|
25
29
|
"bin": {
|
|
26
30
|
"shieldfive-mcp": "src/server.mjs"
|
|
@@ -29,21 +33,27 @@
|
|
|
29
33
|
"access": "public"
|
|
30
34
|
},
|
|
31
35
|
"files": [
|
|
32
|
-
"
|
|
36
|
+
"CHANGELOG.md",
|
|
37
|
+
"LICENSE",
|
|
33
38
|
"README.md",
|
|
34
39
|
"SECURITY.md",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
40
|
+
"server.json",
|
|
41
|
+
"src"
|
|
37
42
|
],
|
|
38
43
|
"engines": {
|
|
39
44
|
"node": ">=20"
|
|
40
45
|
},
|
|
41
46
|
"scripts": {
|
|
42
47
|
"test": "node --test test/*.test.mjs",
|
|
43
|
-
"demo": "node demo/run-demo.mjs"
|
|
48
|
+
"demo": "node demo/run-demo.mjs",
|
|
49
|
+
"build:mcpb": "node scripts/build-mcpb.mjs"
|
|
44
50
|
},
|
|
45
51
|
"dependencies": {
|
|
46
52
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
53
|
+
"@napi-rs/keyring": "^2.1.0",
|
|
54
|
+
"@shieldfive/crypto": "1.0.0-rc.5",
|
|
55
|
+
"libsodium-wrappers-sumo": "^0.8.4",
|
|
47
56
|
"zod": "^3.25.0"
|
|
48
|
-
}
|
|
57
|
+
},
|
|
58
|
+
"mcpName": "io.github.shieldfive/mcp"
|
|
49
59
|
}
|
package/server.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.shieldfive/mcp",
|
|
4
|
+
"title": "ShieldFive",
|
|
5
|
+
"description": "Tidy your end-to-end encrypted ShieldFive vault and local folders: duplicates, space, reorganising. Decrypts on your machine; scoped, revocable access.",
|
|
6
|
+
"version": "0.3.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/shieldfive/mcp",
|
|
9
|
+
"source": "github"
|
|
10
|
+
},
|
|
11
|
+
"websiteUrl": "https://shieldfive.com",
|
|
12
|
+
"packages": [
|
|
13
|
+
{
|
|
14
|
+
"registryType": "npm",
|
|
15
|
+
"identifier": "@shieldfive/mcp",
|
|
16
|
+
"version": "0.3.0",
|
|
17
|
+
"transport": { "type": "stdio" },
|
|
18
|
+
"environmentVariables": [
|
|
19
|
+
{
|
|
20
|
+
"name": "SHIELDFIVE_GRANT",
|
|
21
|
+
"description": "Connection string from ShieldFive → Settings → AI assistants. Optional: `npx @shieldfive/mcp login` stores it in the OS keychain instead.",
|
|
22
|
+
"isRequired": false,
|
|
23
|
+
"isSecret": true
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "SHIELDFIVE_MCP_ROOTS",
|
|
27
|
+
"description": "Local directories the local tools may use, separated by the platform path separator. Optional.",
|
|
28
|
+
"isRequired": false,
|
|
29
|
+
"isSecret": false
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|