@openparachute/vault 0.4.0 → 0.4.4-rc.11

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.
@@ -156,16 +156,16 @@ describe("vault doctor — extended checks", () => {
156
156
  // Isolated HOME with no ~/.claude.json at all — the most common
157
157
  // pre-`mcp-install` state for new users.
158
158
  const res = runCli(["doctor"], dir, { HOME: dir });
159
- expect(res.stdout).toMatch(/! MCP entry in ~\/\.claude\.json/);
160
- expect(res.stdout).toMatch(/does not exist|no mcpServers/);
159
+ expect(res.stdout).toMatch(/! MCP entry in MCP client config/);
160
+ expect(res.stdout).toMatch(/does not exist|no parachute-vault entry/);
161
161
  expect(res.stdout).toMatch(/mcp-install/);
162
162
  });
163
163
 
164
164
  test("warns when ~/.claude.json exists but has no parachute-vault entry", () => {
165
165
  writeFileSync(join(dir, ".claude.json"), JSON.stringify({ mcpServers: {} }));
166
166
  const res = runCli(["doctor"], dir, { HOME: dir });
167
- expect(res.stdout).toMatch(/! MCP entry in ~\/\.claude\.json/);
168
- expect(res.stdout).toMatch(/no mcpServers\["parachute-vault"\] entry/);
167
+ expect(res.stdout).toMatch(/! MCP entry in MCP client config/);
168
+ expect(res.stdout).toMatch(/no parachute-vault entry/);
169
169
  });
170
170
 
171
171
  test("passes MCP entry + port-match checks when URL points at the configured port", () => {
@@ -174,7 +174,8 @@ describe("vault doctor — extended checks", () => {
174
174
  writeFileSync(join(dir, "config.yaml"), "port: 4321\n");
175
175
  writeClaudeJson(dir, "http://127.0.0.1:4321/vault/default/mcp");
176
176
  const res = runCli(["doctor"], dir, { HOME: dir });
177
- expect(res.stdout).toMatch(/✓ MCP entry in ~\/\.claude\.json/);
177
+ // Doctor now names the source file in the check label.
178
+ expect(res.stdout).toMatch(/✓ MCP entry in ~\/\.claude\.json \(parachute-vault\)/);
178
179
  expect(res.stdout).toMatch(/✓ MCP URL port matches vault\s+\(port 4321\)/);
179
180
  // Reachability will warn because nothing is bound to 4321 in the test
180
181
  // env — this is the "entry present, port matches, daemon unreachable"
@@ -186,7 +187,7 @@ describe("vault doctor — extended checks", () => {
186
187
  writeFileSync(join(dir, "config.yaml"), "port: 4321\n");
187
188
  writeClaudeJson(dir, "http://127.0.0.1:9999/vault/default/mcp");
188
189
  const res = runCli(["doctor"], dir, { HOME: dir });
189
- expect(res.stdout).toMatch(/✓ MCP entry in ~\/\.claude\.json/);
190
+ expect(res.stdout).toMatch(/✓ MCP entry in ~\/\.claude\.json \(parachute-vault\)/);
190
191
  expect(res.stdout).toMatch(/! MCP URL port matches vault/);
191
192
  expect(res.stdout).toMatch(/MCP URL port 9999 ≠ vault port 4321/);
192
193
  });
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { describe, test, expect, beforeAll, afterAll, beforeEach } from "bun:test";
16
16
  import { generateKeyPair, exportJWK, SignJWT } from "jose";
17
- import { resetJwksCache, validateHubJwt, looksLikeJwt } from "./hub-jwt.ts";
17
+ import { resetJwksCache, resetRevocationCache, validateHubJwt, looksLikeJwt } from "./hub-jwt.ts";
18
18
 
19
19
  interface Keypair {
20
20
  privateKey: CryptoKey;
@@ -53,11 +53,19 @@ function startJwksFixture(): JwksFixture {
53
53
  port: 0,
54
54
  fetch(req) {
55
55
  const url = new URL(req.url);
56
- if (url.pathname !== "/.well-known/jwks.json") {
57
- return new Response("not found", { status: 404 });
58
- }
59
56
  if (down) return new Response("upstream down", { status: 503 });
60
- return Response.json({ keys: keys.map((k) => k.publicJwk) });
57
+ if (url.pathname === "/.well-known/jwks.json") {
58
+ return Response.json({ keys: keys.map((k) => k.publicJwk) });
59
+ }
60
+ // scope-guard 0.2+ consults `/.well-known/parachute-revocation.json` on
61
+ // every JWT validation (when the token has a jti). Serve an empty list
62
+ // by default so unrelated tests in this file aren't fail-closed by a
63
+ // 404 on that endpoint. The integration tests (`auth-hub-jwt.test.ts`)
64
+ // own the revoked-jti / fail-closed cases separately.
65
+ if (url.pathname === "/.well-known/parachute-revocation.json") {
66
+ return Response.json({ generated_at: new Date().toISOString(), jtis: [] });
67
+ }
68
+ return new Response("not found", { status: 404 });
61
69
  },
62
70
  });
63
71
  return {
@@ -121,6 +129,9 @@ beforeEach(() => {
121
129
  fixture.setUnreachable(false);
122
130
  fixture.setKeys([kp]);
123
131
  resetJwksCache();
132
+ // Drop the per-process revocation cache so each test starts cold against
133
+ // the fixture (an empty list by default; tests opt into populated lists).
134
+ resetRevocationCache();
124
135
  });
125
136
 
126
137
  describe("looksLikeJwt", () => {
package/src/hub-jwt.ts CHANGED
@@ -75,5 +75,14 @@ export function resetJwksCache(): void {
75
75
  guard.resetJwksCache();
76
76
  }
77
77
 
78
+ /**
79
+ * Reset the cached revocation list. Tests use this to start from a clean
80
+ * fail-closed state between cases; production callers shouldn't need it
81
+ * (the cache refreshes itself on TTL expiry).
82
+ */
83
+ export function resetRevocationCache(): void {
84
+ guard.resetRevocationCache();
85
+ }
86
+
78
87
  export { HubJwtError, looksLikeJwt };
79
88
  export type { HubJwtClaims, ValidateHubJwtOptions };
package/src/mcp-http.ts CHANGED
@@ -41,7 +41,6 @@ const TOOL_REQUIRED_VERB: Record<string, VaultVerb> = {
41
41
  "query-notes": "read",
42
42
  "list-tags": "read",
43
43
  "find-path": "read",
44
- "synthesize-notes": "read",
45
44
  "vault-info": "read",
46
45
  "create-note": "write",
47
46
  "update-note": "write",
@@ -58,7 +57,10 @@ function requiredVerbForTool(toolName: string): VaultVerb {
58
57
 
59
58
  /** Handle scoped MCP at /vault/{name}/mcp (single vault). */
60
59
  export async function handleScopedMcp(req: Request, vaultName: string, auth: AuthResult): Promise<Response> {
61
- const instruction = getServerInstruction(vaultName);
60
+ // Auth flows through to getServerInstruction so the connect-time
61
+ // markdown brief is filtered by `scoped_tags` — symmetric with the
62
+ // JSON `vault-info` wrapper.
63
+ const instruction = await getServerInstruction(vaultName, auth);
62
64
  return handleMcp(req, () => generateScopedMcpTools(vaultName, auth), `parachute-vault/${vaultName}`, vaultName, auth, instruction);
63
65
  }
64
66