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/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
- simulate
9
- } from "./chunk-ZBKJANP7.mjs";
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-OHUTUFTC.mjs";
41
+ } from "./chunk-UBZJ3VI2.mjs";
22
42
  import {
23
43
  createSandboxServer
24
- } from "./chunk-J6L4XCTE.mjs";
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-X63ELMU4.mjs";
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-546U3A7R.mjs";
52
- import {
53
- ed25519,
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 as randomBytes2 } from "crypto";
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 = randomBytes2(16).toString("hex");
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 randomBytes3 } from "crypto";
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 = randomBytes3(32);
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 merkleRoot2 = computeMerkleRoot(receiptHashes);
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: merkleRoot2,
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: merkleRoot2,
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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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
+ };