protect-mcp 0.7.6 → 0.9.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.
@@ -1,7 +1,9 @@
1
1
  import {
2
- ed25519,
3
2
  sha256
4
- } from "./chunk-LJQOALYR.mjs";
3
+ } from "./chunk-AYNQIEN7.mjs";
4
+ import {
5
+ ed25519
6
+ } from "./chunk-FFVJL3KQ.mjs";
5
7
  import {
6
8
  Hash,
7
9
  abytes,
@@ -14,9 +16,6 @@ import {
14
16
  toBytes
15
17
  } from "./chunk-D733KAPG.mjs";
16
18
 
17
- // node_modules/@noble/hashes/esm/sha256.js
18
- var sha2562 = sha256;
19
-
20
19
  // src/commitments/merkle.ts
21
20
  var DOMAIN_LEAF = 0;
22
21
  var DOMAIN_INTERNAL = 1;
@@ -24,14 +23,14 @@ function hashLeaf(leafBytes) {
24
23
  const buf = new Uint8Array(leafBytes.length + 1);
25
24
  buf[0] = DOMAIN_LEAF;
26
25
  buf.set(leafBytes, 1);
27
- return sha2562(buf);
26
+ return sha256(buf);
28
27
  }
29
28
  function hashInternal(left, right) {
30
29
  const buf = new Uint8Array(left.length + right.length + 1);
31
30
  buf[0] = DOMAIN_INTERNAL;
32
31
  buf.set(left, 1);
33
32
  buf.set(right, 1 + left.length);
34
- return sha2562(buf);
33
+ return sha256(buf);
35
34
  }
36
35
  function merkleRoot(leafHashes) {
37
36
  if (leafHashes.length === 0) {
@@ -315,7 +314,7 @@ function signCommittedDecision(entry, committedFieldNames, signingKey, publicKey
315
314
  payload.committed_field_names = committedFields.map((f) => f.name);
316
315
  }
317
316
  const canonical = jcs(payload);
318
- const messageHash = sha2562(new TextEncoder().encode(canonical));
317
+ const messageHash = sha256(new TextEncoder().encode(canonical));
319
318
  const signatureBytes = ed25519.sign(messageHash, hexToBytes(signingKey));
320
319
  const signedReceipt = {
321
320
  ...payload,
@@ -329,7 +328,7 @@ function signCommittedDecision(entry, committedFieldNames, signingKey, publicKey
329
328
  }
330
329
  };
331
330
  const signedJson = JSON.stringify(signedReceipt);
332
- const receiptHash = bytesToHex(sha2562(new TextEncoder().encode(jcs(signedReceipt))));
331
+ const receiptHash = bytesToHex(sha256(new TextEncoder().encode(jcs(signedReceipt))));
333
332
  return {
334
333
  signed: signedJson,
335
334
  artifact_type: "decision_receipt_committed_v1",
@@ -458,7 +457,7 @@ function committedFieldNamesFromReceipt(receipt, openings) {
458
457
  return Array.from(new Set(names)).sort();
459
458
  }
460
459
  function receiptHashHex(receipt) {
461
- return bytesToHex(sha2562(new TextEncoder().encode(jcs(receipt))));
460
+ return bytesToHex(sha256(new TextEncoder().encode(jcs(receipt))));
462
461
  }
463
462
  function verifyCommittedReceiptSignature(receipt) {
464
463
  const signature = receipt.signature;
@@ -468,7 +467,7 @@ function verifyCommittedReceiptSignature(receipt) {
468
467
  return null;
469
468
  }
470
469
  const { signature: _signature, ...payloadWithoutSig } = receipt;
471
- const messageHash = sha2562(new TextEncoder().encode(jcs(payloadWithoutSig)));
470
+ const messageHash = sha256(new TextEncoder().encode(jcs(payloadWithoutSig)));
472
471
  try {
473
472
  return ed25519.verify(base64urlDecode(sig.sig), messageHash, hexToBytes(sig.public_key));
474
473
  } catch {
@@ -477,7 +476,6 @@ function verifyCommittedReceiptSignature(receipt) {
477
476
  }
478
477
 
479
478
  export {
480
- sha2562 as sha256,
481
479
  hmac,
482
480
  signCommittedDecision,
483
481
  discloseField,
@@ -0,0 +1,201 @@
1
+ import {
2
+ canonicalJson
3
+ } from "./chunk-KMNXHGGT.mjs";
4
+ import {
5
+ sha256
6
+ } from "./chunk-AYNQIEN7.mjs";
7
+ import {
8
+ ed25519
9
+ } from "./chunk-FFVJL3KQ.mjs";
10
+ import "./chunk-JIDDQUSQ.mjs";
11
+ import {
12
+ bytesToHex,
13
+ hexToBytes
14
+ } from "./chunk-D733KAPG.mjs";
15
+ import "./chunk-PQJP2ZCI.mjs";
16
+
17
+ // src/claim.ts
18
+ var CLAIM_TYPE = "scopeblind.claim.v1";
19
+ function sha256Hex(input) {
20
+ const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
21
+ return bytesToHex(sha256(bytes));
22
+ }
23
+ function receiptToLeaf(e) {
24
+ const p = e && typeof e.payload === "object" && e.payload || e;
25
+ const dec = String(p.decision || e.decision || "").toLowerCase();
26
+ const v = /den|block|reject|refus/.test(dec) ? "blocked" : /ask|approv|hold|escal|review|pending/.test(dec) ? "held" : "allowed";
27
+ const enr = p.enrichment;
28
+ const c = enr && Array.isArray(enr.capabilities) ? enr.capabilities.map(String).sort() : [];
29
+ const tsRaw = e.issued_at || p.timestamp || p.issued_at;
30
+ const ms = typeof tsRaw === "number" ? tsRaw : typeof tsRaw === "string" ? Date.parse(tsRaw) : NaN;
31
+ const t = isFinite(ms) ? new Date(ms).toISOString() : "";
32
+ const d = sha256Hex(canonicalJson(e));
33
+ return { d, v, c, t };
34
+ }
35
+ function leafHash(leaf) {
36
+ return sha256Hex(canonicalJson(leaf));
37
+ }
38
+ function merkleRoot(leafHashes) {
39
+ if (leafHashes.length === 0) return sha256Hex("scopeblind.claim.empty");
40
+ let level = [...leafHashes].sort();
41
+ while (level.length > 1) {
42
+ const next = [];
43
+ for (let i = 0; i < level.length; i += 2) {
44
+ const a = level[i];
45
+ const b = i + 1 < level.length ? level[i + 1] : level[i];
46
+ next.push(sha256Hex(a + b));
47
+ }
48
+ level = next;
49
+ }
50
+ return level[0];
51
+ }
52
+ function evaluate(pred, leaves) {
53
+ if (pred.kind === "no_capability") {
54
+ const matched2 = leaves.filter((l) => l.c.indexOf(pred.capability) >= 0).length;
55
+ return { statement: `No action used capability "${pred.capability}"`, holds: matched2 === 0, matched: matched2 };
56
+ }
57
+ if (pred.kind === "only_capabilities") {
58
+ const allow = new Set(pred.capabilities);
59
+ const matched2 = leaves.filter((l) => !l.c.every((c) => allow.has(c))).length;
60
+ return { statement: `All actions were confined to capabilities {${pred.capabilities.join(", ")}}`, holds: matched2 === 0, matched: matched2 };
61
+ }
62
+ if (pred.kind === "no_verdict") {
63
+ const matched2 = leaves.filter((l) => l.v === pred.verdict).length;
64
+ return { statement: `No action was ${pred.verdict}`, holds: matched2 === 0, matched: matched2 };
65
+ }
66
+ const matched = leaves.filter((l) => l.v === pred.verdict).length;
67
+ return { statement: `${matched} action${matched === 1 ? " was" : "s were"} ${pred.verdict}`, holds: true, matched };
68
+ }
69
+ function messageHash(unsigned) {
70
+ return sha256(new TextEncoder().encode(canonicalJson(unsigned)));
71
+ }
72
+ function buildClaim(receipts, predicate, key, issuedAt) {
73
+ const leaves = receipts.map(receiptToLeaf);
74
+ const root = merkleRoot(leaves.map(leafHash));
75
+ const claim = evaluate(predicate, leaves);
76
+ const times = leaves.map((l) => l.t).filter(Boolean).sort();
77
+ const unsigned = {
78
+ type: CLAIM_TYPE,
79
+ predicate,
80
+ claim,
81
+ scope: { total: leaves.length, from: times[0] || "", to: times[times.length - 1] || "" },
82
+ record: { root },
83
+ leaves,
84
+ issuer: { kid: key.kid, publicKey: key.publicKey, issuer: key.issuer || "protect-mcp" },
85
+ issued_at: issuedAt
86
+ };
87
+ const signature = bytesToHex(ed25519.sign(messageHash(unsigned), hexToBytes(key.privateKey)));
88
+ return { ...unsigned, signature };
89
+ }
90
+ function verifyClaim(pack, overridePublicKey) {
91
+ const reasons = [];
92
+ const leaves = Array.isArray(pack.leaves) ? pack.leaves : [];
93
+ const recomputedRoot = merkleRoot(leaves.map(leafHash));
94
+ const root_ok = !!pack.record && recomputedRoot === pack.record.root;
95
+ if (!root_ok) reasons.push("record commitment (Merkle root) does not match the disclosed decisions");
96
+ const recomputed = evaluate(pack.predicate, leaves);
97
+ const predicate_ok = !!pack.claim && recomputed.holds === pack.claim.holds && recomputed.matched === pack.claim.matched;
98
+ if (!predicate_ok) reasons.push("claim result does not match the predicate recomputed over the disclosed decisions");
99
+ let authentic = false;
100
+ try {
101
+ const { signature, ...unsigned } = pack;
102
+ const pub = overridePublicKey || pack.issuer && pack.issuer.publicKey;
103
+ if (pub && signature) {
104
+ authentic = ed25519.verify(hexToBytes(signature), messageHash(unsigned), hexToBytes(pub));
105
+ }
106
+ } catch {
107
+ }
108
+ if (!authentic) reasons.push("signature does not verify against the issuer public key");
109
+ return {
110
+ valid: authentic && root_ok && predicate_ok,
111
+ authentic,
112
+ root_ok,
113
+ predicate_ok,
114
+ holds: !!(pack.claim && pack.claim.holds),
115
+ matched: pack.claim ? pack.claim.matched : recomputed.matched,
116
+ total: leaves.length,
117
+ statement: pack.claim ? pack.claim.statement : recomputed.statement,
118
+ reasons
119
+ };
120
+ }
121
+ var ANCHOR_SCHEMA = "scopeblind.protect-mcp.anchor.v1";
122
+ var DEFAULT_LOG = "https://scopeblind.com";
123
+ function anchorDeepSort(o) {
124
+ if (o === null || typeof o !== "object") return o;
125
+ if (Array.isArray(o)) return o.map(anchorDeepSort);
126
+ const src = o;
127
+ const out = {};
128
+ for (const k of Object.keys(src).sort()) out[k] = anchorDeepSort(src[k]);
129
+ return out;
130
+ }
131
+ function toBase64(bytes) {
132
+ let bin = "";
133
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
134
+ return btoa(bin);
135
+ }
136
+ function claimDigest(pack) {
137
+ return sha256Hex(canonicalJson(pack));
138
+ }
139
+ function buildAnchorEnvelope(pack, key, issuedAt) {
140
+ const signed = {
141
+ type: "evidence_pack",
142
+ schema: ANCHOR_SCHEMA,
143
+ anchors: "protect-mcp-claim",
144
+ claim_digest: claimDigest(pack),
145
+ record_root: pack.record.root,
146
+ statement: pack.claim.statement,
147
+ holds: pack.claim.holds,
148
+ matched: pack.claim.matched,
149
+ total: pack.scope.total,
150
+ issued_at: issuedAt,
151
+ verification_key: key.publicKey,
152
+ disclosure: "internal"
153
+ };
154
+ const hash = sha256(new TextEncoder().encode(JSON.stringify(anchorDeepSort(signed))));
155
+ const digest = bytesToHex(hash);
156
+ const signature = bytesToHex(ed25519.sign(hash, hexToBytes(key.privateKey)));
157
+ return { ...signed, signature, digest };
158
+ }
159
+ async function anchorClaim(pack, key, opts) {
160
+ const envelope = buildAnchorEnvelope(pack, key, opts.issuedAt);
161
+ const base = (opts.log || DEFAULT_LOG).replace(/\/+$/, "");
162
+ const doFetch = opts.fetchImpl || globalThis.fetch;
163
+ if (!doFetch) return { ok: false, claim_digest: envelope.claim_digest, error: "fetch_unavailable", envelope };
164
+ const encoded = toBase64(new TextEncoder().encode(JSON.stringify(envelope)));
165
+ try {
166
+ const resp = await doFetch(`${base}/fn/log/anchor-pack`, {
167
+ method: "POST",
168
+ headers: { "content-type": "application/json" },
169
+ body: JSON.stringify({ encoded })
170
+ });
171
+ const data = await resp.json().catch(() => null);
172
+ if (!resp.ok || !data || !data.ok || typeof data.seq !== "number") {
173
+ return { ok: false, claim_digest: envelope.claim_digest, error: data && data.error || `http_${resp.status}`, envelope };
174
+ }
175
+ return {
176
+ ok: true,
177
+ claim_digest: envelope.claim_digest,
178
+ seq: data.seq,
179
+ entry_url: `${base}/fn/log/${data.seq}`,
180
+ anchored_at: data.anchored_at,
181
+ already_anchored: !!data.already_anchored,
182
+ envelope
183
+ };
184
+ } catch {
185
+ return { ok: false, claim_digest: envelope.claim_digest, error: "network_error", envelope };
186
+ }
187
+ }
188
+ export {
189
+ ANCHOR_SCHEMA,
190
+ CLAIM_TYPE,
191
+ DEFAULT_LOG,
192
+ anchorClaim,
193
+ buildAnchorEnvelope,
194
+ buildClaim,
195
+ claimDigest,
196
+ evaluate,
197
+ leafHash,
198
+ merkleRoot,
199
+ receiptToLeaf,
200
+ verifyClaim
201
+ };