protect-mcp 0.7.2 → 0.7.4
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 +69 -0
- package/README.md +157 -9
- package/dist/{bundle-XTR3YMPO.mjs → bundle-YUUHAG7O.mjs} +1 -1
- package/dist/chunk-36UID5WY.mjs +662 -0
- package/dist/{chunk-546U3A7R.mjs → chunk-D2RDY2JR.mjs} +92 -1
- package/dist/chunk-F2FKQ4XN.mjs +410 -0
- package/dist/{chunk-J6L4XCTE.mjs → chunk-KPSICBAJ.mjs} +75 -0
- package/dist/{chunk-X63ELMU4.mjs → chunk-NVJHGXXG.mjs} +37 -7
- package/dist/{chunk-5JXFV37Y.mjs → chunk-PM2ZO57M.mjs} +10 -2
- package/dist/{chunk-OHUTUFTC.mjs → chunk-UBZJ3VI2.mjs} +19 -15
- package/dist/cli.js +4034 -502
- package/dist/cli.mjs +2028 -9
- package/dist/demo-server.d.mts +3 -0
- package/dist/demo-server.d.ts +3 -0
- package/dist/demo-server.js +75 -0
- package/dist/demo-server.mjs +1 -1
- package/dist/hook-server.js +144 -25
- package/dist/hook-server.mjs +2 -2
- package/dist/{http-transport-R5AO7X6D.mjs → http-transport-4GMMRPW7.mjs} +2 -2
- package/dist/index.d.mts +115 -1
- package/dist/index.d.ts +115 -1
- package/dist/index.js +988 -80
- package/dist/index.mjs +48 -249
- package/dist/receipt-registry-6CAOY6RP.mjs +279 -0
- package/dist/signing-committed-MMLJ6OGG.mjs +15 -0
- package/package.json +1 -1
- package/dist/chunk-ZBKJANP7.mjs +0 -145
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectSignedReceipts,
|
|
3
|
+
createAuditBundle
|
|
4
|
+
} from "./chunk-PM2ZO57M.mjs";
|
|
5
|
+
import {
|
|
6
|
+
createSelectiveDisclosurePackage,
|
|
7
|
+
discloseField,
|
|
8
|
+
signCommittedDecision,
|
|
9
|
+
verifySelectiveDisclosurePackage
|
|
10
|
+
} from "./chunk-F2FKQ4XN.mjs";
|
|
1
11
|
import {
|
|
2
12
|
formatReportMarkdown,
|
|
3
13
|
generateReport
|
|
4
14
|
} from "./chunk-JQDVKZBN.mjs";
|
|
5
15
|
import {
|
|
16
|
+
CONNECTOR_PILOTS,
|
|
17
|
+
POLICY_PACKS,
|
|
18
|
+
connectorDirectory,
|
|
19
|
+
connectorDoctor,
|
|
20
|
+
connectorPilotIds,
|
|
6
21
|
formatSimulation,
|
|
22
|
+
getConnectorPilot,
|
|
23
|
+
getPolicyPack,
|
|
7
24
|
parseLogFile,
|
|
8
|
-
|
|
9
|
-
|
|
25
|
+
policyPackIds,
|
|
26
|
+
readInstalledConnectorPilots,
|
|
27
|
+
simulate,
|
|
28
|
+
writeConnectorPilots
|
|
29
|
+
} from "./chunk-36UID5WY.mjs";
|
|
10
30
|
import {
|
|
11
31
|
ProtectGateway,
|
|
12
32
|
buildDecisionContext,
|
|
@@ -18,10 +38,10 @@ import {
|
|
|
18
38
|
resolveCredential,
|
|
19
39
|
sendApprovalNotification,
|
|
20
40
|
validateCredentials
|
|
21
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-UBZJ3VI2.mjs";
|
|
22
42
|
import {
|
|
23
43
|
createSandboxServer
|
|
24
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-KPSICBAJ.mjs";
|
|
25
45
|
import {
|
|
26
46
|
BUILTIN_PATTERNS,
|
|
27
47
|
generateHookSettings,
|
|
@@ -33,7 +53,7 @@ import {
|
|
|
33
53
|
forwardReceipt,
|
|
34
54
|
getScopeBlindBridge,
|
|
35
55
|
startHookServer
|
|
36
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-NVJHGXXG.mjs";
|
|
37
57
|
import {
|
|
38
58
|
checkRateLimit,
|
|
39
59
|
evaluateCedar,
|
|
@@ -48,244 +68,11 @@ import {
|
|
|
48
68
|
policySetFromSource,
|
|
49
69
|
runEvaluatorSelfTest,
|
|
50
70
|
signDecision
|
|
51
|
-
} from "./chunk-
|
|
52
|
-
import
|
|
53
|
-
|
|
54
|
-
sha256
|
|
55
|
-
} from "./chunk-LYKNULYU.mjs";
|
|
56
|
-
import {
|
|
57
|
-
bytesToHex,
|
|
58
|
-
hexToBytes,
|
|
59
|
-
randomBytes
|
|
60
|
-
} from "./chunk-D733KAPG.mjs";
|
|
61
|
-
import {
|
|
62
|
-
collectSignedReceipts,
|
|
63
|
-
createAuditBundle
|
|
64
|
-
} from "./chunk-5JXFV37Y.mjs";
|
|
71
|
+
} from "./chunk-D2RDY2JR.mjs";
|
|
72
|
+
import "./chunk-LYKNULYU.mjs";
|
|
73
|
+
import "./chunk-D733KAPG.mjs";
|
|
65
74
|
import "./chunk-PQJP2ZCI.mjs";
|
|
66
75
|
|
|
67
|
-
// node_modules/@noble/hashes/esm/sha256.js
|
|
68
|
-
var sha2562 = sha256;
|
|
69
|
-
|
|
70
|
-
// src/commitments/merkle.ts
|
|
71
|
-
var DOMAIN_LEAF = 0;
|
|
72
|
-
var DOMAIN_INTERNAL = 1;
|
|
73
|
-
function hashLeaf(leafBytes) {
|
|
74
|
-
const buf = new Uint8Array(leafBytes.length + 1);
|
|
75
|
-
buf[0] = DOMAIN_LEAF;
|
|
76
|
-
buf.set(leafBytes, 1);
|
|
77
|
-
return sha2562(buf);
|
|
78
|
-
}
|
|
79
|
-
function hashInternal(left, right) {
|
|
80
|
-
const buf = new Uint8Array(left.length + right.length + 1);
|
|
81
|
-
buf[0] = DOMAIN_INTERNAL;
|
|
82
|
-
buf.set(left, 1);
|
|
83
|
-
buf.set(right, 1 + left.length);
|
|
84
|
-
return sha2562(buf);
|
|
85
|
-
}
|
|
86
|
-
function merkleRoot(leafHashes) {
|
|
87
|
-
if (leafHashes.length === 0) {
|
|
88
|
-
throw new Error("merkleRoot: cannot compute root of empty leaf set");
|
|
89
|
-
}
|
|
90
|
-
if (leafHashes.length === 1) {
|
|
91
|
-
return leafHashes[0];
|
|
92
|
-
}
|
|
93
|
-
const n = leafHashes.length;
|
|
94
|
-
const k = largestPowerOfTwoLessThan(n);
|
|
95
|
-
const left = merkleRoot(leafHashes.slice(0, k));
|
|
96
|
-
const right = merkleRoot(leafHashes.slice(k));
|
|
97
|
-
return hashInternal(left, right);
|
|
98
|
-
}
|
|
99
|
-
function generateProof(leafHashes, index) {
|
|
100
|
-
if (leafHashes.length === 0) {
|
|
101
|
-
throw new Error("generateProof: empty tree");
|
|
102
|
-
}
|
|
103
|
-
if (index < 0 || index >= leafHashes.length) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`generateProof: index ${index} out of range [0, ${leafHashes.length})`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
const siblings = [];
|
|
109
|
-
collectPath(leafHashes, index, siblings);
|
|
110
|
-
return {
|
|
111
|
-
index,
|
|
112
|
-
treeSize: leafHashes.length,
|
|
113
|
-
siblings: siblings.map((s) => bytesToHex(s))
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
function collectPath(leaves, index, out) {
|
|
117
|
-
if (leaves.length === 1) return;
|
|
118
|
-
const n = leaves.length;
|
|
119
|
-
const k = largestPowerOfTwoLessThan(n);
|
|
120
|
-
if (index < k) {
|
|
121
|
-
collectPath(leaves.slice(0, k), index, out);
|
|
122
|
-
out.push(merkleRoot(leaves.slice(k)));
|
|
123
|
-
} else {
|
|
124
|
-
collectPath(leaves.slice(k), index - k, out);
|
|
125
|
-
out.push(merkleRoot(leaves.slice(0, k)));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
function largestPowerOfTwoLessThan(n) {
|
|
129
|
-
if (n < 2) {
|
|
130
|
-
throw new Error(`largestPowerOfTwoLessThan: n must be >= 2 (got ${n})`);
|
|
131
|
-
}
|
|
132
|
-
let k = 1;
|
|
133
|
-
while (k * 2 < n) k *= 2;
|
|
134
|
-
return k;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// src/commitments/primitives.ts
|
|
138
|
-
function jcs(value) {
|
|
139
|
-
if (value === null || value === void 0) return "null";
|
|
140
|
-
if (typeof value === "boolean" || typeof value === "number")
|
|
141
|
-
return JSON.stringify(value);
|
|
142
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
143
|
-
if (Array.isArray(value))
|
|
144
|
-
return "[" + value.map(jcs).join(",") + "]";
|
|
145
|
-
const obj = value;
|
|
146
|
-
const keys = Object.keys(obj).sort();
|
|
147
|
-
return "{" + keys.map((k) => JSON.stringify(k) + ":" + jcs(obj[k])).join(",") + "}";
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// src/commitments/leaf.ts
|
|
151
|
-
function base64urlNoPad(bytes) {
|
|
152
|
-
const std = typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : btoa(String.fromCharCode(...bytes));
|
|
153
|
-
return std.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
154
|
-
}
|
|
155
|
-
function encodeLeaf(field) {
|
|
156
|
-
const obj = {
|
|
157
|
-
name: field.name,
|
|
158
|
-
salt: base64urlNoPad(field.salt),
|
|
159
|
-
value: field.value
|
|
160
|
-
};
|
|
161
|
-
const canonical = jcs(obj);
|
|
162
|
-
return new TextEncoder().encode(canonical);
|
|
163
|
-
}
|
|
164
|
-
function sortFields(fields) {
|
|
165
|
-
const encoder = new TextEncoder();
|
|
166
|
-
const decorated = fields.map((f) => ({
|
|
167
|
-
field: f,
|
|
168
|
-
nameBytes: encoder.encode(f.name)
|
|
169
|
-
}));
|
|
170
|
-
decorated.sort((a, b) => compareBytes(a.nameBytes, b.nameBytes));
|
|
171
|
-
return decorated.map((d) => d.field);
|
|
172
|
-
}
|
|
173
|
-
function compareBytes(a, b) {
|
|
174
|
-
const len = Math.min(a.length, b.length);
|
|
175
|
-
for (let i = 0; i < len; i++) {
|
|
176
|
-
if (a[i] !== b[i]) return a[i] - b[i];
|
|
177
|
-
}
|
|
178
|
-
return a.length - b.length;
|
|
179
|
-
}
|
|
180
|
-
function leavesFromFields(fields) {
|
|
181
|
-
const sorted = sortFields(fields);
|
|
182
|
-
const leafBytes = sorted.map(encodeLeaf);
|
|
183
|
-
return { sorted, leafBytes };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// src/signing-committed.ts
|
|
187
|
-
function freshSalt() {
|
|
188
|
-
return randomBytes(32);
|
|
189
|
-
}
|
|
190
|
-
function signCommittedDecision(entry, committedFieldNames, signingKey, publicKey, kid, issuer) {
|
|
191
|
-
const allFields = {
|
|
192
|
-
tool: entry.tool,
|
|
193
|
-
decision: entry.decision,
|
|
194
|
-
reason_code: entry.reason_code,
|
|
195
|
-
policy_digest: entry.policy_digest,
|
|
196
|
-
scope: entry.request_id,
|
|
197
|
-
mode: entry.mode,
|
|
198
|
-
request_id: entry.request_id
|
|
199
|
-
};
|
|
200
|
-
if (entry.tier) allFields.tier = entry.tier;
|
|
201
|
-
if (entry.credential_ref) allFields.credential_ref = entry.credential_ref;
|
|
202
|
-
if (entry.rate_limit_remaining !== void 0) {
|
|
203
|
-
allFields.rate_limit_remaining = entry.rate_limit_remaining;
|
|
204
|
-
}
|
|
205
|
-
if (entry.policy_engine) allFields.policy_engine = entry.policy_engine;
|
|
206
|
-
if (entry.hook_event) allFields.hook_event = entry.hook_event;
|
|
207
|
-
if (entry.sandbox_state) allFields.sandbox_state = entry.sandbox_state;
|
|
208
|
-
if (entry.timing) allFields.timing = entry.timing;
|
|
209
|
-
if (entry.swarm) allFields.swarm = entry.swarm;
|
|
210
|
-
if (entry.payload_digest) allFields.payload_digest = entry.payload_digest;
|
|
211
|
-
if (entry.deny_iteration) allFields.deny_iteration = entry.deny_iteration;
|
|
212
|
-
const committedFields = [];
|
|
213
|
-
const cleartextFields = {};
|
|
214
|
-
const openings = {};
|
|
215
|
-
for (const [name, value] of Object.entries(allFields)) {
|
|
216
|
-
if (committedFieldNames.includes(name)) {
|
|
217
|
-
const salt = freshSalt();
|
|
218
|
-
committedFields.push({ name, salt, value });
|
|
219
|
-
} else {
|
|
220
|
-
cleartextFields[name] = value;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
let committedFieldsRoot = null;
|
|
224
|
-
if (committedFields.length > 0) {
|
|
225
|
-
const { sorted, leafBytes } = leavesFromFields(committedFields);
|
|
226
|
-
const leafHashes = leafBytes.map(hashLeaf);
|
|
227
|
-
const root = merkleRoot(leafHashes);
|
|
228
|
-
committedFieldsRoot = bytesToHex(root);
|
|
229
|
-
sorted.forEach((f, i) => {
|
|
230
|
-
openings[f.name] = { name: f.name, value: f.value, salt: f.salt, index: i };
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
const payload = {
|
|
234
|
-
type: "scopeblind.receipt.committed.v1",
|
|
235
|
-
spec: "draft-farley-acta-signed-receipts-01",
|
|
236
|
-
issuer_certification: "self-signed",
|
|
237
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
238
|
-
...cleartextFields
|
|
239
|
-
};
|
|
240
|
-
if (committedFieldsRoot !== null) {
|
|
241
|
-
payload.committed_fields_root = committedFieldsRoot;
|
|
242
|
-
payload.committed_field_names = committedFields.map((f) => f.name);
|
|
243
|
-
}
|
|
244
|
-
const canonical = jcs(payload);
|
|
245
|
-
const messageHash = sha2562(new TextEncoder().encode(canonical));
|
|
246
|
-
const signatureBytes = ed25519.sign(messageHash, hexToBytes(signingKey));
|
|
247
|
-
const signedReceipt = {
|
|
248
|
-
...payload,
|
|
249
|
-
signature: {
|
|
250
|
-
alg: "EdDSA",
|
|
251
|
-
kid,
|
|
252
|
-
issuer,
|
|
253
|
-
sig: base64urlNoPad(signatureBytes),
|
|
254
|
-
public_key: publicKey
|
|
255
|
-
// hex
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
const signedJson = JSON.stringify(signedReceipt);
|
|
259
|
-
const receiptHash = bytesToHex(sha2562(new TextEncoder().encode(jcs(signedReceipt))));
|
|
260
|
-
return {
|
|
261
|
-
signed: signedJson,
|
|
262
|
-
artifact_type: "decision_receipt_committed_v1",
|
|
263
|
-
openings,
|
|
264
|
-
receipt_hash: receiptHash
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function discloseField(receiptHash, fieldName, openings) {
|
|
268
|
-
const o = openings[fieldName];
|
|
269
|
-
if (!o) {
|
|
270
|
-
throw new Error(`disclose: no opening recorded for field "${fieldName}"`);
|
|
271
|
-
}
|
|
272
|
-
const fields = Object.values(openings).map((op) => ({
|
|
273
|
-
name: op.name,
|
|
274
|
-
salt: op.salt,
|
|
275
|
-
value: op.value
|
|
276
|
-
}));
|
|
277
|
-
const { leafBytes } = leavesFromFields(fields);
|
|
278
|
-
const leafHashes = leafBytes.map(hashLeaf);
|
|
279
|
-
const proof = generateProof(leafHashes, o.index);
|
|
280
|
-
return {
|
|
281
|
-
parent_receipt_hash: receiptHash,
|
|
282
|
-
name: fieldName,
|
|
283
|
-
value: o.value,
|
|
284
|
-
salt: base64urlNoPad(o.salt),
|
|
285
|
-
proof
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
76
|
// src/manifest.ts
|
|
290
77
|
function isAgentId(s) {
|
|
291
78
|
return /^sb:agent:[a-f0-9]{32}$/.test(s);
|
|
@@ -791,7 +578,7 @@ function createLogAnchorField(anchor) {
|
|
|
791
578
|
}
|
|
792
579
|
|
|
793
580
|
// src/selective-disclosure.ts
|
|
794
|
-
import { createHash as createHash2, randomBytes
|
|
581
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
795
582
|
function redactFields(receipt, fieldsToRedact) {
|
|
796
583
|
const redacted = JSON.parse(JSON.stringify(receipt));
|
|
797
584
|
const salts = [];
|
|
@@ -807,7 +594,7 @@ function redactFields(receipt, fieldsToRedact) {
|
|
|
807
594
|
if (i === parts.length - 1) {
|
|
808
595
|
if (key in current) {
|
|
809
596
|
const originalValue = current[key];
|
|
810
|
-
const salt =
|
|
597
|
+
const salt = randomBytes(16).toString("hex");
|
|
811
598
|
const commitment = computeCommitment(salt, originalValue);
|
|
812
599
|
salts.push({ field: fieldPath, salt, originalValue });
|
|
813
600
|
current[key] = `sha256(salt + ${typeof originalValue === "string" ? "..." : JSON.stringify(originalValue).slice(0, 20) + "..."})`;
|
|
@@ -1024,9 +811,9 @@ MIT
|
|
|
1024
811
|
}
|
|
1025
812
|
|
|
1026
813
|
// src/webauthn-approval.ts
|
|
1027
|
-
import { createHash as createHash3, randomBytes as
|
|
814
|
+
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
1028
815
|
function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblind.com", timeoutSeconds = 300) {
|
|
1029
|
-
const challengeBytes =
|
|
816
|
+
const challengeBytes = randomBytes2(32);
|
|
1030
817
|
const contextHash = createHash3("sha256").update(JSON.stringify({ requestId, toolName, agentId, timestamp: Date.now() })).digest("hex");
|
|
1031
818
|
return {
|
|
1032
819
|
challenge: base64urlEncode(challengeBytes),
|
|
@@ -1475,7 +1262,7 @@ function createC2PAManifest(receipts, options) {
|
|
|
1475
1262
|
const receiptHashes = receipts.map(
|
|
1476
1263
|
(r) => createHash5("sha256").update(JSON.stringify(r)).digest("hex")
|
|
1477
1264
|
);
|
|
1478
|
-
const
|
|
1265
|
+
const merkleRoot = computeMerkleRoot(receiptHashes);
|
|
1479
1266
|
const assertions = [
|
|
1480
1267
|
// Acta decision provenance — the core assertion
|
|
1481
1268
|
{
|
|
@@ -1488,7 +1275,7 @@ function createC2PAManifest(receipts, options) {
|
|
|
1488
1275
|
decision_count: decisions.length,
|
|
1489
1276
|
allows: allows.length,
|
|
1490
1277
|
denies: denies.length,
|
|
1491
|
-
merkle_root:
|
|
1278
|
+
merkle_root: merkleRoot,
|
|
1492
1279
|
signing_algorithm: "Ed25519",
|
|
1493
1280
|
canonicalization: "JCS (RFC 8785)",
|
|
1494
1281
|
verifier: "npx @veritasacta/verify",
|
|
@@ -1544,7 +1331,7 @@ function createC2PAManifest(receipts, options) {
|
|
|
1544
1331
|
label: "acta.receipt-chain",
|
|
1545
1332
|
data: {
|
|
1546
1333
|
receipt_hashes: receiptHashes,
|
|
1547
|
-
merkle_root:
|
|
1334
|
+
merkle_root: merkleRoot,
|
|
1548
1335
|
note: "Full receipts available via verify URL. Hashes provided for integrity verification."
|
|
1549
1336
|
},
|
|
1550
1337
|
is_hash: true
|
|
@@ -1936,7 +1723,9 @@ async function confidentialInference(_prompt, _config) {
|
|
|
1936
1723
|
}
|
|
1937
1724
|
export {
|
|
1938
1725
|
BUILTIN_PATTERNS,
|
|
1726
|
+
CONNECTOR_PILOTS,
|
|
1939
1727
|
ConfidentialGate,
|
|
1728
|
+
POLICY_PACKS,
|
|
1940
1729
|
ProtectGateway,
|
|
1941
1730
|
ReceiptPropagator,
|
|
1942
1731
|
ScopeBlindBridge,
|
|
@@ -1946,6 +1735,9 @@ export {
|
|
|
1946
1735
|
collectSignedReceipts,
|
|
1947
1736
|
computeCalibration,
|
|
1948
1737
|
confidentialInference,
|
|
1738
|
+
connectorDirectory,
|
|
1739
|
+
connectorDoctor,
|
|
1740
|
+
connectorPilotIds,
|
|
1949
1741
|
createApprovalChallenge,
|
|
1950
1742
|
createApprovalReceiptPayload,
|
|
1951
1743
|
createAttestationField,
|
|
@@ -1957,6 +1749,7 @@ export {
|
|
|
1957
1749
|
createReceiptChannel,
|
|
1958
1750
|
createSandbox,
|
|
1959
1751
|
createSandboxServer,
|
|
1752
|
+
createSelectiveDisclosurePackage,
|
|
1960
1753
|
destroySandbox,
|
|
1961
1754
|
discloseField,
|
|
1962
1755
|
ed25519ToDIDKey,
|
|
@@ -1977,6 +1770,8 @@ export {
|
|
|
1977
1770
|
generateSampleCedarPolicy,
|
|
1978
1771
|
generateSchemaStub,
|
|
1979
1772
|
generateVerifyReceiptSkill,
|
|
1773
|
+
getConnectorPilot,
|
|
1774
|
+
getPolicyPack,
|
|
1980
1775
|
getScopeBlindBridge,
|
|
1981
1776
|
getSignerInfo,
|
|
1982
1777
|
getToolPolicy,
|
|
@@ -1997,8 +1792,10 @@ export {
|
|
|
1997
1792
|
parseLogFile,
|
|
1998
1793
|
parseNotificationConfigFromEnv,
|
|
1999
1794
|
parseRateLimit,
|
|
1795
|
+
policyPackIds,
|
|
2000
1796
|
policySetFromSource,
|
|
2001
1797
|
queryExternalPDP,
|
|
1798
|
+
readInstalledConnectorPilots,
|
|
2002
1799
|
receiptToVP,
|
|
2003
1800
|
receiptsToHFRows,
|
|
2004
1801
|
redactFields,
|
|
@@ -2022,5 +1819,7 @@ export {
|
|
|
2022
1819
|
verifyApprovalAssertion,
|
|
2023
1820
|
verifyCommitment,
|
|
2024
1821
|
verifyEvidenceAttestation,
|
|
2025
|
-
verifyRekorAnchor
|
|
1822
|
+
verifyRekorAnchor,
|
|
1823
|
+
verifySelectiveDisclosurePackage,
|
|
1824
|
+
writeConnectorPilots
|
|
2026
1825
|
};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import "./chunk-PQJP2ZCI.mjs";
|
|
2
|
+
|
|
3
|
+
// src/receipt-registry.ts
|
|
4
|
+
import { createHash, randomUUID } from "crypto";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
var ORG_IDENTITY_FILE = ".protect-mcp-org.json";
|
|
8
|
+
var REGISTRY_FILE = ".protect-mcp-registry.json";
|
|
9
|
+
var VERIFIER_PAGE_FILE = "scopeblind-verifier.html";
|
|
10
|
+
function sha256Hex(input) {
|
|
11
|
+
return createHash("sha256").update(input).digest("hex");
|
|
12
|
+
}
|
|
13
|
+
function stableStringify(value) {
|
|
14
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
15
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
16
|
+
const obj = value;
|
|
17
|
+
return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`).join(",")}}`;
|
|
18
|
+
}
|
|
19
|
+
function safeReadJson(path) {
|
|
20
|
+
try {
|
|
21
|
+
if (!existsSync(path)) return null;
|
|
22
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function requestIdFromReceipt(receipt) {
|
|
28
|
+
const direct = receipt.request_id || receipt.scope;
|
|
29
|
+
if (typeof direct === "string") return direct;
|
|
30
|
+
const payload = receipt.payload;
|
|
31
|
+
if (payload && typeof payload === "object") {
|
|
32
|
+
const candidate = payload.request_id || payload.scope;
|
|
33
|
+
if (typeof candidate === "string") return candidate;
|
|
34
|
+
}
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
function keyIdFromReceipt(receipt) {
|
|
38
|
+
const kid = receipt.kid;
|
|
39
|
+
if (typeof kid === "string") return kid;
|
|
40
|
+
const signature = receipt.signature;
|
|
41
|
+
if (signature && typeof signature === "object") {
|
|
42
|
+
const nested = signature.kid;
|
|
43
|
+
if (typeof nested === "string") return nested;
|
|
44
|
+
}
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
function issuerFromReceipt(receipt) {
|
|
48
|
+
const issuer = receipt.issuer;
|
|
49
|
+
if (typeof issuer === "string") return issuer;
|
|
50
|
+
const signature = receipt.signature;
|
|
51
|
+
if (signature && typeof signature === "object") {
|
|
52
|
+
const nested = signature.issuer;
|
|
53
|
+
if (typeof nested === "string") return nested;
|
|
54
|
+
}
|
|
55
|
+
return void 0;
|
|
56
|
+
}
|
|
57
|
+
function receiptType(receipt) {
|
|
58
|
+
return String(receipt.type || receipt.artifact_type || receipt.v || "receipt");
|
|
59
|
+
}
|
|
60
|
+
function readReceiptDigestRecords(dir) {
|
|
61
|
+
const receiptPath = join(dir, ".protect-mcp-receipts.jsonl");
|
|
62
|
+
if (!existsSync(receiptPath)) return [];
|
|
63
|
+
const raw = readFileSync(receiptPath, "utf-8");
|
|
64
|
+
return raw.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
65
|
+
try {
|
|
66
|
+
const receipt = JSON.parse(line);
|
|
67
|
+
const publicKey = (() => {
|
|
68
|
+
const sig = receipt.signature;
|
|
69
|
+
if (sig && typeof sig === "object" && typeof sig.public_key === "string") {
|
|
70
|
+
return String(sig.public_key);
|
|
71
|
+
}
|
|
72
|
+
return void 0;
|
|
73
|
+
})();
|
|
74
|
+
return [{
|
|
75
|
+
type: "scopeblind.receipt_digest.v1",
|
|
76
|
+
receipt_hash: sha256Hex(line),
|
|
77
|
+
receipt_bytes: Buffer.byteLength(line, "utf-8"),
|
|
78
|
+
receipt_type: receiptType(receipt),
|
|
79
|
+
request_id: requestIdFromReceipt(receipt),
|
|
80
|
+
local_issuer: issuerFromReceipt(receipt),
|
|
81
|
+
local_kid: keyIdFromReceipt(receipt),
|
|
82
|
+
local_public_key_hint: publicKey ? `${publicKey.slice(0, 12)}...${publicKey.slice(-8)}` : void 0,
|
|
83
|
+
observed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
84
|
+
source_file: receiptPath
|
|
85
|
+
}];
|
|
86
|
+
} catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function createOrgIdentity(opts) {
|
|
92
|
+
const now = (opts.now || /* @__PURE__ */ new Date()).toISOString();
|
|
93
|
+
const existing = safeReadJson(join(opts.dir, ORG_IDENTITY_FILE));
|
|
94
|
+
const keyData = safeReadJson(join(opts.dir, "keys", "gateway.json")) || {};
|
|
95
|
+
const orgId = opts.orgId || String(existing?.org_id || `org_${randomUUID().slice(0, 12)}`);
|
|
96
|
+
const orgName = opts.orgName || String(existing?.org_name || "Local ScopeBlind Org");
|
|
97
|
+
const billingAccountId = opts.billingAccountId || String(existing?.billing_account_id || `billing_${orgId}`);
|
|
98
|
+
const publicKey = typeof keyData.publicKey === "string" ? keyData.publicKey : "";
|
|
99
|
+
const kid = typeof keyData.kid === "string" ? keyData.kid : publicKey ? `kid_${publicKey.slice(0, 12)}` : "local-key";
|
|
100
|
+
const issuer = typeof keyData.issuer === "string" ? keyData.issuer : "protect-mcp";
|
|
101
|
+
return {
|
|
102
|
+
type: "scopeblind.org_identity.v1",
|
|
103
|
+
org_id: orgId,
|
|
104
|
+
org_name: orgName,
|
|
105
|
+
billing_account_id: billingAccountId,
|
|
106
|
+
created_at: typeof existing?.created_at === "string" ? existing.created_at : now,
|
|
107
|
+
public_key_directory: publicKey ? [{
|
|
108
|
+
type: "scopeblind.org_public_key.v1",
|
|
109
|
+
org_id: orgId,
|
|
110
|
+
key_id: kid,
|
|
111
|
+
issuer,
|
|
112
|
+
algorithm: "Ed25519",
|
|
113
|
+
public_key_hex: publicKey,
|
|
114
|
+
created_at: now,
|
|
115
|
+
source: "local_gateway_key"
|
|
116
|
+
}] : [],
|
|
117
|
+
privacy: {
|
|
118
|
+
raw_prompt_upload: false,
|
|
119
|
+
raw_tool_payload_upload: false,
|
|
120
|
+
raw_receipt_upload: false,
|
|
121
|
+
digest_only: true
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function writeOrgIdentity(dir, identity) {
|
|
126
|
+
const path = join(dir, ORG_IDENTITY_FILE);
|
|
127
|
+
writeFileSync(path, JSON.stringify(identity, null, 2) + "\n");
|
|
128
|
+
return path;
|
|
129
|
+
}
|
|
130
|
+
function localAnchors(records, org, now, verifierBaseUrl) {
|
|
131
|
+
return records.map((record) => ({
|
|
132
|
+
type: "scopeblind.timestamp_anchor.v1",
|
|
133
|
+
anchor_id: `local_${record.receipt_hash.slice(0, 16)}`,
|
|
134
|
+
receipt_hash: record.receipt_hash,
|
|
135
|
+
org_id: org.org_id,
|
|
136
|
+
timestamp_utc: now.toISOString(),
|
|
137
|
+
timestamp_source: "local-preview-not-independent",
|
|
138
|
+
verifier_url: verifierBaseUrl ? `${verifierBaseUrl.replace(/\/$/, "")}/verify?digest=${record.receipt_hash}` : void 0
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
async function hostedAnchors(opts) {
|
|
142
|
+
const endpoint = opts.endpoint.replace(/\/$/, "") + "/v1/receipt-registry/anchor";
|
|
143
|
+
const payload = {
|
|
144
|
+
type: "scopeblind.receipt_registry_anchor_request.v1",
|
|
145
|
+
org: {
|
|
146
|
+
org_id: opts.org.org_id,
|
|
147
|
+
org_name: opts.org.org_name,
|
|
148
|
+
billing_account_id: opts.org.billing_account_id,
|
|
149
|
+
public_key_directory: opts.org.public_key_directory
|
|
150
|
+
},
|
|
151
|
+
privacy: opts.org.privacy,
|
|
152
|
+
billing: {
|
|
153
|
+
metered_unit: "receipt_digest_anchor",
|
|
154
|
+
count: opts.records.length,
|
|
155
|
+
raw_prompt_upload: false,
|
|
156
|
+
raw_data_upload: false
|
|
157
|
+
},
|
|
158
|
+
receipt_digests: opts.records.map((record) => ({
|
|
159
|
+
receipt_hash: record.receipt_hash,
|
|
160
|
+
receipt_bytes: record.receipt_bytes,
|
|
161
|
+
receipt_type: record.receipt_type,
|
|
162
|
+
request_id: record.request_id,
|
|
163
|
+
local_issuer: record.local_issuer,
|
|
164
|
+
local_kid: record.local_kid
|
|
165
|
+
}))
|
|
166
|
+
};
|
|
167
|
+
const bodyText = stableStringify(payload);
|
|
168
|
+
for (const forbidden of ["payload_preview", "raw_receipt", "prompt", "tool_output", "privateKey"]) {
|
|
169
|
+
if (bodyText.includes(`${JSON.stringify(forbidden)}:`)) throw new Error(`hosted anchor payload contains forbidden field: ${forbidden}`);
|
|
170
|
+
}
|
|
171
|
+
const res = await fetch(endpoint, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: {
|
|
174
|
+
"content-type": "application/json",
|
|
175
|
+
authorization: `Bearer ${opts.token}`,
|
|
176
|
+
"user-agent": "protect-mcp/receipt-registry"
|
|
177
|
+
},
|
|
178
|
+
body: JSON.stringify(payload)
|
|
179
|
+
});
|
|
180
|
+
if (!res.ok) {
|
|
181
|
+
const text = await res.text().catch(() => "");
|
|
182
|
+
throw new Error(`hosted anchor failed: HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
183
|
+
}
|
|
184
|
+
const response = await res.json().catch(() => ({}));
|
|
185
|
+
const anchors = Array.isArray(response.anchors) ? response.anchors : [];
|
|
186
|
+
return opts.records.map((record, index) => {
|
|
187
|
+
const anchor = anchors[index] || anchors.find((candidate) => candidate.receipt_hash === record.receipt_hash) || {};
|
|
188
|
+
return {
|
|
189
|
+
type: "scopeblind.timestamp_anchor.v1",
|
|
190
|
+
anchor_id: String(anchor.anchor_id || `hosted_${record.receipt_hash.slice(0, 16)}`),
|
|
191
|
+
receipt_hash: record.receipt_hash,
|
|
192
|
+
org_id: opts.org.org_id,
|
|
193
|
+
timestamp_utc: String(anchor.timestamp_utc || anchor.anchored_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
194
|
+
timestamp_source: "scopeblind-hosted",
|
|
195
|
+
registry_url: typeof response.registry_url === "string" ? response.registry_url : void 0,
|
|
196
|
+
verifier_url: typeof anchor.verifier_url === "string" ? anchor.verifier_url : opts.verifierBaseUrl ? `${opts.verifierBaseUrl.replace(/\/$/, "")}/verify?digest=${record.receipt_hash}` : void 0,
|
|
197
|
+
signature: anchor.signature
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
async function createReceiptRegistry(opts) {
|
|
202
|
+
const now = opts.now || /* @__PURE__ */ new Date();
|
|
203
|
+
const org = createOrgIdentity(opts);
|
|
204
|
+
const records = readReceiptDigestRecords(opts.dir);
|
|
205
|
+
if (records.length === 0) throw new Error("No signed receipts found. Run protect-mcp with signing enabled first.");
|
|
206
|
+
let anchors;
|
|
207
|
+
let uploaded = false;
|
|
208
|
+
if (opts.hosted || opts.endpoint || opts.token) {
|
|
209
|
+
if (!opts.endpoint) throw new Error("Hosted anchoring requires --endpoint or SCOPEBLIND_REGISTRY_ENDPOINT.");
|
|
210
|
+
if (!opts.token) throw new Error("Hosted anchoring requires --token or SCOPEBLIND_TOKEN.");
|
|
211
|
+
anchors = await hostedAnchors({ endpoint: opts.endpoint, token: opts.token, org, records, verifierBaseUrl: opts.verifierBaseUrl });
|
|
212
|
+
uploaded = true;
|
|
213
|
+
} else {
|
|
214
|
+
anchors = localAnchors(records, org, now, opts.verifierBaseUrl);
|
|
215
|
+
}
|
|
216
|
+
const registry = {
|
|
217
|
+
type: "scopeblind.receipt_registry.v1",
|
|
218
|
+
version: 1,
|
|
219
|
+
generated_at: now.toISOString(),
|
|
220
|
+
org,
|
|
221
|
+
billing: {
|
|
222
|
+
billing_account_id: org.billing_account_id,
|
|
223
|
+
metered_unit: "receipt_digest_anchor",
|
|
224
|
+
charge_basis: "anchored_receipt_digest_count",
|
|
225
|
+
raw_prompt_upload: false,
|
|
226
|
+
raw_data_upload: false
|
|
227
|
+
},
|
|
228
|
+
privacy: {
|
|
229
|
+
statement: uploaded ? "ScopeBlind hosted registry received receipt digests and public identity metadata only." : "Local preview registry only. No independent timestamp exists until hosted anchoring succeeds.",
|
|
230
|
+
uploaded_fields: ["receipt_hash", "receipt_bytes", "receipt_type", "request_id", "local_issuer", "local_kid", "org_id", "billing_account_id", "org_public_keys"],
|
|
231
|
+
excluded_fields: ["raw_prompt", "raw_tool_payload", "payload_preview", "raw_receipt", "tool_output", "private_key"]
|
|
232
|
+
},
|
|
233
|
+
records,
|
|
234
|
+
anchors,
|
|
235
|
+
verifier: {
|
|
236
|
+
local_page: join(opts.dir, VERIFIER_PAGE_FILE),
|
|
237
|
+
shareable_url_template: opts.verifierBaseUrl ? `${opts.verifierBaseUrl.replace(/\/$/, "")}/verify?digest={receipt_hash}` : "file://scopeblind-verifier.html#digest={receipt_hash}"
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
writeOrgIdentity(opts.dir, org);
|
|
241
|
+
const registryPath = opts.outPath || join(opts.dir, REGISTRY_FILE);
|
|
242
|
+
mkdirSync(dirname(registryPath), { recursive: true });
|
|
243
|
+
writeFileSync(registryPath, JSON.stringify(registry, null, 2) + "\n");
|
|
244
|
+
const verifierPath = join(opts.dir, VERIFIER_PAGE_FILE);
|
|
245
|
+
writeFileSync(verifierPath, renderVerifierPage(registry));
|
|
246
|
+
return { registry, registryPath, verifierPath, uploaded };
|
|
247
|
+
}
|
|
248
|
+
function renderVerifierPage(registry) {
|
|
249
|
+
const embedded = JSON.stringify(registry).replace(/</g, "\\u003c");
|
|
250
|
+
return `<!doctype html>
|
|
251
|
+
<html lang="en">
|
|
252
|
+
<head>
|
|
253
|
+
<meta charset="utf-8">
|
|
254
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
255
|
+
<title>ScopeBlind Receipt Verifier</title>
|
|
256
|
+
<style>
|
|
257
|
+
:root{--ink:#11110f;--muted:#6d675d;--line:#ded7c9;--paper:#f7f3ea;--card:#fffdf7;--ok:#2f6f4e;--warn:#8d620f;--bad:#8f241c}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at top left,#fffdf7,#f7f3ea 48%,#e8dfce);color:var(--ink);font:15px/1.5 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}main{width:min(1040px,calc(100vw - 32px));margin:32px auto}.card{background:rgba(255,253,247,.94);border:1px solid var(--line);border-radius:24px;padding:22px;box-shadow:0 24px 70px rgba(36,30,18,.10);margin-bottom:16px}.kicker{text-transform:uppercase;letter-spacing:.17em;color:var(--muted);font-size:11px;font-weight:900}h1{font:520 clamp(36px,6vw,72px)/.94 ui-serif,Georgia,serif;letter-spacing:-.05em;margin:12px 0}input{width:100%;border:1px solid var(--line);border-radius:14px;padding:13px;background:#fffaf0;font:14px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.pill{display:inline-flex;border-radius:999px;padding:5px 9px;font-size:11px;font-weight:900}.ok{background:#dcebdd;color:var(--ok)}.warn{background:#f4e5bd;color:var(--warn)}.bad{background:#f7d9d3;color:var(--bad)}pre{white-space:pre-wrap;background:#181712;color:#f8f1df;border-radius:16px;padding:14px;overflow:auto}.muted{color:var(--muted)}code{background:#f2eadc;border:1px solid var(--line);border-radius:8px;padding:2px 6px}</style>
|
|
258
|
+
</head>
|
|
259
|
+
<body><main>
|
|
260
|
+
<section class="card"><div class="kicker">ScopeBlind verifier</div><h1>Verify that an independent registry saw this receipt digest.</h1><p class="muted">This page contains receipt digests, anchors, public key metadata, and billing metadata. It does not contain raw prompts, payloads, tool outputs, or raw receipts.</p></section>
|
|
261
|
+
<section class="card"><label class="kicker" for="digest">Receipt digest</label><input id="digest" placeholder="Paste receipt SHA-256 digest" oninput="render()"><div id="result" style="margin-top:16px"></div></section>
|
|
262
|
+
<section class="card"><div class="kicker">Org public key directory</div><pre id="keys"></pre></section>
|
|
263
|
+
</main><script>
|
|
264
|
+
const registry=${embedded};
|
|
265
|
+
function esc(v){return String(v==null?'':v).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));}
|
|
266
|
+
function render(){const q=document.getElementById('digest').value.trim()||new URLSearchParams(location.search).get('digest')||location.hash.replace(/^#digest=/,'');const rec=registry.records.find(r=>r.receipt_hash===q);const anchor=registry.anchors.find(a=>a.receipt_hash===q);const el=document.getElementById('result');if(!q){el.innerHTML='<p class="muted">Paste a digest to verify registry inclusion.</p>';return;}if(!rec){el.innerHTML='<span class="pill bad">not found</span><p>No matching digest in this registry export.</p>';return;}const independent=anchor&&anchor.timestamp_source==='scopeblind-hosted';el.innerHTML='<span class="pill '+(independent?'ok':'warn')+'">'+(independent?'anchored by ScopeBlind':'local preview only')+'</span><pre>'+esc(JSON.stringify({receipt:rec,anchor:anchor||null,billing:registry.billing,privacy:registry.privacy},null,2))+'</pre>';}
|
|
267
|
+
document.getElementById('keys').textContent=JSON.stringify(registry.org.public_key_directory,null,2);render();
|
|
268
|
+
</script></body></html>`;
|
|
269
|
+
}
|
|
270
|
+
export {
|
|
271
|
+
ORG_IDENTITY_FILE,
|
|
272
|
+
REGISTRY_FILE,
|
|
273
|
+
VERIFIER_PAGE_FILE,
|
|
274
|
+
createOrgIdentity,
|
|
275
|
+
createReceiptRegistry,
|
|
276
|
+
readReceiptDigestRecords,
|
|
277
|
+
renderVerifierPage,
|
|
278
|
+
writeOrgIdentity
|
|
279
|
+
};
|