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.
- package/CHANGELOG.md +64 -0
- package/README.md +2 -2
- package/dist/{chunk-6E2DHBAR.mjs → chunk-744JMCY4.mjs} +9 -1
- package/dist/chunk-AYNQIEN7.mjs +10 -0
- package/dist/{chunk-JCMDLN5I.mjs → chunk-CXW2EIRM.mjs} +2 -2
- package/dist/{chunk-LJQOALYR.mjs → chunk-FFVJL3KQ.mjs} +5 -564
- package/dist/{chunk-VTPZ4G5I.mjs → chunk-GHR65WVD.mjs} +1 -1
- package/dist/{chunk-WIPWNWMJ.mjs → chunk-IDUH2O4Q.mjs} +1 -0
- package/dist/chunk-JIDDQUSQ.mjs +568 -0
- package/dist/chunk-KMNXHGGT.mjs +94 -0
- package/dist/{chunk-WV4DKYE4.mjs → chunk-UWB5ALVO.mjs} +10 -12
- package/dist/claim-TUDH2WPB.mjs +201 -0
- package/dist/cli.js +610 -31
- package/dist/cli.mjs +308 -24
- package/dist/{ed25519-BSHMMVNX.mjs → ed25519-SQA3S2RV.mjs} +2 -1
- package/dist/hook-server.js +428 -0
- package/dist/hook-server.mjs +6 -2
- package/dist/{http-transport-JBORN27G.mjs → http-transport-D7C64PIA.mjs} +2 -2
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +107 -18
- package/dist/index.mjs +14 -9
- package/dist/{signing-committed-QXCW24RF.mjs → signing-committed-TGWXSLAO.mjs} +4 -2
- package/package.json +1 -1
- package/policies/agent.cedar +0 -50
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ed25519,
|
|
3
2
|
sha256
|
|
4
|
-
} from "./chunk-
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
+
};
|