protect-mcp 0.5.5 → 0.6.2

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 CHANGED
@@ -1,6 +1,78 @@
1
1
  # protect-mcp
2
2
 
3
- Enterprise security gateway for MCP servers and Claude Code hooks. Signed receipts, Cedar policies, and swarm-aware audit trails.
3
+ [![npm version](https://img.shields.io/npm/v/protect-mcp)](https://www.npmjs.com/package/protect-mcp)
4
+ [![Downloads](https://img.shields.io/npm/dm/protect-mcp)](https://www.npmjs.com/package/protect-mcp)
5
+ [![Claude Code Marketplace](https://img.shields.io/badge/Claude%20Code-marketplace-d97757?logo=anthropic&logoColor=white)](https://github.com/wshobson/agents/tree/main/plugins/protect-mcp)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
7
+
8
+ > A policy check that sits between your AI agent and the tools it calls.
9
+ > Every tool call is evaluated against a rule you wrote. Every decision is signed.
10
+
11
+ > **Receipt format:** `protect-mcp` emits Veritas Acta receipts. Legacy
12
+ > ScopeBlind receipts remain verifiable, but Acta v0.1 is the canonical
13
+ > format going forward. Spec: [`@veritasacta/protocol`](https://www.npmjs.com/package/@veritasacta/protocol)
14
+ > · IETF: [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/).
15
+
16
+ ## What it does, in plain English
17
+
18
+ When an AI agent (Claude Code, Cursor, a custom LangChain app, anything that
19
+ uses the Model Context Protocol) wants to run a command, edit a file, or call
20
+ an API, `protect-mcp` intercepts that request before it executes:
21
+
22
+ 1. **Checks a policy.** You write rules in [Cedar](https://www.cedarpolicy.com/)
23
+ — the same policy language AWS uses for IAM. Rules like *"never allow
24
+ `rm -rf /`"*, *"only allow `Bash` during working hours"*, or *"require
25
+ human approval for anything touching production."*
26
+ 2. **Returns a decision.** `allow`, `deny`, or `request_approval`. The agent
27
+ framework respects the decision — if it's `deny`, the tool call doesn't run.
28
+ 3. **Signs a receipt.** An Ed25519-signed, hash-chained record of the decision,
29
+ written to `.receipts/`. Verifiable offline by anyone with the public key.
30
+ When your auditor asks *"what did that agent do on 2026-03-14?"* you have
31
+ cryptographic proof — not a log file you might have tampered with.
32
+
33
+ You install it once. Your agent keeps working the same way. The difference:
34
+ every action it takes is now policy-checked and audit-evidenced.
35
+
36
+ ## Who this is for
37
+
38
+ - **Developers using Claude Code / Cursor / Cline** who want *"don't let the
39
+ agent delete my repo"* enforced rather than hoped for.
40
+ - **Security teams** shipping agents to engineering who need a portable
41
+ policy layer that travels across frameworks.
42
+ - **Compliance teams** who need tamper-evident evidence of what agents did,
43
+ verifiable without trusting the vendor.
44
+
45
+ ## How it relates to `sb-runtime`
46
+
47
+ `protect-mcp` is a **library** — it sits inside your agent framework and
48
+ gates tool calls cooperatively. Right tool when you own the agent's code and
49
+ trust its framework to honour decisions.
50
+
51
+ [`sb-runtime`](https://github.com/ScopeBlind/sb-runtime) is the companion
52
+ **binary** that wraps the whole agent *process* in an OS-level sandbox
53
+ (Landlock + seccomp on Linux). It enforces decisions at the kernel layer —
54
+ the agent can't ignore them even if it tried. Use `sb-runtime` when you're
55
+ running an agent you didn't write, or when you want belt-and-braces defence
56
+ in depth:
57
+
58
+ ```
59
+ ┌─────────────────────────────────────────────────┐
60
+ │ sb-runtime ← OS refuses forbidden syscalls │
61
+ │ ┌───────────────────────────────────────────┐ │
62
+ │ │ agent process (Claude Code, Python, …) │ │
63
+ │ │ ┌─────────────────────────────────────┐ │ │
64
+ │ │ │ protect-mcp │ │ │
65
+ │ │ │ ← Cedar decides per tool call, │ │ │
66
+ │ │ │ receipts every decision │ │ │
67
+ │ │ └─────────────────────────────────────┘ │ │
68
+ │ └───────────────────────────────────────────┘ │
69
+ └─────────────────────────────────────────────────┘
70
+ ```
71
+
72
+ **One-liner:** `protect-mcp` is the policy hook inside your agent.
73
+ `sb-runtime` is the OS sandbox around it. You want both.
74
+
75
+ ---
4
76
 
5
77
  ## Quick Start — Claude Code
6
78
 
@@ -268,6 +340,42 @@ Free tier: 20,000 receipts/month. No credit card required.
268
340
 
269
341
  [scopeblind.com/pricing](https://scopeblind.com/pricing)
270
342
 
343
+ ### ScopeBlind tenant integration (Founding Plan)
344
+
345
+ If you have a ScopeBlind Founding Plan tenant, set your `SCOPEBLIND_TOKEN`
346
+ (from the welcome email) in the environment and protect-mcp forwards every
347
+ signed receipt to your dashboard at `https://scopeblind.com/console/<your-slug>`.
348
+
349
+ ```bash
350
+ # Your protect-mcp install with cloud-synced receipts
351
+ SCOPEBLIND_TOKEN=scp_... \
352
+ npx protect-mcp init-hooks
353
+ ```
354
+
355
+ How it works:
356
+
357
+ 1. On first receipt, the bridge exchanges your token for a short-lived BRASS-v2
358
+ auth proof at `/fn/brass/issue` (ECDSA P-256 signed, hourly expiry).
359
+ 2. Receipts are batched and POSTed to `/fn/console/<slug>/receipts` every 5
360
+ seconds (up to 128 per batch).
361
+ 3. The local `.receipts/` chain remains authoritative regardless of forward
362
+ status. Network failures are retried; quota exhaustion is reported. No
363
+ crash, no blocking.
364
+
365
+ Quota: founding tier 10,000 receipts/day, enterprise 100,000/day. Anything
366
+ above quota is rejected with a structured response; receipts stay safe in
367
+ `.receipts/` until you upgrade or until tomorrow.
368
+
369
+ Receipt verification by third parties:
370
+
371
+ ```bash
372
+ # Anyone can verify your receipts offline using the public JWKS
373
+ curl https://scopeblind.com/.well-known/jwks.json
374
+ npx @veritasacta/verify .receipts/0001.json
375
+ ```
376
+
377
+ Verification is independent of ScopeBlind — the math doesn't care who runs it.
378
+
271
379
  ## Interoperability
272
380
 
273
381
  The receipt format is independently implemented and verified across multiple systems:
@@ -277,7 +385,7 @@ The receipt format is independently implemented and verified across multiple sys
277
385
  | **4 independent implementations** | TypeScript (protect-mcp), Python (protect-mcp-adk), Rust (Cedar WASM), APS ProxyGateway |
278
386
  | **2 IETF Internet-Drafts** | [draft-farley-acta-signed-receipts-01](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/), [draft-pidlisnyi-aps-00](https://datatracker.ietf.org/doc/draft-pidlisnyi-aps/) |
279
387
  | **8 cross-engine receipts** | [Composition test](https://github.com/ScopeBlind/examples/tree/main/interop/composition-test): 2 engines, 1 verifier, all VALID |
280
- | **1 enterprise integration** | [Microsoft AGT PR #667](https://github.com/microsoft/agent-governance-toolkit/pull/667) merged |
388
+ | **PRs merged into microsoft/agent-governance-toolkit** | [#667](https://github.com/microsoft/agent-governance-toolkit/pull/667), [#1159](https://github.com/microsoft/agent-governance-toolkit/pull/1159), [#1168](https://github.com/microsoft/agent-governance-toolkit/pull/1168), [#1186](https://github.com/microsoft/agent-governance-toolkit/pull/1186), [#1197](https://github.com/microsoft/agent-governance-toolkit/pull/1197), [#1202](https://github.com/microsoft/agent-governance-toolkit/pull/1202), [#1203](https://github.com/microsoft/agent-governance-toolkit/pull/1203), [#1205](https://github.com/microsoft/agent-governance-toolkit/pull/1205) |
281
389
  | **1 verifier, zero dependencies** | `npx @veritasacta/verify receipt.json --key <hex>` (Apache-2.0, offline) |
282
390
 
283
391
  Verify any receipt from any implementation:
@@ -291,7 +399,7 @@ npx @veritasacta/verify receipt.json --key <public-key-hex>
291
399
 
292
400
  - **IETF Internet-Draft**: [draft-farley-acta-signed-receipts-01](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)
293
401
  - **Patent Status**: 4 Australian provisional patents pending (2025-2026)
294
- - **Cedar WASM**: [PR #64](https://github.com/cedar-policy/cedar-for-agents/pull/64) on cedar-for-agents (under review)
402
+ - **Cedar WASM**: [PR #64](https://github.com/cedar-policy/cedar-for-agents/pull/64) merged + [PR #73](https://github.com/cedar-policy/cedar-for-agents/pull/73) (RequestGenerator, pending review)
295
403
 
296
404
  ## What's New in v0.5.3
297
405
 
@@ -11,13 +11,166 @@ import {
11
11
  loadPolicy,
12
12
  parseRateLimit,
13
13
  signDecision
14
- } from "./chunk-YTBC72JJ.mjs";
14
+ } from "./chunk-UV53U6D4.mjs";
15
15
 
16
16
  // src/hook-server.ts
17
17
  import { createServer } from "http";
18
18
  import { createHash, randomUUID, randomBytes } from "crypto";
19
19
  import { appendFileSync, readFileSync, existsSync, readdirSync } from "fs";
20
20
  import { join } from "path";
21
+
22
+ // src/scopeblind-bridge.ts
23
+ var DEFAULT_BASE = "https://scopeblind.com";
24
+ var FLUSH_INTERVAL_MS = 5e3;
25
+ var BATCH_MAX = 128;
26
+ var BRASS_REFRESH_MARGIN_MS = 5 * 60 * 1e3;
27
+ var ScopeBlindBridge = class {
28
+ token;
29
+ base;
30
+ tenantOverride;
31
+ cachedProof = null;
32
+ queue = [];
33
+ flushTimer = null;
34
+ stats;
35
+ shuttingDown = false;
36
+ constructor(env = process.env) {
37
+ this.token = env.SCOPEBLIND_TOKEN || null;
38
+ this.base = (env.SCOPEBLIND_BASE || DEFAULT_BASE).replace(/\/$/, "");
39
+ this.tenantOverride = env.SCOPEBLIND_TENANT || null;
40
+ this.stats = {
41
+ enabled: Boolean(this.token),
42
+ tenant_slug: this.tenantOverride,
43
+ forwarded_total: 0,
44
+ rejected_total: 0,
45
+ last_flush_at: null,
46
+ last_error: null
47
+ };
48
+ if (this.enabled()) {
49
+ this.flushTimer = setInterval(() => {
50
+ void this.flush();
51
+ }, FLUSH_INTERVAL_MS);
52
+ if (typeof this.flushTimer === "object" && this.flushTimer && "unref" in this.flushTimer) {
53
+ this.flushTimer.unref?.();
54
+ }
55
+ process.on("beforeExit", () => {
56
+ void this.shutdown();
57
+ });
58
+ }
59
+ }
60
+ enabled() {
61
+ return Boolean(this.token);
62
+ }
63
+ /** Push a signed receipt into the queue. Non-blocking. */
64
+ forward(signedReceipt) {
65
+ if (!this.enabled() || this.shuttingDown) return;
66
+ this.queue.push(signedReceipt);
67
+ if (this.queue.length >= BATCH_MAX) void this.flush();
68
+ }
69
+ /** Flush the queue. Safe to call concurrently. */
70
+ async flush() {
71
+ if (!this.enabled() || this.queue.length === 0) return;
72
+ const batch = this.queue.splice(0, BATCH_MAX);
73
+ try {
74
+ const proof = await this.ensureBrassProof();
75
+ const slug = this.tenantOverride || proof?.tenant_id;
76
+ if (!slug) {
77
+ this.queue.unshift(...batch);
78
+ return;
79
+ }
80
+ this.stats.tenant_slug = slug;
81
+ const res = await fetch(`${this.base}/fn/console/${slug}/receipts`, {
82
+ method: "POST",
83
+ headers: {
84
+ "content-type": "application/json",
85
+ authorization: `Bearer ${this.token}`,
86
+ "user-agent": "protect-mcp/scopeblind-bridge"
87
+ },
88
+ body: JSON.stringify({ receipts: batch })
89
+ });
90
+ if (!res.ok) {
91
+ const errBody = await res.text().catch(() => "");
92
+ this.stats.last_error = `HTTP ${res.status} ${errBody.slice(0, 160)}`;
93
+ this.stats.rejected_total += batch.length;
94
+ if (res.status >= 500 && res.status !== 503) {
95
+ this.queue.unshift(...batch);
96
+ }
97
+ return;
98
+ }
99
+ const body = await res.json().catch(() => ({}));
100
+ this.stats.forwarded_total += body?.accepted ?? batch.length;
101
+ this.stats.rejected_total += body?.rejected ?? 0;
102
+ this.stats.last_flush_at = (/* @__PURE__ */ new Date()).toISOString();
103
+ this.stats.last_error = null;
104
+ } catch (err) {
105
+ this.stats.last_error = String(err?.message || err);
106
+ this.queue.unshift(...batch);
107
+ }
108
+ }
109
+ /** Exchange SCOPEBLIND_TOKEN for a BRASS-v2 proof; refresh near expiry. */
110
+ async ensureBrassProof() {
111
+ if (!this.token) return null;
112
+ const now = Date.now();
113
+ if (this.cachedProof && Date.parse(this.cachedProof.expires_at) - now > BRASS_REFRESH_MARGIN_MS) {
114
+ return this.cachedProof;
115
+ }
116
+ try {
117
+ const res = await fetch(`${this.base}/fn/brass/issue`, {
118
+ method: "POST",
119
+ headers: {
120
+ "content-type": "application/json",
121
+ "user-agent": "protect-mcp/scopeblind-bridge"
122
+ },
123
+ body: JSON.stringify({
124
+ token: this.token,
125
+ scope: "protect-mcp-receipt-emit",
126
+ ttl_seconds: 3600
127
+ })
128
+ });
129
+ if (!res.ok) {
130
+ const text = await res.text().catch(() => "");
131
+ this.stats.last_error = `brass-issue: HTTP ${res.status} ${text.slice(0, 160)}`;
132
+ return null;
133
+ }
134
+ const body = await res.json();
135
+ if (!body?.auth_proof) {
136
+ this.stats.last_error = "brass-issue: missing auth_proof in response";
137
+ return null;
138
+ }
139
+ this.cachedProof = body.auth_proof;
140
+ return this.cachedProof;
141
+ } catch (err) {
142
+ this.stats.last_error = `brass-issue: ${err?.message || err}`;
143
+ return null;
144
+ }
145
+ }
146
+ /**
147
+ * Return a snapshot of bridge stats. Useful for `protect-mcp scopeblind status`.
148
+ */
149
+ getStats() {
150
+ return {
151
+ ...this.stats,
152
+ queued: this.queue.length,
153
+ brass_proof_expires_at: this.cachedProof?.expires_at || null
154
+ };
155
+ }
156
+ /** Flush remaining receipts and stop the interval. Called on process exit. */
157
+ async shutdown() {
158
+ if (this.shuttingDown) return;
159
+ this.shuttingDown = true;
160
+ if (this.flushTimer) clearInterval(this.flushTimer);
161
+ if (this.queue.length > 0) await this.flush();
162
+ }
163
+ };
164
+ var singleton = null;
165
+ function getScopeBlindBridge() {
166
+ if (!singleton) singleton = new ScopeBlindBridge();
167
+ return singleton;
168
+ }
169
+ function forwardReceipt(signedReceipt) {
170
+ getScopeBlindBridge().forward(signedReceipt);
171
+ }
172
+
173
+ // src/hook-server.ts
21
174
  var DEFAULT_PORT = 9377;
22
175
  var LOG_FILE = ".protect-mcp-log.jsonl";
23
176
  var RECEIPTS_FILE = ".protect-mcp-receipts.jsonl";
@@ -225,7 +378,7 @@ async function handlePreToolUse(input, state) {
225
378
  const hookLatency = Date.now() - hookStart;
226
379
  const denyKey = `${toolName}:${input.sessionId || "default"}`;
227
380
  state.denyCounter.delete(denyKey);
228
- emitDecisionLog(state, {
381
+ const emit = emitDecisionLog(state, {
229
382
  tool: toolName,
230
383
  decision: "allow",
231
384
  reason_code: state.cedarPolicies ? "cedar_allow" : state.jsonPolicy ? "policy_allow" : "observe_mode",
@@ -237,6 +390,15 @@ async function handlePreToolUse(input, state) {
237
390
  sandbox_state: detectSandboxState(),
238
391
  plan_receipt_id: state.activePlanReceiptId || void 0
239
392
  });
393
+ if (state.enforce && emit.signingFailed) {
394
+ return {
395
+ hookSpecificOutput: {
396
+ hookEventName: "PreToolUse",
397
+ permissionDecision: "deny",
398
+ permissionDecisionReason: `[ScopeBlind] "${toolName}" was blocked because its receipt could not be signed. Failing closed: a governed action that cannot be proven is not allowed.`
399
+ }
400
+ };
401
+ }
240
402
  return {};
241
403
  }
242
404
  async function handlePostToolUse(input, state) {
@@ -484,11 +646,35 @@ function emitDecisionLog(state, entry) {
484
646
  } catch {
485
647
  }
486
648
  state.receiptBuffer.add(log.request_id, signed.signed);
487
- } else if (signed.warning) {
488
- process.stderr.write(`[PROTECT_MCP] Warning: ${signed.warning}
649
+ try {
650
+ const bridge = getScopeBlindBridge();
651
+ if (bridge.enabled()) {
652
+ const parsed = typeof signed.signed === "string" ? JSON.parse(signed.signed) : signed.signed;
653
+ bridge.forward(parsed);
654
+ }
655
+ } catch (err) {
656
+ process.stderr.write(`[PROTECT_MCP] ScopeBlind forward error: ${err instanceof Error ? err.message : err}
657
+ `);
658
+ }
659
+ } else if (signed.error) {
660
+ const tombstone = JSON.stringify({
661
+ type: "scopeblind.signing_failure.v1",
662
+ request_id: log.request_id,
663
+ tool: log.tool,
664
+ decision: log.decision,
665
+ error: signed.error,
666
+ at: new Date(log.timestamp).toISOString()
667
+ });
668
+ try {
669
+ appendFileSync(state.receiptFilePath, tombstone + "\n");
670
+ } catch {
671
+ }
672
+ process.stderr.write(`[PROTECT_MCP_SIGNING_FAILURE] ${tombstone}
489
673
  `);
674
+ return { signingFailed: true };
490
675
  }
491
676
  }
677
+ return { signingFailed: false };
492
678
  }
493
679
  async function routeHookEvent(input, state) {
494
680
  switch (input.hookEventName) {
@@ -828,5 +1014,38 @@ function normalizeHookInput(raw) {
828
1014
  }
829
1015
 
830
1016
  export {
1017
+ ScopeBlindBridge,
1018
+ getScopeBlindBridge,
1019
+ forwardReceipt,
831
1020
  startHookServer
832
1021
  };
1022
+ /**
1023
+ * scopeblind-bridge.ts
1024
+ *
1025
+ * Optional bridge between protect-mcp (local, MIT) and a paid ScopeBlind
1026
+ * tenant. When SCOPEBLIND_TOKEN is set in the environment, every signed
1027
+ * receipt that protect-mcp emits also gets forwarded to the tenant's
1028
+ * dashboard at https://scopeblind.com/console/<slug>.
1029
+ *
1030
+ * Lifecycle:
1031
+ * 1. On first use, exchange SCOPEBLIND_TOKEN for a short-lived BRASS-v2
1032
+ * auth proof from /fn/brass/issue. Cache the proof in memory until
1033
+ * ~5 minutes before expiry, then refresh.
1034
+ * 2. As receipts are emitted by hook-server.ts, push them into an
1035
+ * in-memory batch queue.
1036
+ * 3. Flush the queue every 5s (or when it reaches 128 receipts) by POSTing
1037
+ * to /fn/console/<slug>/receipts with Bearer SCOPEBLIND_TOKEN.
1038
+ *
1039
+ * Failure mode: forward errors NEVER throw upstream. protect-mcp continues
1040
+ * to mint and persist receipts locally regardless of dashboard availability.
1041
+ * The bridge logs failures to stderr (best-effort) and retries on the next
1042
+ * flush.
1043
+ *
1044
+ * Configuration:
1045
+ * SCOPEBLIND_TOKEN Tenant bearer token (from welcome email).
1046
+ * SCOPEBLIND_TENANT Optional slug override. By default we discover
1047
+ * the slug from the BRASS proof's tenant_id.
1048
+ * SCOPEBLIND_BASE Defaults to https://scopeblind.com.
1049
+ *
1050
+ * @license MIT
1051
+ */
@@ -12,10 +12,10 @@ import {
12
12
  hexToBytes,
13
13
  isBytes,
14
14
  randomBytes,
15
+ rotr,
15
16
  toBytes,
16
17
  utf8ToBytes
17
18
  } from "./chunk-D733KAPG.mjs";
18
- import "./chunk-PQJP2ZCI.mjs";
19
19
 
20
20
  // node_modules/@noble/hashes/esm/_md.js
21
21
  function setBigUint64(view, byteOffset, value, isLE) {
@@ -30,6 +30,12 @@ function setBigUint64(view, byteOffset, value, isLE) {
30
30
  view.setUint32(byteOffset + h, wh, isLE);
31
31
  view.setUint32(byteOffset + l, wl, isLE);
32
32
  }
33
+ function Chi(a, b, c) {
34
+ return a & b ^ ~a & c;
35
+ }
36
+ function Maj(a, b, c) {
37
+ return a & b ^ a & c ^ b & c;
38
+ }
33
39
  var HashMD = class extends Hash {
34
40
  constructor(blockLen, outputLen, padOffset, isLE) {
35
41
  super();
@@ -120,6 +126,16 @@ var HashMD = class extends Hash {
120
126
  return this._cloneInto();
121
127
  }
122
128
  };
129
+ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
130
+ 1779033703,
131
+ 3144134277,
132
+ 1013904242,
133
+ 2773480762,
134
+ 1359893119,
135
+ 2600822924,
136
+ 528734635,
137
+ 1541459225
138
+ ]);
123
139
  var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
124
140
  1779033703,
125
141
  4089235720,
@@ -175,6 +191,143 @@ var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >
175
191
  var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
176
192
 
177
193
  // node_modules/@noble/hashes/esm/sha2.js
194
+ var SHA256_K = /* @__PURE__ */ Uint32Array.from([
195
+ 1116352408,
196
+ 1899447441,
197
+ 3049323471,
198
+ 3921009573,
199
+ 961987163,
200
+ 1508970993,
201
+ 2453635748,
202
+ 2870763221,
203
+ 3624381080,
204
+ 310598401,
205
+ 607225278,
206
+ 1426881987,
207
+ 1925078388,
208
+ 2162078206,
209
+ 2614888103,
210
+ 3248222580,
211
+ 3835390401,
212
+ 4022224774,
213
+ 264347078,
214
+ 604807628,
215
+ 770255983,
216
+ 1249150122,
217
+ 1555081692,
218
+ 1996064986,
219
+ 2554220882,
220
+ 2821834349,
221
+ 2952996808,
222
+ 3210313671,
223
+ 3336571891,
224
+ 3584528711,
225
+ 113926993,
226
+ 338241895,
227
+ 666307205,
228
+ 773529912,
229
+ 1294757372,
230
+ 1396182291,
231
+ 1695183700,
232
+ 1986661051,
233
+ 2177026350,
234
+ 2456956037,
235
+ 2730485921,
236
+ 2820302411,
237
+ 3259730800,
238
+ 3345764771,
239
+ 3516065817,
240
+ 3600352804,
241
+ 4094571909,
242
+ 275423344,
243
+ 430227734,
244
+ 506948616,
245
+ 659060556,
246
+ 883997877,
247
+ 958139571,
248
+ 1322822218,
249
+ 1537002063,
250
+ 1747873779,
251
+ 1955562222,
252
+ 2024104815,
253
+ 2227730452,
254
+ 2361852424,
255
+ 2428436474,
256
+ 2756734187,
257
+ 3204031479,
258
+ 3329325298
259
+ ]);
260
+ var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
261
+ var SHA256 = class extends HashMD {
262
+ constructor(outputLen = 32) {
263
+ super(64, outputLen, 8, false);
264
+ this.A = SHA256_IV[0] | 0;
265
+ this.B = SHA256_IV[1] | 0;
266
+ this.C = SHA256_IV[2] | 0;
267
+ this.D = SHA256_IV[3] | 0;
268
+ this.E = SHA256_IV[4] | 0;
269
+ this.F = SHA256_IV[5] | 0;
270
+ this.G = SHA256_IV[6] | 0;
271
+ this.H = SHA256_IV[7] | 0;
272
+ }
273
+ get() {
274
+ const { A, B, C, D, E, F, G, H } = this;
275
+ return [A, B, C, D, E, F, G, H];
276
+ }
277
+ // prettier-ignore
278
+ set(A, B, C, D, E, F, G, H) {
279
+ this.A = A | 0;
280
+ this.B = B | 0;
281
+ this.C = C | 0;
282
+ this.D = D | 0;
283
+ this.E = E | 0;
284
+ this.F = F | 0;
285
+ this.G = G | 0;
286
+ this.H = H | 0;
287
+ }
288
+ process(view, offset) {
289
+ for (let i = 0; i < 16; i++, offset += 4)
290
+ SHA256_W[i] = view.getUint32(offset, false);
291
+ for (let i = 16; i < 64; i++) {
292
+ const W15 = SHA256_W[i - 15];
293
+ const W2 = SHA256_W[i - 2];
294
+ const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
295
+ const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
296
+ SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
297
+ }
298
+ let { A, B, C, D, E, F, G, H } = this;
299
+ for (let i = 0; i < 64; i++) {
300
+ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
301
+ const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
302
+ const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
303
+ const T2 = sigma0 + Maj(A, B, C) | 0;
304
+ H = G;
305
+ G = F;
306
+ F = E;
307
+ E = D + T1 | 0;
308
+ D = C;
309
+ C = B;
310
+ B = A;
311
+ A = T1 + T2 | 0;
312
+ }
313
+ A = A + this.A | 0;
314
+ B = B + this.B | 0;
315
+ C = C + this.C | 0;
316
+ D = D + this.D | 0;
317
+ E = E + this.E | 0;
318
+ F = F + this.F | 0;
319
+ G = G + this.G | 0;
320
+ H = H + this.H | 0;
321
+ this.set(A, B, C, D, E, F, G, H);
322
+ }
323
+ roundClean() {
324
+ clean(SHA256_W);
325
+ }
326
+ destroy() {
327
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
328
+ clean(this.buffer);
329
+ }
330
+ };
178
331
  var K512 = /* @__PURE__ */ (() => split([
179
332
  "0x428a2f98d728ae22",
180
333
  "0x7137449123ef65cd",
@@ -372,6 +525,7 @@ var SHA512 = class extends HashMD {
372
525
  this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
373
526
  }
374
527
  };
528
+ var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
375
529
  var sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
376
530
 
377
531
  // node_modules/@noble/curves/esm/utils.js
@@ -2260,23 +2414,25 @@ var hashToCurve = /* @__PURE__ */ (() => ed25519_hasher.hashToCurve)();
2260
2414
  var encodeToCurve = /* @__PURE__ */ (() => ed25519_hasher.encodeToCurve)();
2261
2415
  var hashToRistretto255 = /* @__PURE__ */ (() => ristretto255_hasher.hashToCurve)();
2262
2416
  var hash_to_ristretto255 = /* @__PURE__ */ (() => ristretto255_hasher.hashToCurve)();
2417
+
2263
2418
  export {
2264
- ED25519_TORSION_SUBGROUP,
2265
- RistrettoPoint,
2419
+ sha256,
2266
2420
  ed25519,
2267
- ed25519_hasher,
2268
2421
  ed25519ctx,
2269
2422
  ed25519ph,
2423
+ x25519,
2424
+ ed25519_hasher,
2425
+ ristretto255,
2426
+ ristretto255_hasher,
2427
+ ED25519_TORSION_SUBGROUP,
2428
+ edwardsToMontgomeryPub,
2270
2429
  edwardsToMontgomery,
2271
2430
  edwardsToMontgomeryPriv,
2272
- edwardsToMontgomeryPub,
2273
- encodeToCurve,
2431
+ RistrettoPoint,
2274
2432
  hashToCurve,
2433
+ encodeToCurve,
2275
2434
  hashToRistretto255,
2276
- hash_to_ristretto255,
2277
- ristretto255,
2278
- ristretto255_hasher,
2279
- x25519
2435
+ hash_to_ristretto255
2280
2436
  };
2281
2437
  /*! Bundled license information:
2282
2438
 
@@ -7,7 +7,7 @@ import {
7
7
  parseRateLimit,
8
8
  signDecision,
9
9
  startStatusServer
10
- } from "./chunk-YTBC72JJ.mjs";
10
+ } from "./chunk-UV53U6D4.mjs";
11
11
 
12
12
  // src/evidence-store.ts
13
13
  import { readFileSync, writeFileSync, existsSync } from "fs";
@@ -974,8 +974,20 @@ var ProtectGateway = class {
974
974
  this.evidenceStore.save();
975
975
  }
976
976
  }
977
- } else if (signed.warning) {
978
- process.stderr.write(`[PROTECT_MCP] Warning: ${signed.warning}
977
+ } else if (signed.error) {
978
+ const tombstone = JSON.stringify({
979
+ type: "scopeblind.signing_failure.v1",
980
+ request_id: log.request_id,
981
+ tool: log.tool,
982
+ decision: log.decision,
983
+ error: signed.error,
984
+ at: new Date(log.timestamp).toISOString()
985
+ });
986
+ try {
987
+ appendFileSync(this.receiptFilePath, tombstone + "\n");
988
+ } catch {
989
+ }
990
+ process.stderr.write(`[PROTECT_MCP_SIGNING_FAILURE] ${tombstone}
979
991
  `);
980
992
  }
981
993
  }
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  meetsMinTier
3
- } from "./chunk-BYYWYSHM.mjs";
3
+ } from "./chunk-PLKRTBDR.mjs";
4
4
  import {
5
5
  checkRateLimit,
6
6
  getToolPolicy,
7
7
  parseRateLimit
8
- } from "./chunk-YTBC72JJ.mjs";
8
+ } from "./chunk-UV53U6D4.mjs";
9
9
 
10
10
  // src/simulate.ts
11
11
  import { readFileSync } from "fs";