protect-mcp 0.7.3 → 0.7.5

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.js CHANGED
@@ -1002,10 +1002,10 @@ var require_types = __commonJS({
1002
1002
  if (!jwtRegex.test(jwt))
1003
1003
  return false;
1004
1004
  try {
1005
- const [header] = jwt.split(".");
1006
- if (!header)
1005
+ const [header2] = jwt.split(".");
1006
+ if (!header2)
1007
1007
  return false;
1008
- const base64 = header.replace(/-/g, "+").replace(/_/g, "/").padEnd(header.length + (4 - header.length % 4) % 4, "=");
1008
+ const base64 = header2.replace(/-/g, "+").replace(/_/g, "/").padEnd(header2.length + (4 - header2.length % 4) % 4, "=");
1009
1009
  const decoded = JSON.parse(atob(base64));
1010
1010
  if (typeof decoded !== "object" || decoded === null)
1011
1011
  return false;
@@ -6486,10 +6486,10 @@ var require_schemas = __commonJS({
6486
6486
  const tokensParts = token.split(".");
6487
6487
  if (tokensParts.length !== 3)
6488
6488
  return false;
6489
- const [header] = tokensParts;
6490
- if (!header)
6489
+ const [header2] = tokensParts;
6490
+ if (!header2)
6491
6491
  return false;
6492
- const parsedHeader = JSON.parse(atob(header));
6492
+ const parsedHeader = JSON.parse(atob(header2));
6493
6493
  if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT")
6494
6494
  return false;
6495
6495
  if (!parsedHeader.alg)
@@ -28476,8 +28476,8 @@ var require_resolve = __commonJS({
28476
28476
  }
28477
28477
  return count;
28478
28478
  }
28479
- function getFullPath(resolver, id = "", normalize) {
28480
- if (normalize !== false)
28479
+ function getFullPath(resolver, id = "", normalize2) {
28480
+ if (normalize2 !== false)
28481
28481
  id = normalizeId(id);
28482
28482
  const p = resolver.parse(id);
28483
28483
  return _getFullPath(resolver, p);
@@ -29817,7 +29817,7 @@ var require_fast_uri = __commonJS({
29817
29817
  "use strict";
29818
29818
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
29819
29819
  var { SCHEMES, getSchemeHandler } = require_schemes();
29820
- function normalize(uri, options) {
29820
+ function normalize2(uri, options) {
29821
29821
  if (typeof uri === "string") {
29822
29822
  uri = /** @type {T} */
29823
29823
  serialize(parse(uri, options), options);
@@ -30053,7 +30053,7 @@ var require_fast_uri = __commonJS({
30053
30053
  }
30054
30054
  var fastUri = {
30055
30055
  SCHEMES,
30056
- normalize,
30056
+ normalize: normalize2,
30057
30057
  resolve,
30058
30058
  resolveComponent,
30059
30059
  equal,
@@ -35019,7 +35019,9 @@ var require_mcp = __commonJS({
35019
35019
  var index_exports = {};
35020
35020
  __export(index_exports, {
35021
35021
  BUILTIN_PATTERNS: () => BUILTIN_PATTERNS,
35022
+ CONNECTOR_PILOTS: () => CONNECTOR_PILOTS,
35022
35023
  ConfidentialGate: () => ConfidentialGate,
35024
+ POLICY_PACKS: () => POLICY_PACKS,
35023
35025
  ProtectGateway: () => ProtectGateway,
35024
35026
  ReceiptPropagator: () => ReceiptPropagator,
35025
35027
  ScopeBlindBridge: () => ScopeBlindBridge,
@@ -35029,6 +35031,9 @@ __export(index_exports, {
35029
35031
  collectSignedReceipts: () => collectSignedReceipts,
35030
35032
  computeCalibration: () => computeCalibration,
35031
35033
  confidentialInference: () => confidentialInference,
35034
+ connectorDirectory: () => connectorDirectory,
35035
+ connectorDoctor: () => connectorDoctor,
35036
+ connectorPilotIds: () => connectorPilotIds,
35032
35037
  createApprovalChallenge: () => createApprovalChallenge,
35033
35038
  createApprovalReceiptPayload: () => createApprovalReceiptPayload,
35034
35039
  createAttestationField: () => createAttestationField,
@@ -35040,6 +35045,7 @@ __export(index_exports, {
35040
35045
  createReceiptChannel: () => createReceiptChannel,
35041
35046
  createSandbox: () => createSandbox,
35042
35047
  createSandboxServer: () => createSandboxServer,
35048
+ createSelectiveDisclosurePackage: () => createSelectiveDisclosurePackage,
35043
35049
  destroySandbox: () => destroySandbox,
35044
35050
  discloseField: () => discloseField,
35045
35051
  ed25519ToDIDKey: () => ed25519ToDIDKey,
@@ -35060,6 +35066,8 @@ __export(index_exports, {
35060
35066
  generateSampleCedarPolicy: () => generateSampleCedarPolicy,
35061
35067
  generateSchemaStub: () => generateSchemaStub,
35062
35068
  generateVerifyReceiptSkill: () => generateVerifyReceiptSkill,
35069
+ getConnectorPilot: () => getConnectorPilot,
35070
+ getPolicyPack: () => getPolicyPack,
35063
35071
  getScopeBlindBridge: () => getScopeBlindBridge,
35064
35072
  getSignerInfo: () => getSignerInfo,
35065
35073
  getToolPolicy: () => getToolPolicy,
@@ -35080,8 +35088,10 @@ __export(index_exports, {
35080
35088
  parseLogFile: () => parseLogFile,
35081
35089
  parseNotificationConfigFromEnv: () => parseNotificationConfigFromEnv,
35082
35090
  parseRateLimit: () => parseRateLimit,
35091
+ policyPackIds: () => policyPackIds,
35083
35092
  policySetFromSource: () => policySetFromSource,
35084
35093
  queryExternalPDP: () => queryExternalPDP,
35094
+ readInstalledConnectorPilots: () => readInstalledConnectorPilots,
35085
35095
  receiptToVP: () => receiptToVP,
35086
35096
  receiptsToHFRows: () => receiptsToHFRows,
35087
35097
  redactFields: () => redactFields,
@@ -35105,13 +35115,15 @@ __export(index_exports, {
35105
35115
  verifyApprovalAssertion: () => verifyApprovalAssertion,
35106
35116
  verifyCommitment: () => verifyCommitment,
35107
35117
  verifyEvidenceAttestation: () => verifyEvidenceAttestation,
35108
- verifyRekorAnchor: () => verifyRekorAnchor
35118
+ verifyRekorAnchor: () => verifyRekorAnchor,
35119
+ verifySelectiveDisclosurePackage: () => verifySelectiveDisclosurePackage,
35120
+ writeConnectorPilots: () => writeConnectorPilots
35109
35121
  });
35110
35122
  module.exports = __toCommonJS(index_exports);
35111
35123
 
35112
35124
  // src/gateway.ts
35113
35125
  var import_node_child_process = require("child_process");
35114
- var import_node_crypto3 = require("crypto");
35126
+ var import_node_crypto4 = require("crypto");
35115
35127
  var import_node_readline = require("readline");
35116
35128
  var import_node_fs6 = require("fs");
35117
35129
  var import_node_path4 = require("path");
@@ -35541,6 +35553,7 @@ function signDecision(entry) {
35541
35553
  if (entry.timing) payload.timing = entry.timing;
35542
35554
  if (entry.swarm) payload.swarm = entry.swarm;
35543
35555
  if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
35556
+ if (entry.action_readback) payload.action_readback = entry.action_readback;
35544
35557
  if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
35545
35558
  const result = artifactsModule.createSignedArtifact(
35546
35559
  artifactType,
@@ -36214,7 +36227,7 @@ function handleHealth(res, startTime, config) {
36214
36227
  status: "ok",
36215
36228
  uptime_ms: Date.now() - startTime,
36216
36229
  mode: config.mode,
36217
- version: "0.3.1"
36230
+ version: process.env.PROTECT_MCP_VERSION || "unknown"
36218
36231
  }));
36219
36232
  }
36220
36233
  function handleStatus(res, logDir) {
@@ -36347,6 +36360,95 @@ function handleListApprovals(res, approvalStore) {
36347
36360
  res.end(JSON.stringify({ grants }));
36348
36361
  }
36349
36362
 
36363
+ // src/action-readback.ts
36364
+ var import_node_crypto3 = require("crypto");
36365
+ var SECRET_KEY_RE = /(api[_-]?key|authorization|bearer|credential|password|secret|session|token|private[_-]?key)/i;
36366
+ var DESTINATION_KEYS = [
36367
+ "path",
36368
+ "file_path",
36369
+ "filePath",
36370
+ "url",
36371
+ "uri",
36372
+ "endpoint",
36373
+ "host",
36374
+ "hostname",
36375
+ "repo",
36376
+ "repository",
36377
+ "branch",
36378
+ "channel",
36379
+ "to",
36380
+ "recipient",
36381
+ "symbol",
36382
+ "account",
36383
+ "bucket",
36384
+ "database",
36385
+ "table",
36386
+ "service"
36387
+ ];
36388
+ function stableStringify(value) {
36389
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
36390
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
36391
+ const obj = value;
36392
+ return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`).join(",")}}`;
36393
+ }
36394
+ function redact(value, path = [], redacted = [], disclosed = [], depth = 0) {
36395
+ if (depth > 4) return "[truncated-depth]";
36396
+ if (value === null || value === void 0) return value;
36397
+ if (typeof value !== "object") {
36398
+ if (path.length > 0) disclosed.push(path.join("."));
36399
+ if (typeof value === "string" && value.length > 240) return `${value.slice(0, 240)}...`;
36400
+ return value;
36401
+ }
36402
+ if (Array.isArray(value)) {
36403
+ return value.slice(0, 20).map((item, idx) => redact(item, [...path, String(idx)], redacted, disclosed, depth + 1));
36404
+ }
36405
+ const out = {};
36406
+ for (const [key, child] of Object.entries(value)) {
36407
+ const childPath = [...path, key];
36408
+ if (SECRET_KEY_RE.test(key)) {
36409
+ redacted.push(childPath.join("."));
36410
+ out[key] = "[redacted]";
36411
+ continue;
36412
+ }
36413
+ out[key] = redact(child, childPath, redacted, disclosed, depth + 1);
36414
+ }
36415
+ return out;
36416
+ }
36417
+ function firstStringValue(input, keys) {
36418
+ for (const key of keys) {
36419
+ const value = input[key];
36420
+ if (typeof value === "string" && value.trim()) return value.trim();
36421
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
36422
+ }
36423
+ return void 0;
36424
+ }
36425
+ function actionFor(tool, input) {
36426
+ const explicit = firstStringValue(input, ["action", "operation", "method", "verb", "command"]);
36427
+ if (explicit) return explicit.length > 90 ? `${explicit.slice(0, 90)}...` : explicit;
36428
+ return tool;
36429
+ }
36430
+ function buildActionReadback(tool, input) {
36431
+ const normalized = input && typeof input === "object" && !Array.isArray(input) ? input : { value: input };
36432
+ const canonical = stableStringify(normalized);
36433
+ const redactedFields = [];
36434
+ const disclosedFields = [];
36435
+ const payloadPreview = redact(normalized, [], redactedFields, disclosedFields);
36436
+ const action = actionFor(tool, normalized);
36437
+ const destination = firstStringValue(normalized, DESTINATION_KEYS);
36438
+ const summary = destination ? `${tool} -> ${destination}` : `${tool} request`;
36439
+ return {
36440
+ tool,
36441
+ action,
36442
+ destination,
36443
+ payload_preview: payloadPreview,
36444
+ payload_hash: (0, import_node_crypto3.createHash)("sha256").update(canonical).digest("hex"),
36445
+ payload_bytes: Buffer.byteLength(canonical, "utf-8"),
36446
+ disclosed_fields: [...new Set(disclosedFields)].slice(0, 80),
36447
+ redacted_fields: [...new Set(redactedFields)].slice(0, 80),
36448
+ summary
36449
+ };
36450
+ }
36451
+
36350
36452
  // src/gateway.ts
36351
36453
  var LOG_FILE2 = ".protect-mcp-log.jsonl";
36352
36454
  var RECEIPTS_FILE = ".protect-mcp-receipts.jsonl";
@@ -36362,7 +36464,7 @@ var ProtectGateway = class {
36362
36464
  /** Approval grants keyed by request_id (scoped to the specific action that was requested) */
36363
36465
  approvalStore = /* @__PURE__ */ new Map();
36364
36466
  /** Random nonce generated at startup — required for approval endpoint authentication */
36365
- approvalNonce = (0, import_node_crypto3.randomBytes)(16).toString("hex");
36467
+ approvalNonce = (0, import_node_crypto4.randomBytes)(16).toString("hex");
36366
36468
  currentTier = "unknown";
36367
36469
  admissionResult = null;
36368
36470
  /** Notification config for approval gates (SMS, webhook, email) */
@@ -36516,8 +36618,10 @@ var ProtectGateway = class {
36516
36618
  }
36517
36619
  async interceptToolCall(request) {
36518
36620
  const toolName = request.params?.name || "unknown";
36519
- const requestId = (0, import_node_crypto3.randomUUID)().slice(0, 12);
36621
+ const requestId = (0, import_node_crypto4.randomUUID)().slice(0, 12);
36520
36622
  const mode = this.config.enforce ? "enforce" : "shadow";
36623
+ const toolInput = request.params?.arguments && typeof request.params.arguments === "object" ? request.params.arguments : request.params || {};
36624
+ const actionReadback = buildActionReadback(toolName, toolInput);
36521
36625
  let resolvedAgentKid = this.admissionResult?.agent_id;
36522
36626
  let effectiveToolPolicy;
36523
36627
  if (this.config.multiAgent?.enabled) {
@@ -36527,7 +36631,7 @@ var ProtectGateway = class {
36527
36631
  if (agentOverrides && agentOverrides[toolName]) {
36528
36632
  effectiveToolPolicy = { ...getToolPolicy(toolName, this.config.policy), ...agentOverrides[toolName] };
36529
36633
  } else if (!resolvedAgentKid && this.config.multiAgent.unknownAgentPolicy === "deny") {
36530
- this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "unknown_agent_denied", request_id: requestId, tier: this.currentTier });
36634
+ this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "unknown_agent_denied", request_id: requestId, tier: this.currentTier, action_readback: actionReadback });
36531
36635
  if (this.config.enforce) {
36532
36636
  return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied: unidentified agent`);
36533
36637
  }
@@ -36548,7 +36652,7 @@ var ProtectGateway = class {
36548
36652
  if (cred.resolved) {
36549
36653
  credentialRef = cred.label;
36550
36654
  } else if (cred.error && !cred.error.includes("not configured")) {
36551
- this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "credential_error", request_id: requestId, tier: this.currentTier, credential_ref: toolName });
36655
+ this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "credential_error", request_id: requestId, tier: this.currentTier, credential_ref: toolName, action_readback: actionReadback });
36552
36656
  if (this.config.enforce) {
36553
36657
  return this.makeErrorResponse(request.id, -32600, `Credential error for tool "${toolName}"`);
36554
36658
  }
@@ -36563,13 +36667,13 @@ var ProtectGateway = class {
36563
36667
  });
36564
36668
  if (!cedarDecision.allowed) {
36565
36669
  const reason = cedarDecision.reason || "cedar_deny";
36566
- this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36670
+ this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36567
36671
  if (this.config.enforce) {
36568
36672
  return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied by Cedar policy`);
36569
36673
  }
36570
36674
  return null;
36571
36675
  }
36572
- this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "cedar_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36676
+ this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "cedar_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36573
36677
  return null;
36574
36678
  } catch (err) {
36575
36679
  if (this.config.verbose) this.log(`Cedar evaluation error: ${err instanceof Error ? err.message : err}`);
@@ -36587,7 +36691,7 @@ var ProtectGateway = class {
36587
36691
  const externalDecision = await queryExternalPDP(ctx, this.config.policy.external);
36588
36692
  if (!externalDecision.allowed) {
36589
36693
  const reason = `external_pdp_deny${externalDecision.reason ? ": " + externalDecision.reason : ""}`;
36590
- this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36694
+ this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36591
36695
  if (this.config.enforce) {
36592
36696
  return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied by external policy engine`);
36593
36697
  }
@@ -36599,7 +36703,7 @@ var ProtectGateway = class {
36599
36703
  }
36600
36704
  if (toolPolicy.min_tier) {
36601
36705
  if (!meetsMinTier(this.currentTier, toolPolicy.min_tier)) {
36602
- this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "tier_insufficient", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36706
+ this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "tier_insufficient", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36603
36707
  if (this.config.enforce) {
36604
36708
  return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" requires tier "${toolPolicy.min_tier}"`);
36605
36709
  }
@@ -36607,7 +36711,7 @@ var ProtectGateway = class {
36607
36711
  }
36608
36712
  }
36609
36713
  if (toolPolicy.block) {
36610
- this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "policy_block", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36714
+ this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "policy_block", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36611
36715
  if (this.config.enforce) {
36612
36716
  return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" is blocked by policy`);
36613
36717
  }
@@ -36618,10 +36722,10 @@ var ProtectGateway = class {
36618
36722
  const alwaysGrant = this.approvalStore.get(`always:${toolName}`);
36619
36723
  if (grant && Date.now() < grant.expires_at || alwaysGrant && Date.now() < alwaysGrant.expires_at) {
36620
36724
  if (grant && grant.mode === "once") this.approvalStore.delete(requestId);
36621
- this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "approval_granted", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36725
+ this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "approval_granted", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36622
36726
  return null;
36623
36727
  }
36624
- this.emitDecisionLog({ tool: toolName, decision: "require_approval", reason_code: "requires_human_approval", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36728
+ this.emitDecisionLog({ tool: toolName, decision: "require_approval", reason_code: "requires_human_approval", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36625
36729
  if (this.notificationConfig) {
36626
36730
  sendApprovalNotification(this.notificationConfig, {
36627
36731
  requestId,
@@ -36644,7 +36748,7 @@ var ProtectGateway = class {
36644
36748
  content: [
36645
36749
  {
36646
36750
  type: "text",
36647
- text: `REQUIRES_APPROVAL: The tool "${toolName}" requires human approval before execution. Request ID: ${requestId}. Approval nonce: ${this.approvalNonce}. Tell the user you need their approval to use "${toolName}" and will retry when granted. Do NOT retry this tool call until the user explicitly approves it.`
36751
+ text: `REQUIRES_APPROVAL: The tool "${toolName}" requires human approval before execution. Exact action: ${actionReadback.summary}. Payload hash: ${actionReadback.payload_hash.slice(0, 16)}\u2026 Request ID: ${requestId}. Approval nonce: ${this.approvalNonce}. Tell the user you need their approval to use "${toolName}" and will retry when granted. Do NOT retry this tool call until the user explicitly approves it.`
36648
36752
  }
36649
36753
  ],
36650
36754
  isError: true
@@ -36660,19 +36764,19 @@ var ProtectGateway = class {
36660
36764
  const key = `tool:${toolName}:${this.currentTier}`;
36661
36765
  const { allowed, remaining } = checkRateLimit(key, limit, this.rateLimitStore);
36662
36766
  if (!allowed) {
36663
- this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "rate_limit_exceeded", request_id: requestId, rate_limit_remaining: 0, tier: this.currentTier, credential_ref: credentialRef });
36767
+ this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "rate_limit_exceeded", request_id: requestId, rate_limit_remaining: 0, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36664
36768
  if (this.config.enforce) {
36665
36769
  return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" rate limit exceeded (${rateSpec})`);
36666
36770
  }
36667
36771
  return null;
36668
36772
  }
36669
- this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "policy_allow", request_id: requestId, rate_limit_remaining: remaining, tier: this.currentTier, credential_ref: credentialRef });
36773
+ this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "policy_allow", request_id: requestId, rate_limit_remaining: remaining, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36670
36774
  } catch {
36671
- this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "default_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36775
+ this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "default_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36672
36776
  }
36673
36777
  } else {
36674
36778
  const reasonCode = this.config.enforce ? "policy_allow" : "observe_mode";
36675
- this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: reasonCode, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
36779
+ this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: reasonCode, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
36676
36780
  }
36677
36781
  return null;
36678
36782
  }
@@ -36694,8 +36798,8 @@ var ProtectGateway = class {
36694
36798
  */
36695
36799
  emitDecisionLog(entry) {
36696
36800
  const mode = this.config.enforce ? "enforce" : "shadow";
36697
- const otelTraceId = entry.otel_trace_id || (0, import_node_crypto3.randomBytes)(16).toString("hex");
36698
- const otelSpanId = entry.otel_span_id || (0, import_node_crypto3.randomBytes)(8).toString("hex");
36801
+ const otelTraceId = entry.otel_trace_id || (0, import_node_crypto4.randomBytes)(16).toString("hex");
36802
+ const otelSpanId = entry.otel_span_id || (0, import_node_crypto4.randomBytes)(8).toString("hex");
36699
36803
  const log = {
36700
36804
  v: 2,
36701
36805
  tool: entry.tool || "unknown",
@@ -36703,12 +36807,13 @@ var ProtectGateway = class {
36703
36807
  reason_code: entry.reason_code || "default_allow",
36704
36808
  policy_digest: this.config.policyDigest,
36705
36809
  policy_engine: this.config.policy?.policy_engine || "built-in",
36706
- request_id: entry.request_id || (0, import_node_crypto3.randomUUID)().slice(0, 12),
36810
+ request_id: entry.request_id || (0, import_node_crypto4.randomUUID)().slice(0, 12),
36707
36811
  timestamp: Date.now(),
36708
36812
  mode,
36709
36813
  ...entry.rate_limit_remaining !== void 0 && { rate_limit_remaining: entry.rate_limit_remaining },
36710
36814
  ...entry.tier && { tier: entry.tier },
36711
36815
  ...entry.credential_ref && { credential_ref: entry.credential_ref },
36816
+ ...entry.action_readback && { action_readback: entry.action_readback },
36712
36817
  otel_trace_id: otelTraceId,
36713
36818
  otel_span_id: otelSpanId
36714
36819
  };
@@ -36894,6 +36999,12 @@ function abytes(b, ...lengths) {
36894
36999
  if (lengths.length > 0 && !lengths.includes(b.length))
36895
37000
  throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
36896
37001
  }
37002
+ function ahash(h) {
37003
+ if (typeof h !== "function" || typeof h.create !== "function")
37004
+ throw new Error("Hash should be wrapped by utils.createHasher");
37005
+ anumber(h.outputLen);
37006
+ anumber(h.blockLen);
37007
+ }
36897
37008
  function aexists(instance, checkFinished = true) {
36898
37009
  if (instance.destroyed)
36899
37010
  throw new Error("Hash instance has been destroyed");
@@ -37129,6 +37240,24 @@ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
37129
37240
  528734635,
37130
37241
  1541459225
37131
37242
  ]);
37243
+ var SHA384_IV = /* @__PURE__ */ Uint32Array.from([
37244
+ 3418070365,
37245
+ 3238371032,
37246
+ 1654270250,
37247
+ 914150663,
37248
+ 2438529370,
37249
+ 812702999,
37250
+ 355462360,
37251
+ 4144912697,
37252
+ 1731405415,
37253
+ 4290775857,
37254
+ 2394180231,
37255
+ 1750603025,
37256
+ 3675008525,
37257
+ 1694076839,
37258
+ 1203062813,
37259
+ 3204075428
37260
+ ]);
37132
37261
  var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
37133
37262
  1779033703,
37134
37263
  4089235720,
@@ -37518,8 +37647,30 @@ var SHA512 = class extends HashMD {
37518
37647
  this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
37519
37648
  }
37520
37649
  };
37650
+ var SHA384 = class extends SHA512 {
37651
+ constructor() {
37652
+ super(48);
37653
+ this.Ah = SHA384_IV[0] | 0;
37654
+ this.Al = SHA384_IV[1] | 0;
37655
+ this.Bh = SHA384_IV[2] | 0;
37656
+ this.Bl = SHA384_IV[3] | 0;
37657
+ this.Ch = SHA384_IV[4] | 0;
37658
+ this.Cl = SHA384_IV[5] | 0;
37659
+ this.Dh = SHA384_IV[6] | 0;
37660
+ this.Dl = SHA384_IV[7] | 0;
37661
+ this.Eh = SHA384_IV[8] | 0;
37662
+ this.El = SHA384_IV[9] | 0;
37663
+ this.Fh = SHA384_IV[10] | 0;
37664
+ this.Fl = SHA384_IV[11] | 0;
37665
+ this.Gh = SHA384_IV[12] | 0;
37666
+ this.Gl = SHA384_IV[13] | 0;
37667
+ this.Hh = SHA384_IV[14] | 0;
37668
+ this.Hl = SHA384_IV[15] | 0;
37669
+ }
37670
+ };
37521
37671
  var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
37522
37672
  var sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
37673
+ var sha384 = /* @__PURE__ */ createHasher(() => new SHA384());
37523
37674
 
37524
37675
  // node_modules/@noble/curves/esm/utils.js
37525
37676
  var _0n = /* @__PURE__ */ BigInt(0);
@@ -37543,6 +37694,10 @@ function _abytes2(value, length, title = "") {
37543
37694
  }
37544
37695
  return value;
37545
37696
  }
37697
+ function numberToHexUnpadded(num) {
37698
+ const hex = num.toString(16);
37699
+ return hex.length & 1 ? "0" + hex : hex;
37700
+ }
37546
37701
  function hexToNumber(hex) {
37547
37702
  if (typeof hex !== "string")
37548
37703
  throw new Error("hex string expected, got " + typeof hex);
@@ -37605,6 +37760,56 @@ function bitLen(n) {
37605
37760
  return len;
37606
37761
  }
37607
37762
  var bitMask = (n) => (_1n << BigInt(n)) - _1n;
37763
+ function createHmacDrbg(hashLen, qByteLen, hmacFn) {
37764
+ if (typeof hashLen !== "number" || hashLen < 2)
37765
+ throw new Error("hashLen must be a number");
37766
+ if (typeof qByteLen !== "number" || qByteLen < 2)
37767
+ throw new Error("qByteLen must be a number");
37768
+ if (typeof hmacFn !== "function")
37769
+ throw new Error("hmacFn must be a function");
37770
+ const u8n = (len) => new Uint8Array(len);
37771
+ const u8of = (byte) => Uint8Array.of(byte);
37772
+ let v = u8n(hashLen);
37773
+ let k = u8n(hashLen);
37774
+ let i = 0;
37775
+ const reset = () => {
37776
+ v.fill(1);
37777
+ k.fill(0);
37778
+ i = 0;
37779
+ };
37780
+ const h = (...b) => hmacFn(k, v, ...b);
37781
+ const reseed = (seed = u8n(0)) => {
37782
+ k = h(u8of(0), seed);
37783
+ v = h();
37784
+ if (seed.length === 0)
37785
+ return;
37786
+ k = h(u8of(1), seed);
37787
+ v = h();
37788
+ };
37789
+ const gen = () => {
37790
+ if (i++ >= 1e3)
37791
+ throw new Error("drbg: tried 1000 values");
37792
+ let len = 0;
37793
+ const out = [];
37794
+ while (len < qByteLen) {
37795
+ v = h();
37796
+ const sl = v.slice();
37797
+ out.push(sl);
37798
+ len += v.length;
37799
+ }
37800
+ return concatBytes(...out);
37801
+ };
37802
+ const genUntil = (seed, pred) => {
37803
+ reset();
37804
+ reseed(seed);
37805
+ let res = void 0;
37806
+ while (!(res = pred(gen())))
37807
+ reseed();
37808
+ reset();
37809
+ return res;
37810
+ };
37811
+ return genUntil;
37812
+ }
37608
37813
  function _validateObject(object, fields, optFields = {}) {
37609
37814
  if (!object || typeof object !== "object")
37610
37815
  throw new Error("expected valid options object");
@@ -37959,6 +38164,26 @@ function Field(ORDER, bitLenOrOpts, isLE = false, opts = {}) {
37959
38164
  });
37960
38165
  return Object.freeze(f);
37961
38166
  }
38167
+ function getFieldBytesLength(fieldOrder) {
38168
+ if (typeof fieldOrder !== "bigint")
38169
+ throw new Error("field order must be bigint");
38170
+ const bitLength = fieldOrder.toString(2).length;
38171
+ return Math.ceil(bitLength / 8);
38172
+ }
38173
+ function getMinHashLength(fieldOrder) {
38174
+ const length = getFieldBytesLength(fieldOrder);
38175
+ return length + Math.ceil(length / 2);
38176
+ }
38177
+ function mapHashToField(key, fieldOrder, isLE = false) {
38178
+ const len = key.length;
38179
+ const fieldLen = getFieldBytesLength(fieldOrder);
38180
+ const minLen = getMinHashLength(fieldOrder);
38181
+ if (len < 16 || len < minLen || len > 1024)
38182
+ throw new Error("expected " + minLen + "-1024 bytes of input, got " + len);
38183
+ const num = isLE ? bytesToNumberLE(key) : bytesToNumberBE(key);
38184
+ const reduced = mod(num, fieldOrder - _1n2) + _1n2;
38185
+ return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
38186
+ }
37962
38187
 
37963
38188
  // node_modules/@noble/curves/esm/abstract/curve.js
37964
38189
  var _0n3 = BigInt(0);
@@ -38152,6 +38377,21 @@ var wNAF = class {
38152
38377
  return getW(elm) !== 1;
38153
38378
  }
38154
38379
  };
38380
+ function mulEndoUnsafe(Point, point, k1, k2) {
38381
+ let acc = point;
38382
+ let p1 = Point.ZERO;
38383
+ let p2 = Point.ZERO;
38384
+ while (k1 > _0n3 || k2 > _0n3) {
38385
+ if (k1 & _1n3)
38386
+ p1 = p1.add(acc);
38387
+ if (k2 & _1n3)
38388
+ p2 = p2.add(acc);
38389
+ acc = acc.double();
38390
+ k1 >>= _1n3;
38391
+ k2 >>= _1n3;
38392
+ }
38393
+ return { p1, p2 };
38394
+ }
38155
38395
  function pippenger(c, fieldN, points, scalars) {
38156
38396
  validateMSMPoints(points, c);
38157
38397
  validateMSMScalars(scalars, fieldN);
@@ -39058,6 +39298,50 @@ function collectPath(leaves, index, out) {
39058
39298
  out.push(merkleRoot(leaves.slice(0, k)));
39059
39299
  }
39060
39300
  }
39301
+ function verifyProof(expectedRootHex, leafHash, proof) {
39302
+ if (proof.index < 0 || proof.index >= proof.treeSize) return false;
39303
+ if (proof.treeSize === 1) {
39304
+ return proof.siblings.length === 0 && bytesToHex(leafHash).toLowerCase() === expectedRootHex.toLowerCase();
39305
+ }
39306
+ let result;
39307
+ try {
39308
+ result = reconstructRoot(
39309
+ leafHash,
39310
+ proof.index,
39311
+ proof.treeSize,
39312
+ proof.siblings
39313
+ );
39314
+ } catch {
39315
+ return false;
39316
+ }
39317
+ return bytesToHex(result).toLowerCase() === expectedRootHex.toLowerCase();
39318
+ }
39319
+ function reconstructRoot(leafHash, index, treeSize, siblings) {
39320
+ if (treeSize === 1) {
39321
+ if (siblings.length !== 0) {
39322
+ throw new Error("reconstructRoot: extra siblings at single-leaf level");
39323
+ }
39324
+ return leafHash;
39325
+ }
39326
+ if (siblings.length === 0) {
39327
+ throw new Error("reconstructRoot: ran out of siblings before single-leaf");
39328
+ }
39329
+ const k = largestPowerOfTwoLessThan(treeSize);
39330
+ const outermostSibling = hexToBytes(siblings[siblings.length - 1]);
39331
+ const innerSiblings = siblings.slice(0, -1);
39332
+ if (index < k) {
39333
+ const leftHash = reconstructRoot(leafHash, index, k, innerSiblings);
39334
+ return hashInternal(leftHash, outermostSibling);
39335
+ } else {
39336
+ const rightHash = reconstructRoot(
39337
+ leafHash,
39338
+ index - k,
39339
+ treeSize - k,
39340
+ innerSiblings
39341
+ );
39342
+ return hashInternal(outermostSibling, rightHash);
39343
+ }
39344
+ }
39061
39345
  function largestPowerOfTwoLessThan(n) {
39062
39346
  if (n < 2) {
39063
39347
  throw new Error(`largestPowerOfTwoLessThan: n must be >= 2 (got ${n})`);
@@ -39067,6 +39351,74 @@ function largestPowerOfTwoLessThan(n) {
39067
39351
  return k;
39068
39352
  }
39069
39353
 
39354
+ // node_modules/@noble/hashes/esm/hmac.js
39355
+ var HMAC = class extends Hash {
39356
+ constructor(hash, _key) {
39357
+ super();
39358
+ this.finished = false;
39359
+ this.destroyed = false;
39360
+ ahash(hash);
39361
+ const key = toBytes(_key);
39362
+ this.iHash = hash.create();
39363
+ if (typeof this.iHash.update !== "function")
39364
+ throw new Error("Expected instance of class which extends utils.Hash");
39365
+ this.blockLen = this.iHash.blockLen;
39366
+ this.outputLen = this.iHash.outputLen;
39367
+ const blockLen = this.blockLen;
39368
+ const pad = new Uint8Array(blockLen);
39369
+ pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
39370
+ for (let i = 0; i < pad.length; i++)
39371
+ pad[i] ^= 54;
39372
+ this.iHash.update(pad);
39373
+ this.oHash = hash.create();
39374
+ for (let i = 0; i < pad.length; i++)
39375
+ pad[i] ^= 54 ^ 92;
39376
+ this.oHash.update(pad);
39377
+ clean(pad);
39378
+ }
39379
+ update(buf) {
39380
+ aexists(this);
39381
+ this.iHash.update(buf);
39382
+ return this;
39383
+ }
39384
+ digestInto(out) {
39385
+ aexists(this);
39386
+ abytes(out, this.outputLen);
39387
+ this.finished = true;
39388
+ this.iHash.digestInto(out);
39389
+ this.oHash.update(out);
39390
+ this.oHash.digestInto(out);
39391
+ this.destroy();
39392
+ }
39393
+ digest() {
39394
+ const out = new Uint8Array(this.oHash.outputLen);
39395
+ this.digestInto(out);
39396
+ return out;
39397
+ }
39398
+ _cloneInto(to) {
39399
+ to || (to = Object.create(Object.getPrototypeOf(this), {}));
39400
+ const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
39401
+ to = to;
39402
+ to.finished = finished;
39403
+ to.destroyed = destroyed;
39404
+ to.blockLen = blockLen;
39405
+ to.outputLen = outputLen;
39406
+ to.oHash = oHash._cloneInto(to.oHash);
39407
+ to.iHash = iHash._cloneInto(to.iHash);
39408
+ return to;
39409
+ }
39410
+ clone() {
39411
+ return this._cloneInto();
39412
+ }
39413
+ destroy() {
39414
+ this.destroyed = true;
39415
+ this.oHash.destroy();
39416
+ this.iHash.destroy();
39417
+ }
39418
+ };
39419
+ var hmac = (hash, key, message) => new HMAC(hash, key).update(message).digest();
39420
+ hmac.create = (hash, key) => new HMAC(hash, key);
39421
+
39070
39422
  // src/commitments/primitives.ts
39071
39423
  function jcs(value) {
39072
39424
  if (value === null || value === void 0) return "null";
@@ -39085,6 +39437,17 @@ function base64urlNoPad(bytes) {
39085
39437
  const std = typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : btoa(String.fromCharCode(...bytes));
39086
39438
  return std.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
39087
39439
  }
39440
+ function base64urlDecode(s) {
39441
+ const std = s.replace(/-/g, "+").replace(/_/g, "/");
39442
+ const padded = std + "=".repeat((4 - std.length % 4) % 4);
39443
+ if (typeof Buffer !== "undefined") {
39444
+ return new Uint8Array(Buffer.from(padded, "base64"));
39445
+ }
39446
+ const bin = atob(padded);
39447
+ const out = new Uint8Array(bin.length);
39448
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
39449
+ return out;
39450
+ }
39088
39451
  function encodeLeaf(field) {
39089
39452
  const obj = {
39090
39453
  name: field.name,
@@ -39218,11 +39581,128 @@ function discloseField(receiptHash, fieldName, openings) {
39218
39581
  proof
39219
39582
  };
39220
39583
  }
39584
+ function createSelectiveDisclosurePackage(receipt, fieldNames, openings) {
39585
+ const receiptHash = receiptHashHex(receipt);
39586
+ const committedFieldsRoot = typeof receipt.committed_fields_root === "string" ? receipt.committed_fields_root : "";
39587
+ if (!committedFieldsRoot) {
39588
+ throw new Error("selective disclosure requires a committed receipt with committed_fields_root");
39589
+ }
39590
+ const committedFieldNames = committedFieldNamesFromReceipt(receipt, openings);
39591
+ const uniqueFields = Array.from(new Set(fieldNames));
39592
+ for (const fieldName of uniqueFields) {
39593
+ if (!committedFieldNames.includes(fieldName)) {
39594
+ throw new Error(`selective disclosure: field "${fieldName}" is not committed by this receipt`);
39595
+ }
39596
+ }
39597
+ const disclosures = uniqueFields.map((fieldName) => discloseField(receiptHash, fieldName, openings));
39598
+ const hiddenFields = committedFieldNames.filter((fieldName) => !uniqueFields.includes(fieldName));
39599
+ return {
39600
+ type: "scopeblind.selective_disclosure.v0",
39601
+ version: 0,
39602
+ parent_receipt_hash: receiptHash,
39603
+ committed_fields_root: committedFieldsRoot,
39604
+ disclosed_fields: uniqueFields,
39605
+ hidden_fields: hiddenFields,
39606
+ disclosures,
39607
+ verifier_explanation: {
39608
+ summary: "This package opens selected committed receipt fields and leaves the rest hidden.",
39609
+ disclosed: uniqueFields.length ? `Disclosed fields: ${uniqueFields.join(", ")}.` : "No fields were disclosed.",
39610
+ hidden: hiddenFields.length ? `Hidden committed fields: ${hiddenFields.join(", ")}. Their salted commitments remain bound to the signed receipt root.` : "No committed fields remain hidden.",
39611
+ limitation: "Selective Disclosure v0 uses salted SHA-256 commitments and Merkle proofs. It is not a full zero-knowledge proof."
39612
+ }
39613
+ };
39614
+ }
39615
+ function verifySelectiveDisclosurePackage(receipt, disclosure) {
39616
+ const errors = [];
39617
+ if (disclosure.type !== "scopeblind.selective_disclosure.v0") {
39618
+ errors.push("disclosure.type is not scopeblind.selective_disclosure.v0");
39619
+ }
39620
+ const actualReceiptHash = receiptHashHex(receipt);
39621
+ const receiptHashValid = disclosure.parent_receipt_hash === actualReceiptHash;
39622
+ if (!receiptHashValid) {
39623
+ errors.push("parent_receipt_hash does not match the supplied receipt");
39624
+ }
39625
+ const root = typeof receipt.committed_fields_root === "string" ? receipt.committed_fields_root : "";
39626
+ const commitmentRootValid = Boolean(root) && disclosure.committed_fields_root === root;
39627
+ if (!commitmentRootValid) {
39628
+ errors.push("committed_fields_root does not match the supplied receipt");
39629
+ }
39630
+ const signatureValid = verifyCommittedReceiptSignature(receipt);
39631
+ if (signatureValid === false) {
39632
+ errors.push("receipt signature failed verification");
39633
+ }
39634
+ const committedFieldNames = committedFieldNamesFromReceipt(receipt, {});
39635
+ const disclosed = /* @__PURE__ */ new Set();
39636
+ for (const item of disclosure.disclosures || []) {
39637
+ if (item.parent_receipt_hash !== disclosure.parent_receipt_hash) {
39638
+ errors.push(`disclosure for "${item.name}" targets a different receipt hash`);
39639
+ continue;
39640
+ }
39641
+ if (!committedFieldNames.includes(item.name)) {
39642
+ errors.push(`field "${item.name}" is not listed in committed_field_names`);
39643
+ continue;
39644
+ }
39645
+ const leafBytes = encodeLeaf({
39646
+ name: item.name,
39647
+ salt: base64urlDecode(item.salt),
39648
+ value: item.value
39649
+ });
39650
+ const ok = root ? verifyProof(root, hashLeaf(leafBytes), item.proof) : false;
39651
+ if (!ok) {
39652
+ errors.push(`field "${item.name}" failed Merkle inclusion verification`);
39653
+ } else {
39654
+ disclosed.add(item.name);
39655
+ }
39656
+ }
39657
+ const disclosedFields = Array.from(disclosed);
39658
+ const hiddenFields = committedFieldNames.filter((fieldName) => !disclosed.has(fieldName));
39659
+ const valid = errors.length === 0 && receiptHashValid && commitmentRootValid && signatureValid !== false;
39660
+ const explanation = [
39661
+ valid ? "Selective disclosure verified: the disclosed fields open to the signed receipt commitment root." : "Selective disclosure failed verification.",
39662
+ signatureValid === true ? "Receipt signature verified against the embedded Ed25519 public key." : signatureValid === null ? "Receipt signature was not checked because the committed receipt did not carry an embedded Ed25519 signature object." : "Receipt signature did not verify.",
39663
+ disclosedFields.length ? `Disclosed fields: ${disclosedFields.join(", ")}.` : "No fields were disclosed.",
39664
+ hiddenFields.length ? `Hidden fields: ${hiddenFields.join(", ")}. These remain private but bound to the same commitment root.` : "No committed fields remain hidden.",
39665
+ "Limitation: this is salted commitment disclosure, not full zero-knowledge."
39666
+ ];
39667
+ return {
39668
+ valid,
39669
+ receipt_hash_valid: receiptHashValid,
39670
+ signature_valid: signatureValid,
39671
+ commitment_root_valid: commitmentRootValid,
39672
+ disclosed_fields: disclosedFields,
39673
+ hidden_fields: hiddenFields,
39674
+ errors,
39675
+ explanation
39676
+ };
39677
+ }
39678
+ function committedFieldNamesFromReceipt(receipt, openings) {
39679
+ const fromReceipt = Array.isArray(receipt.committed_field_names) ? receipt.committed_field_names.filter((fieldName) => typeof fieldName === "string") : [];
39680
+ const names = fromReceipt.length ? fromReceipt : Object.keys(openings);
39681
+ return Array.from(new Set(names)).sort();
39682
+ }
39683
+ function receiptHashHex(receipt) {
39684
+ return bytesToHex(sha2562(new TextEncoder().encode(jcs(receipt))));
39685
+ }
39686
+ function verifyCommittedReceiptSignature(receipt) {
39687
+ const signature = receipt.signature;
39688
+ if (!signature || typeof signature !== "object") return null;
39689
+ const sig = signature;
39690
+ if (sig.alg !== "EdDSA" || typeof sig.sig !== "string" || typeof sig.public_key !== "string") {
39691
+ return null;
39692
+ }
39693
+ const { signature: _signature, ...payloadWithoutSig } = receipt;
39694
+ const messageHash = sha2562(new TextEncoder().encode(jcs(payloadWithoutSig)));
39695
+ try {
39696
+ return ed25519.verify(base64urlDecode(sig.sig), messageHash, hexToBytes(sig.public_key));
39697
+ } catch {
39698
+ return false;
39699
+ }
39700
+ }
39221
39701
 
39222
39702
  // src/bundle.ts
39223
39703
  function createAuditBundle(opts) {
39224
39704
  const receipts = opts.receipts.filter(
39225
- (r) => r && typeof r === "object" && typeof r.signature === "string"
39705
+ (r) => r && typeof r === "object" && (typeof r.signature === "string" || r.signature !== null && typeof r.signature === "object")
39226
39706
  );
39227
39707
  if (receipts.length === 0) {
39228
39708
  throw new Error("Audit bundle requires at least one signed receipt");
@@ -39251,10 +39731,18 @@ function createAuditBundle(opts) {
39251
39731
  time_range: timeRange,
39252
39732
  receipts,
39253
39733
  anchors: opts.anchors || [],
39734
+ selective_disclosures: opts.selectiveDisclosures || [],
39735
+ privacy: {
39736
+ selective_disclosure: {
39737
+ supported: true,
39738
+ model: "salted_commitments_merkle_v0",
39739
+ statement: "Committed receipts may disclose selected fields with salted Merkle openings. Undisclosed committed fields remain hidden while staying bound to the signed commitment root."
39740
+ }
39741
+ },
39254
39742
  verification: {
39255
39743
  algorithm: "ed25519",
39256
39744
  signing_keys: Array.from(keyMap.values()),
39257
- instructions: `Verify each receipt by: (1) remove the "signature" field, (2) canonicalize the remaining object with JCS (sorted keys at every level), (3) encode as UTF-8 bytes, (4) verify the Ed25519 signature using the signing key matching the receipt's "kid" field. CLI: npx @veritasacta/verify bundle.json --bundle`
39745
+ instructions: `Verify each receipt by: (1) remove the "signature" field, (2) canonicalize the remaining object with JCS (sorted keys at every level), (3) encode as UTF-8 bytes, (4) verify the Ed25519 signature using the signing key matching the receipt's "kid" field. For scopeblind.selective_disclosure.v0 packages, recompute each disclosed leaf and verify it against the receipt committed_fields_root; fields not disclosed remain hidden. CLI: npx @veritasacta/verify bundle.json --bundle`
39258
39746
  }
39259
39747
  };
39260
39748
  }
@@ -39717,7 +40205,7 @@ function validateEvidenceReceipt(receipt) {
39717
40205
 
39718
40206
  // src/hook-server.ts
39719
40207
  var import_node_http2 = require("http");
39720
- var import_node_crypto4 = require("crypto");
40208
+ var import_node_crypto5 = require("crypto");
39721
40209
  var import_node_fs9 = require("fs");
39722
40210
  var import_node_path5 = require("path");
39723
40211
 
@@ -39900,7 +40388,7 @@ function computePayloadDigest(input) {
39900
40388
  return void 0;
39901
40389
  }
39902
40390
  return {
39903
- input_hash: (0, import_node_crypto4.createHash)("sha256").update(content).digest("hex"),
40391
+ input_hash: (0, import_node_crypto5.createHash)("sha256").update(content).digest("hex"),
39904
40392
  input_size: size,
39905
40393
  truncated: true,
39906
40394
  preview: content.slice(0, 256)
@@ -39913,7 +40401,7 @@ function computeOutputDigest(output) {
39913
40401
  return void 0;
39914
40402
  }
39915
40403
  return {
39916
- output_hash: (0, import_node_crypto4.createHash)("sha256").update(content).digest("hex"),
40404
+ output_hash: (0, import_node_crypto5.createHash)("sha256").update(content).digest("hex"),
39917
40405
  output_size: size
39918
40406
  };
39919
40407
  }
@@ -39936,13 +40424,14 @@ function detectSandboxState() {
39936
40424
  async function handlePreToolUse(input, state) {
39937
40425
  const hookStart = Date.now();
39938
40426
  const toolName = input.toolName || "unknown";
39939
- const requestId = input.toolUseId || (0, import_node_crypto4.randomUUID)().slice(0, 12);
40427
+ const requestId = input.toolUseId || (0, import_node_crypto5.randomUUID)().slice(0, 12);
39940
40428
  state.inflightTools.set(requestId, {
39941
40429
  tool: toolName,
39942
40430
  startedAt: hookStart,
39943
40431
  requestId
39944
40432
  });
39945
40433
  const payloadDigest = computePayloadDigest(input.toolInput);
40434
+ const actionReadback = buildActionReadback(toolName, input.toolInput || {});
39946
40435
  const swarm = {
39947
40436
  ...state.swarmContext,
39948
40437
  ...input.agentId && { agent_id: input.agentId },
@@ -39982,6 +40471,7 @@ async function handlePreToolUse(input, state) {
39982
40471
  swarm: swarm.team_name ? swarm : void 0,
39983
40472
  timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
39984
40473
  payload_digest: payloadDigest,
40474
+ action_readback: actionReadback,
39985
40475
  deny_iteration: denyCount,
39986
40476
  sandbox_state: detectSandboxState(),
39987
40477
  plan_receipt_id: state.activePlanReceiptId || void 0
@@ -40016,6 +40506,7 @@ async function handlePreToolUse(input, state) {
40016
40506
  swarm: swarm.team_name ? swarm : void 0,
40017
40507
  timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
40018
40508
  payload_digest: payloadDigest,
40509
+ action_readback: actionReadback,
40019
40510
  sandbox_state: detectSandboxState()
40020
40511
  });
40021
40512
  return {
@@ -40040,6 +40531,7 @@ async function handlePreToolUse(input, state) {
40040
40531
  swarm: swarm.team_name ? swarm : void 0,
40041
40532
  timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
40042
40533
  payload_digest: payloadDigest,
40534
+ action_readback: actionReadback,
40043
40535
  sandbox_state: detectSandboxState()
40044
40536
  });
40045
40537
  return {
@@ -40060,13 +40552,15 @@ async function handlePreToolUse(input, state) {
40060
40552
  hook_event: "PreToolUse",
40061
40553
  swarm: swarm.team_name ? swarm : void 0,
40062
40554
  timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
40555
+ payload_digest: payloadDigest,
40556
+ action_readback: actionReadback,
40063
40557
  sandbox_state: detectSandboxState()
40064
40558
  });
40065
40559
  return {
40066
40560
  hookSpecificOutput: {
40067
40561
  hookEventName: "PreToolUse",
40068
40562
  permissionDecision: "ask",
40069
- permissionDecisionReason: `[ScopeBlind] "${toolName}" requires human approval. Policy: ${state.policyDigest}`
40563
+ permissionDecisionReason: `[ScopeBlind] Approval required for exactly this action: ${actionReadback.summary}. Payload hash: ${actionReadback.payload_hash.slice(0, 16)}\u2026 Policy: ${state.policyDigest}`
40070
40564
  }
40071
40565
  };
40072
40566
  }
@@ -40111,6 +40605,7 @@ async function handlePreToolUse(input, state) {
40111
40605
  swarm: swarm.team_name ? swarm : void 0,
40112
40606
  timing: { hook_latency_ms: hookLatency, started_at: hookStart },
40113
40607
  payload_digest: payloadDigest,
40608
+ action_readback: actionReadback,
40114
40609
  sandbox_state: detectSandboxState(),
40115
40610
  plan_receipt_id: state.activePlanReceiptId || void 0
40116
40611
  });
@@ -40127,7 +40622,7 @@ async function handlePreToolUse(input, state) {
40127
40622
  }
40128
40623
  async function handlePostToolUse(input, state) {
40129
40624
  const toolName = input.toolName || "unknown";
40130
- const requestId = input.toolUseId || (0, import_node_crypto4.randomUUID)().slice(0, 12);
40625
+ const requestId = input.toolUseId || (0, import_node_crypto5.randomUUID)().slice(0, 12);
40131
40626
  const now = Date.now();
40132
40627
  const inflight = state.inflightTools.get(requestId);
40133
40628
  const timing = {
@@ -40139,7 +40634,7 @@ async function handlePostToolUse(input, state) {
40139
40634
  state.inflightTools.delete(requestId);
40140
40635
  }
40141
40636
  const outputDigest = computeOutputDigest(input.toolResult);
40142
- const receiptId = (0, import_node_crypto4.randomUUID)().slice(0, 8);
40637
+ const receiptId = (0, import_node_crypto5.randomUUID)().slice(0, 8);
40143
40638
  const policyName = state.cedarPolicies ? `cedar:${state.policyDigest}` : state.policyDigest;
40144
40639
  const additionalContext = `[ScopeBlind] Tool call receipted. Policy: ${policyName}. Decision: allow. Receipt: #${receiptId}.` + (timing.tool_duration_ms !== void 0 ? ` Duration: ${timing.tool_duration_ms}ms.` : "") + (timing.hook_latency_ms !== void 0 ? ` Overhead: ${timing.hook_latency_ms}ms.` : "");
40145
40640
  emitDecisionLog(state, {
@@ -40171,7 +40666,7 @@ function handleSubagentStart(input, state) {
40171
40666
  tool: `subagent:${agentId}`,
40172
40667
  decision: "allow",
40173
40668
  reason_code: "subagent_started",
40174
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40669
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40175
40670
  hook_event: "SubagentStart",
40176
40671
  swarm: {
40177
40672
  ...state.swarmContext,
@@ -40192,7 +40687,7 @@ function handleSubagentStop(input, state) {
40192
40687
  tool: `subagent:${agentId}`,
40193
40688
  decision: "allow",
40194
40689
  reason_code: "subagent_stopped",
40195
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40690
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40196
40691
  hook_event: "SubagentStop",
40197
40692
  swarm: {
40198
40693
  ...state.swarmContext,
@@ -40207,7 +40702,7 @@ function handleTaskCreated(input, state) {
40207
40702
  tool: `task:${input.taskId || "unknown"}`,
40208
40703
  decision: "allow",
40209
40704
  reason_code: "task_created",
40210
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40705
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40211
40706
  hook_event: "TaskCreated",
40212
40707
  swarm: {
40213
40708
  ...state.swarmContext,
@@ -40221,7 +40716,7 @@ function handleTaskCompleted(input, state) {
40221
40716
  tool: `task:${input.taskId || "unknown"}`,
40222
40717
  decision: "allow",
40223
40718
  reason_code: "task_completed",
40224
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40719
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40225
40720
  hook_event: "TaskCompleted",
40226
40721
  swarm: state.swarmContext
40227
40722
  });
@@ -40232,7 +40727,7 @@ function handleSessionStart(input, state) {
40232
40727
  tool: "session",
40233
40728
  decision: "allow",
40234
40729
  reason_code: "session_started",
40235
- request_id: input.sessionId || (0, import_node_crypto4.randomUUID)().slice(0, 12),
40730
+ request_id: input.sessionId || (0, import_node_crypto5.randomUUID)().slice(0, 12),
40236
40731
  hook_event: "SessionStart",
40237
40732
  swarm: state.swarmContext,
40238
40733
  sandbox_state: detectSandboxState()
@@ -40255,7 +40750,7 @@ function handleSessionEnd(input, state) {
40255
40750
  tool: "session",
40256
40751
  decision: "allow",
40257
40752
  reason_code: "session_ended",
40258
- request_id: input.sessionId || (0, import_node_crypto4.randomUUID)().slice(0, 12),
40753
+ request_id: input.sessionId || (0, import_node_crypto5.randomUUID)().slice(0, 12),
40259
40754
  hook_event: "SessionEnd",
40260
40755
  swarm: state.swarmContext
40261
40756
  });
@@ -40266,7 +40761,7 @@ function handleTeammateIdle(input, state) {
40266
40761
  tool: `teammate:${input.agentId || "unknown"}`,
40267
40762
  decision: "allow",
40268
40763
  reason_code: "teammate_idle",
40269
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40764
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40270
40765
  hook_event: "TeammateIdle",
40271
40766
  swarm: {
40272
40767
  ...state.swarmContext,
@@ -40294,7 +40789,7 @@ function handleConfigChange(input, state) {
40294
40789
  tool: "config",
40295
40790
  decision: "deny",
40296
40791
  reason_code: "config_tamper_detected",
40297
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40792
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40298
40793
  hook_event: "ConfigChange",
40299
40794
  swarm: state.swarmContext
40300
40795
  });
@@ -40303,7 +40798,7 @@ function handleConfigChange(input, state) {
40303
40798
  tool: "config",
40304
40799
  decision: "allow",
40305
40800
  reason_code: "config_changed",
40306
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40801
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40307
40802
  hook_event: "ConfigChange"
40308
40803
  });
40309
40804
  }
@@ -40325,7 +40820,7 @@ function handleStop(input, state) {
40325
40820
  tool: "session",
40326
40821
  decision: "allow",
40327
40822
  reason_code: "agent_stopped",
40328
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40823
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40329
40824
  hook_event: "Stop",
40330
40825
  swarm: state.swarmContext
40331
40826
  });
@@ -40333,8 +40828,8 @@ function handleStop(input, state) {
40333
40828
  }
40334
40829
  function emitDecisionLog(state, entry) {
40335
40830
  const mode = state.enforce ? "enforce" : "shadow";
40336
- const otelTraceId = (0, import_node_crypto4.randomBytes)(16).toString("hex");
40337
- const otelSpanId = (0, import_node_crypto4.randomBytes)(8).toString("hex");
40831
+ const otelTraceId = (0, import_node_crypto5.randomBytes)(16).toString("hex");
40832
+ const otelSpanId = (0, import_node_crypto5.randomBytes)(8).toString("hex");
40338
40833
  const log = {
40339
40834
  v: 2,
40340
40835
  tool: entry.tool || "unknown",
@@ -40342,7 +40837,7 @@ function emitDecisionLog(state, entry) {
40342
40837
  reason_code: entry.reason_code || "default_allow",
40343
40838
  policy_digest: state.policyDigest,
40344
40839
  policy_engine: state.cedarPolicies ? "cedar" : "built-in",
40345
- request_id: entry.request_id || (0, import_node_crypto4.randomUUID)().slice(0, 12),
40840
+ request_id: entry.request_id || (0, import_node_crypto5.randomUUID)().slice(0, 12),
40346
40841
  timestamp: Date.now(),
40347
40842
  mode,
40348
40843
  otel_trace_id: otelTraceId,
@@ -40523,7 +41018,7 @@ async function startHookServer(options = {}) {
40523
41018
  res.end(JSON.stringify({
40524
41019
  status: "ok",
40525
41020
  server: "protect-mcp-hooks",
40526
- version: "0.5.0",
41021
+ version: process.env.PROTECT_MCP_VERSION || "unknown",
40527
41022
  uptime_ms: Date.now() - state.startTime,
40528
41023
  mode: enforce ? "enforce" : "shadow",
40529
41024
  policy_digest: policyDigest,
@@ -40610,9 +41105,10 @@ async function startHookServer(options = {}) {
40610
41105
  const pad = (s, n = 46) => s.padEnd(n);
40611
41106
  w(`
40612
41107
  `);
40613
- w(` protect-mcp v0.5.4
41108
+ w(process.env.PROTECT_MCP_VERSION ? ` protect-mcp v${process.env.PROTECT_MCP_VERSION}
41109
+ ` : ` protect-mcp
40614
41110
  `);
40615
- w(` ScopeBlind \u2014 https://scopeblind.com
41111
+ w(` ScopeBlind \xB7 https://scopeblind.com
40616
41112
  `);
40617
41113
  w(`
40618
41114
  `);
@@ -40640,7 +41136,13 @@ async function startHookServer(options = {}) {
40640
41136
  `);
40641
41137
  w(`
40642
41138
  `);
40643
- w(` deny is authoritative \u2014 cannot be overridden.
41139
+ w(` deny is authoritative: it cannot be overridden.
41140
+ `);
41141
+ w(`
41142
+ `);
41143
+ w(` See your record npx protect-mcp record
41144
+ `);
41145
+ w(` a searchable view of every decision, all on this machine
40644
41146
  `);
40645
41147
  w(`
40646
41148
  `);
@@ -41321,8 +41823,906 @@ function generateSchemaStub(namespace = "ScopeBlind") {
41321
41823
  ].join("\n");
41322
41824
  }
41323
41825
 
41826
+ // src/policy-packs.ts
41827
+ var header = (id, description) => `// ScopeBlind protect-mcp policy pack: ${id}
41828
+ // ${description}
41829
+ // Start in shadow mode, review receipts, then run with --enforce.
41830
+
41831
+ `;
41832
+ var defaultPermit = `
41833
+ // Default posture: allow non-matching calls so teams can start in shadow mode.
41834
+ // Tighten this after reviewing your local action dashboard.
41835
+ permit(principal, action == Action::"MCP::Tool::call", resource);
41836
+ `;
41837
+ var filesystemSafe = `${header("filesystem-safe", "Block common destructive filesystem and secret-file access patterns.")}// Destructive file tools are never safe as an unattended default.
41838
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"delete_file");
41839
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"remove_file");
41840
+
41841
+ // Secret-like reads by path.
41842
+ forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41843
+ context has "input" && context.input has "path" && (
41844
+ context.input.path like "*/.env*" ||
41845
+ context.input.path like "*/id_rsa*" ||
41846
+ context.input.path like "*/.ssh/*" ||
41847
+ context.input.path like "*secret*" ||
41848
+ context.input.path like "*credential*"
41849
+ )
41850
+ };
41851
+
41852
+ // Dangerous shell operations that mutate or destroy local state.
41853
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
41854
+ context has "command" && (
41855
+ context.command like "*rm -rf*" ||
41856
+ context.command like "*mkfs*" ||
41857
+ context.command like "*dd if=*" ||
41858
+ context.command like "*chmod -R 777*" ||
41859
+ context.command like "*chown -R*"
41860
+ )
41861
+ };
41862
+ ${defaultPermit}`;
41863
+ var gitSafe = `${header("git-safe", "Prevent unattended history rewrites, force pushes, and destructive repo cleanup.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
41864
+ context has "command" && (
41865
+ context.command like "*git push --force*" ||
41866
+ context.command like "*git push -f*" ||
41867
+ context.command like "*git reset --hard*" ||
41868
+ context.command like "*git clean -fd*" ||
41869
+ context.command like "*git checkout --*" ||
41870
+ context.command like "*git branch -D*" ||
41871
+ context.command like "*gh repo delete*"
41872
+ )
41873
+ };
41874
+ ${defaultPermit}`;
41875
+ var emailSafe = `${header("email-safe", "Permit drafting but block unattended external sends.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"mail.send");
41876
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"email.send");
41877
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"send_email");
41878
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"gmail.send");
41879
+
41880
+ // Shell fallbacks that send mail are blocked too.
41881
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
41882
+ context has "command" && (
41883
+ context.command like "*sendmail*" ||
41884
+ context.command like "*mailx*" ||
41885
+ context.command like "*smtp*"
41886
+ )
41887
+ };
41888
+ ${defaultPermit}`;
41889
+ var databaseSafe = `${header("database-safe", "Allow reads, block write/admin SQL unless explicitly approved elsewhere.")}forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41890
+ context has "input" && context.input has "query" && (
41891
+ context.input.query like "*DROP *" ||
41892
+ context.input.query like "*TRUNCATE *" ||
41893
+ context.input.query like "*DELETE *" ||
41894
+ context.input.query like "*UPDATE *" ||
41895
+ context.input.query like "*INSERT *" ||
41896
+ context.input.query like "*ALTER *" ||
41897
+ context.input.query like "*GRANT *" ||
41898
+ context.input.query like "*REVOKE *"
41899
+ )
41900
+ };
41901
+ ${defaultPermit}`;
41902
+ var cloudSpendSafe = `${header("cloud-spend-safe", "Block cloud actions that can create spend or destroy infrastructure.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
41903
+ context has "command" && (
41904
+ context.command like "*terraform destroy*" ||
41905
+ context.command like "*terraform apply*" ||
41906
+ context.command like "*pulumi up*" ||
41907
+ context.command like "*pulumi destroy*" ||
41908
+ context.command like "*aws ec2 run-instances*" ||
41909
+ context.command like "*aws rds create*" ||
41910
+ context.command like "*gcloud compute instances create*" ||
41911
+ context.command like "*az vm create*" ||
41912
+ context.command like "*kubectl delete*"
41913
+ )
41914
+ };
41915
+ ${defaultPermit}`;
41916
+ var secretsSafe = `${header("secrets-safe", "Block secret exfiltration from files, env, shell, and common credential tools.")}forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41917
+ context has "input" && context.input has "path" && (
41918
+ context.input.path like "*/.env*" ||
41919
+ context.input.path like "*/.aws/credentials*" ||
41920
+ context.input.path like "*/.npmrc*" ||
41921
+ context.input.path like "*/.netrc*" ||
41922
+ context.input.path like "*/id_rsa*" ||
41923
+ context.input.path like "*secret*" ||
41924
+ context.input.path like "*token*"
41925
+ )
41926
+ };
41927
+
41928
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
41929
+ context has "command" && (
41930
+ context.command like "*printenv*" ||
41931
+ context.command like "*env |*" ||
41932
+ context.command like "*security find-generic-password*" ||
41933
+ context.command like "*aws secretsmanager get-secret-value*" ||
41934
+ context.command like "*gcloud secrets versions access*" ||
41935
+ context.command like "*op read*"
41936
+ )
41937
+ };
41938
+ ${defaultPermit}`;
41939
+ var financeMandateSafe = `${header("finance-mandate-safe", "Block restricted-list and concentration-limit breaches in booking tools.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"pms.book") when {
41940
+ context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
41941
+ };
41942
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"booking.execute") when {
41943
+ context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
41944
+ };
41945
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"booking.ticket") when {
41946
+ context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
41947
+ };
41948
+
41949
+ // Default example caps: single-name > 10%, gross > 200%, net > 100%.
41950
+ forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41951
+ context has "input" && context.input has "post_trade_weight_bps" && context.input.post_trade_weight_bps > 1000
41952
+ };
41953
+ forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41954
+ context has "input" && context.input has "post_trade_gross_exposure_bps" && context.input.post_trade_gross_exposure_bps > 20000
41955
+ };
41956
+ forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41957
+ context has "input" && context.input has "post_trade_net_exposure_bps" && context.input.post_trade_net_exposure_bps > 10000
41958
+ };
41959
+ ${defaultPermit}`;
41960
+ var POLICY_PACKS = [
41961
+ {
41962
+ id: "filesystem-safe",
41963
+ name: "Filesystem Safe",
41964
+ description: "Blocks destructive filesystem calls and secret-like path reads.",
41965
+ recommendedMode: "shadow-first",
41966
+ files: [{ path: "filesystem-safe.cedar", contents: filesystemSafe }]
41967
+ },
41968
+ {
41969
+ id: "git-safe",
41970
+ name: "Git Safe",
41971
+ description: "Blocks force pushes, hard resets, destructive cleanup, and repo deletion.",
41972
+ recommendedMode: "shadow-first",
41973
+ files: [{ path: "git-safe.cedar", contents: gitSafe }]
41974
+ },
41975
+ {
41976
+ id: "email-safe",
41977
+ name: "Email Safe",
41978
+ description: "Allows drafting workflows while blocking unattended sends.",
41979
+ recommendedMode: "shadow-first",
41980
+ files: [{ path: "email-safe.cedar", contents: emailSafe }]
41981
+ },
41982
+ {
41983
+ id: "database-safe",
41984
+ name: "Database Safe",
41985
+ description: "Allows read-oriented DB tools while blocking mutating/admin SQL.",
41986
+ recommendedMode: "shadow-first",
41987
+ files: [{ path: "database-safe.cedar", contents: databaseSafe }]
41988
+ },
41989
+ {
41990
+ id: "cloud-spend-safe",
41991
+ name: "Cloud Spend Safe",
41992
+ description: "Blocks obvious cloud spend creation and infrastructure destruction.",
41993
+ recommendedMode: "shadow-first",
41994
+ files: [{ path: "cloud-spend-safe.cedar", contents: cloudSpendSafe }]
41995
+ },
41996
+ {
41997
+ id: "secrets-safe",
41998
+ name: "Secrets Safe",
41999
+ description: "Blocks common file, env, shell, and cloud secret exfiltration paths.",
42000
+ recommendedMode: "enforce-ready",
42001
+ files: [{ path: "secrets-safe.cedar", contents: secretsSafe }]
42002
+ },
42003
+ {
42004
+ id: "finance-mandate-safe",
42005
+ name: "Finance Mandate Safe",
42006
+ description: "Blocks restricted-list and concentration breaches in booking flows.",
42007
+ recommendedMode: "shadow-first",
42008
+ files: [{ path: "finance-mandate-safe.cedar", contents: financeMandateSafe }]
42009
+ }
42010
+ ];
42011
+ function getPolicyPack(id) {
42012
+ return POLICY_PACKS.find((pack) => pack.id === id);
42013
+ }
42014
+ function policyPackIds() {
42015
+ return POLICY_PACKS.map((pack) => pack.id);
42016
+ }
42017
+
42018
+ // src/connector-pilots.ts
42019
+ var import_node_fs10 = require("fs");
42020
+ var import_node_path6 = require("path");
42021
+ var defaultPermit2 = `
42022
+ // Default posture: observe all non-matching tools so the connector can be piloted in shadow mode.
42023
+ permit(principal, action == Action::"MCP::Tool::call", resource);
42024
+ `;
42025
+ var nautilusBridgePy = String.raw`#!/usr/bin/env python3
42026
+ """
42027
+ ScopeBlind external bridge for NautilusTrader-compatible pilots.
42028
+
42029
+ This file is intentionally outside NautilusTrader. It gives protect-mcp a stable
42030
+ JSONL command boundary for staging, approval-gated submission, cancellation, and
42031
+ event export while keeping the trading engine customer-owned.
42032
+
42033
+ Mock mode runs without NautilusTrader installed. Real mode is enabled by setting
42034
+ NAUTILUS_BRIDGE_MODULE to "module.path:ClassName"; the class may implement:
42035
+ submit_order(order), modify_order(order), cancel_order(order), reconcile(order),
42036
+ export_events(since=None)
42037
+ """
42038
+
42039
+ from __future__ import annotations
42040
+
42041
+ import hashlib
42042
+ import importlib
42043
+ import json
42044
+ import os
42045
+ import sys
42046
+ import time
42047
+ from dataclasses import dataclass, field
42048
+ from pathlib import Path
42049
+ from typing import Any, Callable
42050
+
42051
+
42052
+ def canonical_json(value: Any) -> str:
42053
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
42054
+
42055
+
42056
+ def sha256_json(value: Any) -> str:
42057
+ return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
42058
+
42059
+
42060
+ def now_ms() -> int:
42061
+ return int(time.time() * 1000)
42062
+
42063
+
42064
+ @dataclass
42065
+ class BridgeState:
42066
+ root: Path = field(default_factory=lambda: Path(os.environ.get("SCOPEBLIND_NAUTILUS_STATE_DIR", ".protect-mcp/nautilus")))
42067
+
42068
+ def __post_init__(self) -> None:
42069
+ self.root.mkdir(parents=True, exist_ok=True)
42070
+ self.orders_path.touch(exist_ok=True)
42071
+ self.events_path.touch(exist_ok=True)
42072
+
42073
+ @property
42074
+ def orders_path(self) -> Path:
42075
+ return self.root / "orders.jsonl"
42076
+
42077
+ @property
42078
+ def events_path(self) -> Path:
42079
+ return self.root / "events.jsonl"
42080
+
42081
+ def append_order(self, order: dict[str, Any]) -> None:
42082
+ with self.orders_path.open("a", encoding="utf-8") as handle:
42083
+ handle.write(canonical_json(order) + "\n")
42084
+
42085
+ def append_event(self, event: dict[str, Any]) -> dict[str, Any]:
42086
+ enriched = {
42087
+ "event_id": event.get("event_id") or f"nt-{now_ms()}-{len(event)}",
42088
+ "observed_at_ms": now_ms(),
42089
+ **event,
42090
+ }
42091
+ enriched["event_digest"] = sha256_json(enriched)
42092
+ with self.events_path.open("a", encoding="utf-8") as handle:
42093
+ handle.write(canonical_json(enriched) + "\n")
42094
+ return enriched
42095
+
42096
+ def events(self) -> list[dict[str, Any]]:
42097
+ rows: list[dict[str, Any]] = []
42098
+ with self.events_path.open("r", encoding="utf-8") as handle:
42099
+ for line in handle:
42100
+ if line.strip():
42101
+ rows.append(json.loads(line))
42102
+ return rows
42103
+
42104
+
42105
+ class ScopeBlindNautilusBridge:
42106
+ def __init__(self) -> None:
42107
+ self.state = BridgeState()
42108
+ self.real = self._load_real_bridge()
42109
+
42110
+ def _load_real_bridge(self) -> Any | None:
42111
+ target = os.environ.get("NAUTILUS_BRIDGE_MODULE")
42112
+ if not target:
42113
+ return None
42114
+ module_name, _, class_name = target.partition(":")
42115
+ if not module_name or not class_name:
42116
+ raise ValueError("NAUTILUS_BRIDGE_MODULE must be module.path:ClassName")
42117
+ module = importlib.import_module(module_name)
42118
+ return getattr(module, class_name)()
42119
+
42120
+ def handle(self, command: dict[str, Any]) -> dict[str, Any]:
42121
+ action = command.get("action")
42122
+ handlers: dict[str, Callable[[dict[str, Any]], dict[str, Any]]] = {
42123
+ "stage_order": self.stage_order,
42124
+ "submit_order": self.submit_order,
42125
+ "modify_order": self.modify_order,
42126
+ "cancel_order": self.cancel_order,
42127
+ "reconcile": self.reconcile,
42128
+ "export_events": self.export_events,
42129
+ }
42130
+ if action not in handlers:
42131
+ return self.error(command, "unknown_action", f"Unsupported action: {action}")
42132
+ try:
42133
+ return handlers[action](command)
42134
+ except Exception as exc:
42135
+ return self.error(command, "bridge_error", str(exc))
42136
+
42137
+ def require(self, command: dict[str, Any], *fields: str) -> None:
42138
+ missing = [field for field in fields if command.get(field) in (None, "")]
42139
+ if missing:
42140
+ raise ValueError(f"missing required field(s): {', '.join(missing)}")
42141
+
42142
+ def require_approved(self, command: dict[str, Any]) -> None:
42143
+ self.require(command, "approval_receipt")
42144
+ if command.get("mandate_passed") is not True:
42145
+ raise ValueError("mandate_passed must be true before live order mutation")
42146
+
42147
+ def stage_order(self, command: dict[str, Any]) -> dict[str, Any]:
42148
+ self.require(command, "client_order_id", "instrument_id", "side", "quantity")
42149
+ order = self.order_projection(command, status="staged")
42150
+ self.state.append_order(order)
42151
+ event = self.state.append_event({
42152
+ "type": "scopeblind.nautilus.order_staged.v1",
42153
+ "client_order_id": order["client_order_id"],
42154
+ "order_digest": sha256_json(order),
42155
+ "disclosure": "position_blind",
42156
+ })
42157
+ return self.ok(command, {"status": "staged", "order": order, "event": event})
42158
+
42159
+ def submit_order(self, command: dict[str, Any]) -> dict[str, Any]:
42160
+ self.require_approved(command)
42161
+ order = self.order_projection(command, status="submitted")
42162
+ if self.real and hasattr(self.real, "submit_order"):
42163
+ external = self.real.submit_order(order)
42164
+ else:
42165
+ external = {"mode": "mock", "external_order_id": f"MOCK-{order['client_order_id']}"}
42166
+ event = self.state.append_event({
42167
+ "type": "scopeblind.nautilus.order_submitted.v1",
42168
+ "client_order_id": order["client_order_id"],
42169
+ "order_digest": sha256_json(order),
42170
+ "external_digest": sha256_json(external),
42171
+ "disclosure": "position_blind",
42172
+ })
42173
+ return self.ok(command, {"status": "submitted", "order": order, "external": external, "event": event})
42174
+
42175
+ def modify_order(self, command: dict[str, Any]) -> dict[str, Any]:
42176
+ self.require_approved(command)
42177
+ self.require(command, "client_order_id")
42178
+ if self.real and hasattr(self.real, "modify_order"):
42179
+ external = self.real.modify_order(command)
42180
+ else:
42181
+ external = {"mode": "mock", "modified": command["client_order_id"]}
42182
+ event = self.state.append_event({
42183
+ "type": "scopeblind.nautilus.order_modified.v1",
42184
+ "client_order_id": command["client_order_id"],
42185
+ "command_digest": sha256_json(command),
42186
+ "external_digest": sha256_json(external),
42187
+ "disclosure": "position_blind",
42188
+ })
42189
+ return self.ok(command, {"status": "modified", "external": external, "event": event})
42190
+
42191
+ def cancel_order(self, command: dict[str, Any]) -> dict[str, Any]:
42192
+ self.require_approved(command)
42193
+ self.require(command, "client_order_id")
42194
+ if self.real and hasattr(self.real, "cancel_order"):
42195
+ external = self.real.cancel_order(command)
42196
+ else:
42197
+ external = {"mode": "mock", "cancelled": command["client_order_id"]}
42198
+ event = self.state.append_event({
42199
+ "type": "scopeblind.nautilus.order_cancelled.v1",
42200
+ "client_order_id": command["client_order_id"],
42201
+ "command_digest": sha256_json(command),
42202
+ "external_digest": sha256_json(external),
42203
+ "disclosure": "position_blind",
42204
+ })
42205
+ return self.ok(command, {"status": "cancelled", "external": external, "event": event})
42206
+
42207
+ def reconcile(self, command: dict[str, Any]) -> dict[str, Any]:
42208
+ self.require(command, "client_order_id")
42209
+ if self.real and hasattr(self.real, "reconcile"):
42210
+ external = self.real.reconcile(command)
42211
+ else:
42212
+ external = {"mode": "mock", "client_order_id": command["client_order_id"], "state": "accepted"}
42213
+ event = self.state.append_event({
42214
+ "type": "scopeblind.nautilus.reconciled.v1",
42215
+ "client_order_id": command["client_order_id"],
42216
+ "external_digest": sha256_json(external),
42217
+ "disclosure": "position_blind",
42218
+ })
42219
+ return self.ok(command, {"status": "reconciled", "external": external, "event": event})
42220
+
42221
+ def export_events(self, command: dict[str, Any]) -> dict[str, Any]:
42222
+ if self.real and hasattr(self.real, "export_events"):
42223
+ external_events = self.real.export_events(command.get("since"))
42224
+ else:
42225
+ external_events = self.state.events()
42226
+ return self.ok(command, {
42227
+ "status": "exported",
42228
+ "event_count": len(external_events),
42229
+ "commitment_root": sha256_json(external_events),
42230
+ "events": external_events,
42231
+ })
42232
+
42233
+ def order_projection(self, command: dict[str, Any], status: str) -> dict[str, Any]:
42234
+ return {
42235
+ "client_order_id": command["client_order_id"],
42236
+ "instrument_id": command["instrument_id"],
42237
+ "side": command["side"],
42238
+ "quantity": command["quantity"],
42239
+ "price": command.get("price"),
42240
+ "time_in_force": command.get("time_in_force", "GTC"),
42241
+ "strategy_id": command.get("strategy_id"),
42242
+ "mandate_digest": command.get("mandate_digest"),
42243
+ "approval_receipt": command.get("approval_receipt"),
42244
+ "status": status,
42245
+ "created_at_ms": now_ms(),
42246
+ }
42247
+
42248
+ def ok(self, command: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]:
42249
+ return {
42250
+ "ok": True,
42251
+ "bridge": "scopeblind.nautilus.external.v1",
42252
+ "mode": "real" if self.real else "mock",
42253
+ "request_digest": sha256_json(command),
42254
+ **result,
42255
+ }
42256
+
42257
+ def error(self, command: dict[str, Any], code: str, message: str) -> dict[str, Any]:
42258
+ return {
42259
+ "ok": False,
42260
+ "bridge": "scopeblind.nautilus.external.v1",
42261
+ "mode": "real" if self.real else "mock",
42262
+ "error": {"code": code, "message": message},
42263
+ "request_digest": sha256_json(command),
42264
+ }
42265
+
42266
+
42267
+ def main() -> int:
42268
+ bridge = ScopeBlindNautilusBridge()
42269
+ for line in sys.stdin:
42270
+ if not line.strip():
42271
+ continue
42272
+ command = json.loads(line)
42273
+ print(canonical_json(bridge.handle(command)), flush=True)
42274
+ return 0
42275
+
42276
+
42277
+ if __name__ == "__main__":
42278
+ raise SystemExit(main())
42279
+ `;
42280
+ var nautilusAdapterReadme = `# NautilusTrader-compatible external bridge
42281
+
42282
+ This connector is intentionally external to NautilusTrader. It lets protect-mcp
42283
+ control and receipt high-risk order actions while a customer-owned Nautilus
42284
+ process remains the trading engine.
42285
+
42286
+ ## Local mock run
42287
+
42288
+ \`\`\`bash
42289
+ python3 .protect-mcp/connectors/nautilus-trader/bridge.py <<'JSONL'
42290
+ {"action":"stage_order","client_order_id":"SB-1","instrument_id":"AAPL.NASDAQ","side":"BUY","quantity":"50","price":"182.40","mandate_digest":"demo"}
42291
+ {"action":"submit_order","client_order_id":"SB-1","instrument_id":"AAPL.NASDAQ","side":"BUY","quantity":"50","price":"182.40","mandate_digest":"demo","mandate_passed":true,"approval_receipt":"receipt-demo"}
42292
+ {"action":"export_events"}
42293
+ JSONL
42294
+ \`\`\`
42295
+
42296
+ ## Real mode
42297
+
42298
+ Set \`NAUTILUS_BRIDGE_MODULE=customer_module:BridgeClass\`. The class can
42299
+ implement \`submit_order\`, \`modify_order\`, \`cancel_order\`, \`reconcile\`,
42300
+ and \`export_events\`. Keep that glue in the customer's repository so Nautilus
42301
+ licensing, credentials, and trading logic stay outside ScopeBlind.
42302
+
42303
+ ## Upstream contribution posture
42304
+
42305
+ The best NautilusTrader contribution is not this bridge or a UI. It is a small,
42306
+ vendor-neutral audit/event sink RFC: a documented way to export normalized order
42307
+ commands, execution reports, fills, cancels, and reconciliation events so
42308
+ external compliance wrappers can prove what happened without mutating the
42309
+ engine.
42310
+ `;
42311
+ var CONNECTOR_PILOTS = [
42312
+ {
42313
+ id: "github",
42314
+ category: "code",
42315
+ name: "GitHub pull-request control",
42316
+ status: "usable-pilot",
42317
+ description: "Controls GitHub REST/MCP calls for issue, PR, branch, and workflow actions.",
42318
+ value: "Useful when agents already have repo access through GitHub MCP, gh, or a GitHub-backed tool server.",
42319
+ env: [
42320
+ { name: "GITHUB_TOKEN", required: true, description: "Fine-grained token scoped to the pilot repo." },
42321
+ { name: "GITHUB_REPOSITORY", required: true, description: "owner/repo target for the pilot." }
42322
+ ],
42323
+ tools: ["github.rest.request", "github.issue.create", "github.pull_request.merge", "github.workflow.dispatch"],
42324
+ actions: [
42325
+ { name: "Read repo metadata", tool: "github.rest.request", risk: "low", mode: "observe", description: "GET-only repository and PR inspection." },
42326
+ { name: "Create issue or comment", tool: "github.issue.create", risk: "medium", mode: "require_approval", description: "External write to the system of record." },
42327
+ { name: "Merge PR / dispatch workflow", tool: "github.pull_request.merge", risk: "high", mode: "require_approval", description: "Code-changing or CI-triggering action." }
42328
+ ],
42329
+ setup: [
42330
+ "Create a fine-grained GitHub token for one repository.",
42331
+ "Set GITHUB_TOKEN and GITHUB_REPOSITORY.",
42332
+ "Run the agent through protect-mcp and review GitHub tool calls in the dashboard."
42333
+ ],
42334
+ config: {
42335
+ type: "scopeblind.connector_pilot.v1",
42336
+ provider: "github",
42337
+ target_env: ["GITHUB_TOKEN", "GITHUB_REPOSITORY"],
42338
+ safe_read_probe: "GET /repos/{GITHUB_REPOSITORY}",
42339
+ controlled_tools: ["github.rest.request", "github.issue.create", "github.pull_request.merge", "github.workflow.dispatch"],
42340
+ approval_required_for: ["POST", "PATCH", "PUT", "DELETE", "merge", "workflow_dispatch"],
42341
+ receipt_fields: ["method", "path", "repo", "actor", "payload_hash", "approval_reason"]
42342
+ },
42343
+ cedar: `${defaultPermit2}
42344
+ // GitHub pilot: reads are observed; writes and merges need exact-action approval.
42345
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42346
+ when { context.tool == "github.pull_request.merge" };
42347
+
42348
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42349
+ when { context.tool == "github.workflow.dispatch" && !context.approved };
42350
+
42351
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42352
+ when { context.tool == "github.issue.create" && !context.approved };
42353
+ `
42354
+ },
42355
+ {
42356
+ id: "email-gmail",
42357
+ category: "communications",
42358
+ name: "Gmail self-send / draft approval",
42359
+ status: "usable-pilot",
42360
+ description: "Uses the existing Gmail OAuth connector path and restricts send mode to email.self for the first production pilot.",
42361
+ value: "Makes external communications reviewable before an agent can send mail.",
42362
+ env: [
42363
+ { name: "GOOGLE_CLIENT_ID", required: true, description: "OAuth client for Gmail." },
42364
+ { name: "GOOGLE_CLIENT_SECRET", required: true, description: "OAuth client secret." },
42365
+ { name: "CONNECTOR_TOKEN_KEY", required: true, description: "AES-GCM key material for sealed connector tokens." }
42366
+ ],
42367
+ tools: ["gmail.draft.create", "gmail.send.email_self", "email.send"],
42368
+ actions: [
42369
+ { name: "Create draft", tool: "gmail.draft.create", risk: "medium", mode: "require_approval", description: "Draft content can leak sensitive information." },
42370
+ { name: "Self-send test", tool: "gmail.send.email_self", risk: "medium", mode: "require_approval", description: "First release allows only sending to the account owner." },
42371
+ { name: "External send", tool: "email.send", risk: "high", mode: "deny", description: "Direct external send stays blocked until a customer-specific allowlist exists." }
42372
+ ],
42373
+ setup: [
42374
+ "Configure Google OAuth redirect /fn/connectors/gmail/callback.",
42375
+ "Connect Gmail through the hosted console or local connector flow.",
42376
+ "Keep send mode to email.self until the customer approves recipient allowlists."
42377
+ ],
42378
+ config: {
42379
+ type: "scopeblind.connector_pilot.v1",
42380
+ provider: "gmail",
42381
+ hosted_functions: ["/fn/connectors/gmail/start", "/fn/connectors/gmail/callback", "/fn/connectors/gmail/send", "/fn/connectors/gmail/status"],
42382
+ first_release_scope: "email.self",
42383
+ denied_until_configured: ["email.send.external", "email.bulk_send"],
42384
+ receipt_fields: ["to_hash", "subject_hash", "body_hash", "approval_reason", "gmail_message_id"]
42385
+ },
42386
+ cedar: `${defaultPermit2}
42387
+ // Email pilot: no direct external send. Draft/self-send require exact approval.
42388
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42389
+ when { context.tool == "email.send" };
42390
+
42391
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42392
+ when { context.tool == "gmail.draft.create" && !context.approved };
42393
+
42394
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42395
+ when { context.tool == "gmail.send.email_self" && !context.approved };
42396
+ `
42397
+ },
42398
+ {
42399
+ id: "filesystem-git",
42400
+ category: "local-computer",
42401
+ name: "Filesystem and Git control",
42402
+ status: "usable-pilot",
42403
+ description: "Controls reads, writes, shell commands, and Git mutation in the local project.",
42404
+ value: "Immediately useful with Claude Code, Codex, Cursor, and any agent that edits files or runs shell commands.",
42405
+ env: [],
42406
+ tools: ["Read", "Write", "Edit", "MultiEdit", "Bash", "git.commit", "git.push"],
42407
+ actions: [
42408
+ { name: "Read files", tool: "Read", risk: "low", mode: "observe", description: "Observe file reads for audit context." },
42409
+ { name: "Write/edit files", tool: "Write", risk: "medium", mode: "require_approval", description: "Require approval for sensitive paths or broad rewrites." },
42410
+ { name: "Git push/reset", tool: "Bash", risk: "high", mode: "require_approval", description: "Commands that publish, reset, or delete require exact-action approval." }
42411
+ ],
42412
+ setup: [
42413
+ "Run protect-mcp init-hooks in the project.",
42414
+ "Install filesystem-safe and Git-safe policy packs.",
42415
+ "Review the dashboard before turning on enforce mode."
42416
+ ],
42417
+ config: {
42418
+ type: "scopeblind.connector_pilot.v1",
42419
+ provider: "filesystem-git",
42420
+ local_only: true,
42421
+ protected_paths: [".env", ".ssh", "keys/", "secrets/", "node_modules/"],
42422
+ dangerous_command_patterns: ["rm -rf", "git push", "git reset --hard", "curl | sh", "chmod 777"],
42423
+ receipt_fields: ["tool", "path_hash", "command_hash", "diff_hash", "approval_reason"]
42424
+ },
42425
+ cedar: `${defaultPermit2}
42426
+ // Filesystem/Git pilot: dangerous shell and protected-path writes need approval.
42427
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42428
+ when { context.tool == "Bash" && context.command_pattern.contains("git reset --hard") && !context.approved };
42429
+
42430
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42431
+ when { context.tool == "Bash" && context.command_pattern.contains("git push") && !context.approved };
42432
+
42433
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42434
+ when { ["Write", "Edit", "MultiEdit"].contains(context.tool) && context.path.contains(".env") && !context.approved };
42435
+ `
42436
+ },
42437
+ {
42438
+ id: "slack-teams",
42439
+ category: "communications",
42440
+ name: "Slack or Teams outbound approval",
42441
+ status: "usable-pilot",
42442
+ description: "Controls messages to Slack channels or Microsoft Teams webhooks.",
42443
+ value: "Makes high-impact internal broadcasts and client channels approval-gated.",
42444
+ env: [
42445
+ { name: "SLACK_BOT_TOKEN", required: false, description: "Slack bot token for chat.postMessage pilots." },
42446
+ { name: "SLACK_CHANNEL_ID", required: false, description: "Default Slack channel for the pilot." },
42447
+ { name: "TEAMS_WEBHOOK_URL", required: false, description: "Teams incoming webhook URL if Teams is preferred." }
42448
+ ],
42449
+ tools: ["slack.chat.postMessage", "slack.files.upload", "teams.webhook.post"],
42450
+ actions: [
42451
+ { name: "Post internal message", tool: "slack.chat.postMessage", risk: "medium", mode: "require_approval", description: "Message text and channel are read back before send." },
42452
+ { name: "Upload file", tool: "slack.files.upload", risk: "high", mode: "require_approval", description: "Files can leak customer data and need explicit approval." },
42453
+ { name: "Teams webhook post", tool: "teams.webhook.post", risk: "medium", mode: "require_approval", description: "Webhook destination and payload hash are receipted." }
42454
+ ],
42455
+ setup: [
42456
+ "Choose Slack or Teams for the first pilot, not both.",
42457
+ "Set the relevant token/webhook environment variables.",
42458
+ "Start with a private test channel and exact-action approval for every send."
42459
+ ],
42460
+ config: {
42461
+ type: "scopeblind.connector_pilot.v1",
42462
+ provider: "slack-or-teams",
42463
+ supported_modes: ["slack.chat.postMessage", "teams.webhook.post"],
42464
+ require_channel_allowlist: true,
42465
+ receipt_fields: ["channel_hash", "message_hash", "file_hash", "approval_reason", "provider_message_id"]
42466
+ },
42467
+ cedar: `${defaultPermit2}
42468
+ // Slack/Teams pilot: all outbound posts and uploads require approval by default.
42469
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42470
+ when { ["slack.chat.postMessage", "slack.files.upload", "teams.webhook.post"].contains(context.tool) && !context.approved };
42471
+ `
42472
+ },
42473
+ {
42474
+ id: "finance-pms",
42475
+ category: "finance",
42476
+ name: "Finance PMS mock-to-real adapter",
42477
+ status: "usable-pilot",
42478
+ description: "Stages orders into a PMS adapter contract, with mock mode locally and real mode through PMS_ADAPTER_URL.",
42479
+ value: "Gives hedge funds the controlled booking path: parse, mandate-check, approve, book, corroborate, receipt.",
42480
+ env: [
42481
+ { name: "PMS_ADAPTER_URL", required: false, description: "Customer-owned adapter endpoint. Omit for local mock mode." },
42482
+ { name: "PMS_ADAPTER_TOKEN", required: false, description: "Bearer token for the customer-owned PMS adapter." }
42483
+ ],
42484
+ tools: ["pms.order.stage", "pms.order.book", "pms.order.cancel", "pms.reconcile"],
42485
+ actions: [
42486
+ { name: "Stage order", tool: "pms.order.stage", risk: "medium", mode: "require_approval", description: "Creates a booking ticket but does not execute." },
42487
+ { name: "Book order", tool: "pms.order.book", risk: "high", mode: "require_approval", description: "Must pass mandate checks and human readback." },
42488
+ { name: "Cancel/order correction", tool: "pms.order.cancel", risk: "high", mode: "require_approval", description: "Mutates book state and requires approval." }
42489
+ ],
42490
+ setup: [
42491
+ "Run local mock mode first with the Legate finance pilot pack.",
42492
+ "Point PMS_ADAPTER_URL at a customer-owned bridge when ready.",
42493
+ "Require mandate checks and exact-action approval before pms.order.book."
42494
+ ],
42495
+ config: {
42496
+ type: "scopeblind.connector_pilot.v1",
42497
+ provider: "finance-pms",
42498
+ mode: "mock-first",
42499
+ adapter_contract: {
42500
+ stage: "POST /orders/stage",
42501
+ book: "POST /orders/book",
42502
+ cancel: "POST /orders/{client_order_id}/cancel",
42503
+ reconcile: "GET /orders/{client_order_id}"
42504
+ },
42505
+ receipt_fields: ["client_order_id", "side", "symbol_hash", "qty", "price", "mandate_digest", "approval_reason", "external_confirmation_hash"]
42506
+ },
42507
+ cedar: `${defaultPermit2}
42508
+ // Finance/PMS pilot: booking actions require mandate pass and exact approval.
42509
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42510
+ when { ["pms.order.stage", "pms.order.book", "pms.order.cancel"].contains(context.tool) && !context.approved };
42511
+
42512
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42513
+ when { context.tool == "pms.order.book" && context.mandate_passed != true };
42514
+ `
42515
+ },
42516
+ {
42517
+ id: "nautilus-trader",
42518
+ category: "finance",
42519
+ name: "NautilusTrader-compatible external bridge",
42520
+ status: "usable-pilot",
42521
+ description: "Controls NautilusTrader-compatible staged orders through an external JSONL bridge, with local mock mode and customer-owned real mode.",
42522
+ value: "Turns Nautilus into a strong Legate demo target: mandate-check, exact approval, external order event, position-blind audit bundle, and later reconciliation.",
42523
+ env: [
42524
+ { name: "NAUTILUS_BRIDGE_MODULE", required: false, description: "Optional customer glue in module.path:ClassName form for real Nautilus submission." },
42525
+ { name: "SCOPEBLIND_NAUTILUS_STATE_DIR", required: false, description: "Optional state directory for local mock events. Defaults to .protect-mcp/nautilus." },
42526
+ { name: "NAUTILUS_TRADER_PROJECT", required: false, description: "Optional path to the customer Nautilus project when running real mode." }
42527
+ ],
42528
+ tools: [
42529
+ "nautilus.order.stage",
42530
+ "nautilus.order.submit",
42531
+ "nautilus.order.modify",
42532
+ "nautilus.order.cancel",
42533
+ "nautilus.strategy.deploy",
42534
+ "nautilus.event.export",
42535
+ "nautilus.reconcile"
42536
+ ],
42537
+ actions: [
42538
+ { name: "Stage order", tool: "nautilus.order.stage", risk: "medium", mode: "require_approval", description: "Creates a position-blind booking intent and event commitment." },
42539
+ { name: "Submit order", tool: "nautilus.order.submit", risk: "high", mode: "require_approval", description: "Requires mandate pass plus exact approval before live order mutation." },
42540
+ { name: "Modify or cancel order", tool: "nautilus.order.modify", risk: "high", mode: "require_approval", description: "Mutates live order state and must carry a fresh approval receipt." },
42541
+ { name: "Deploy strategy", tool: "nautilus.strategy.deploy", risk: "high", mode: "require_approval", description: "Requires signed strategy pack, mandate scope, and operator approval." },
42542
+ { name: "Export event log", tool: "nautilus.event.export", risk: "low", mode: "observe", description: "Exports normalized event commitments for receipt corroboration." }
42543
+ ],
42544
+ setup: [
42545
+ "Run mock mode first: protect-mcp connectors init nautilus-trader --force.",
42546
+ "Pipe stage/submit/reconcile JSONL through .protect-mcp/connectors/nautilus-trader/bridge.py.",
42547
+ "For real mode, set NAUTILUS_BRIDGE_MODULE to customer-owned glue that calls NautilusTrader APIs.",
42548
+ "Open an upstream NautilusTrader RFC for a neutral audit/event sink before proposing any PR."
42549
+ ],
42550
+ config: {
42551
+ type: "scopeblind.connector_pilot.v1",
42552
+ provider: "nautilus-trader-compatible",
42553
+ mode: "external-bridge-mock-first",
42554
+ license_boundary: "No NautilusTrader code is bundled. Real mode calls a customer-owned process/module.",
42555
+ adapter_contract: {
42556
+ protocol: "stdin/stdout JSONL",
42557
+ bridge: ".protect-mcp/connectors/nautilus-trader/bridge.py",
42558
+ real_mode_env: "NAUTILUS_BRIDGE_MODULE=module.path:ClassName",
42559
+ actions: ["stage_order", "submit_order", "modify_order", "cancel_order", "reconcile", "export_events"]
42560
+ },
42561
+ controlled_tools: [
42562
+ "nautilus.order.stage",
42563
+ "nautilus.order.submit",
42564
+ "nautilus.order.modify",
42565
+ "nautilus.order.cancel",
42566
+ "nautilus.strategy.deploy",
42567
+ "nautilus.event.export",
42568
+ "nautilus.reconcile"
42569
+ ],
42570
+ approval_required_for: ["submit_order", "modify_order", "cancel_order", "strategy_deploy"],
42571
+ receipt_fields: [
42572
+ "client_order_id",
42573
+ "instrument_id_hash",
42574
+ "side",
42575
+ "quantity",
42576
+ "price",
42577
+ "mandate_digest",
42578
+ "approval_receipt",
42579
+ "external_event_digest",
42580
+ "commitment_root"
42581
+ ],
42582
+ upstream_rfc: {
42583
+ title: "[RFC] Add a vendor-neutral order/execution audit event sink",
42584
+ non_goals: ["ScopeBlind dependency", "UI dashboard", "AI tooling", "new venue adapter"]
42585
+ }
42586
+ },
42587
+ artifacts: [
42588
+ { path: "nautilus-trader/bridge.py", contents: nautilusBridgePy, executable: true },
42589
+ { path: "nautilus-trader/README.md", contents: nautilusAdapterReadme }
42590
+ ],
42591
+ cedar: `${defaultPermit2}
42592
+ // NautilusTrader-compatible pilot: stage can be observed, but any live mutation requires exact approval.
42593
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42594
+ when { ["nautilus.order.submit", "nautilus.order.modify", "nautilus.order.cancel", "nautilus.strategy.deploy"].contains(context.tool) && !context.approved };
42595
+
42596
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42597
+ when { ["nautilus.order.submit", "nautilus.order.modify", "nautilus.order.cancel"].contains(context.tool) && context.mandate_passed != true };
42598
+
42599
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42600
+ when { context.tool == "nautilus.strategy.deploy" && context.strategy_pack_signed != true };
42601
+ `
42602
+ }
42603
+ ];
42604
+ function connectorPilotIds() {
42605
+ return CONNECTOR_PILOTS.map((pilot) => pilot.id);
42606
+ }
42607
+ function getConnectorPilot(id) {
42608
+ return CONNECTOR_PILOTS.find((pilot) => pilot.id === id);
42609
+ }
42610
+ function connectorDirectory(dir) {
42611
+ return (0, import_node_path6.join)(dir, ".protect-mcp", "connectors");
42612
+ }
42613
+ function writeConnectorPilots(opts) {
42614
+ const directory = connectorDirectory(opts.dir);
42615
+ (0, import_node_fs10.mkdirSync)(directory, { recursive: true });
42616
+ const selected = opts.ids && opts.ids.length > 0 && !opts.ids.includes("all") ? opts.ids.map((id) => {
42617
+ const pilot = getConnectorPilot(id);
42618
+ if (!pilot) throw new Error(`Unknown connector pilot: ${id}`);
42619
+ return pilot;
42620
+ }) : CONNECTOR_PILOTS;
42621
+ const written = [];
42622
+ for (const pilot of selected) {
42623
+ const configPath = (0, import_node_path6.join)(directory, `${pilot.id}.json`);
42624
+ const policyPath = (0, import_node_path6.join)(directory, `${pilot.id}.cedar`);
42625
+ if (!opts.force && ((0, import_node_fs10.existsSync)(configPath) || (0, import_node_fs10.existsSync)(policyPath))) {
42626
+ throw new Error(`Refusing to overwrite ${pilot.id}. Re-run with --force if intentional.`);
42627
+ }
42628
+ (0, import_node_fs10.writeFileSync)(configPath, JSON.stringify({ ...pilot.config, id: pilot.id, name: pilot.name, category: pilot.category, tools: pilot.tools, actions: pilot.actions, setup: pilot.setup }, null, 2) + "\n");
42629
+ (0, import_node_fs10.writeFileSync)(policyPath, pilot.cedar.endsWith("\n") ? pilot.cedar : `${pilot.cedar}
42630
+ `);
42631
+ written.push(configPath, policyPath);
42632
+ for (const artifact of pilot.artifacts || []) {
42633
+ const artifactPath = connectorArtifactPath(directory, artifact.path);
42634
+ (0, import_node_fs10.mkdirSync)((0, import_node_path6.dirname)(artifactPath), { recursive: true });
42635
+ (0, import_node_fs10.writeFileSync)(artifactPath, artifact.contents.endsWith("\n") ? artifact.contents : `${artifact.contents}
42636
+ `);
42637
+ if (artifact.executable) (0, import_node_fs10.chmodSync)(artifactPath, 493);
42638
+ written.push(artifactPath);
42639
+ }
42640
+ }
42641
+ (0, import_node_fs10.writeFileSync)((0, import_node_path6.join)(directory, "README.md"), renderConnectorReadme(selected));
42642
+ written.push((0, import_node_path6.join)(directory, "README.md"));
42643
+ return { written, pilots: selected, directory };
42644
+ }
42645
+ function connectorArtifactPath(directory, relativePath) {
42646
+ const clean2 = (0, import_node_path6.normalize)(relativePath).replace(/^(\.\.(\/|\\|$))+/, "");
42647
+ if (clean2.startsWith("/") || clean2.includes("..")) {
42648
+ throw new Error(`Unsafe connector artifact path: ${relativePath}`);
42649
+ }
42650
+ return (0, import_node_path6.join)(directory, clean2);
42651
+ }
42652
+ function readInstalledConnectorPilots(dir) {
42653
+ const directory = connectorDirectory(dir);
42654
+ if (!(0, import_node_fs10.existsSync)(directory)) return [];
42655
+ return (0, import_node_fs10.readdirSync)(directory).filter((name) => name.endsWith(".json")).map((name) => {
42656
+ const configPath = (0, import_node_path6.join)(directory, name);
42657
+ try {
42658
+ const parsed = JSON.parse((0, import_node_fs10.readFileSync)(configPath, "utf-8"));
42659
+ const id = String(parsed.id || name.replace(/\.json$/, ""));
42660
+ const pilot = getConnectorPilot(id);
42661
+ return {
42662
+ id,
42663
+ name: String(parsed.name || pilot?.name || id),
42664
+ category: String(parsed.category || pilot?.category || "unknown"),
42665
+ status: String(parsed.status || parsed.type || "installed"),
42666
+ config_path: configPath,
42667
+ policy_path: (0, import_node_path6.join)(directory, `${id}.cedar`)
42668
+ };
42669
+ } catch {
42670
+ return null;
42671
+ }
42672
+ }).filter(Boolean);
42673
+ }
42674
+ function connectorDoctor(dir, env = process.env) {
42675
+ const installed = new Set(readInstalledConnectorPilots(dir).map((pilot) => pilot.id));
42676
+ return CONNECTOR_PILOTS.map((pilot) => {
42677
+ const envRows = pilot.env.map((item) => ({
42678
+ name: item.name,
42679
+ required: item.required,
42680
+ present: Boolean(env[item.name]),
42681
+ description: item.description
42682
+ }));
42683
+ const missingRequired = envRows.filter((item) => item.required && !item.present).map((item) => item.name);
42684
+ const optionalPresent = envRows.filter((item) => !item.required && item.present).map((item) => item.name);
42685
+ const optionalProviderReady = pilot.id === "slack-teams" ? Boolean(env.SLACK_BOT_TOKEN || env.TEAMS_WEBHOOK_URL) : pilot.id === "finance-pms" ? Boolean(env.PMS_ADAPTER_URL) : pilot.id === "nautilus-trader" ? Boolean(env.NAUTILUS_BRIDGE_MODULE || env.NAUTILUS_TRADER_PROJECT) : false;
42686
+ const mockModeReady = pilot.id === "finance-pms" || pilot.id === "nautilus-trader";
42687
+ return {
42688
+ id: pilot.id,
42689
+ name: pilot.name,
42690
+ category: pilot.category,
42691
+ installed: installed.has(pilot.id),
42692
+ usable: missingRequired.length === 0 && (pilot.env.some((item) => item.required) || pilot.env.length === 0 || optionalProviderReady || mockModeReady),
42693
+ mode: pilot.id === "finance-pms" && !env.PMS_ADAPTER_URL ? "mock" : pilot.id === "nautilus-trader" && !env.NAUTILUS_BRIDGE_MODULE ? "mock_bridge" : pilot.id === "slack-teams" && !env.SLACK_BOT_TOKEN && !env.TEAMS_WEBHOOK_URL ? "needs_provider_choice" : "configured_or_local",
42694
+ missing_required: missingRequired,
42695
+ optional_present: optionalPresent,
42696
+ tools: pilot.tools,
42697
+ next: missingRequired.length > 0 ? `Set ${missingRequired.join(", ")}` : installed.has(pilot.id) ? "Run through protect-mcp and inspect the dashboard." : `Install with protect-mcp connectors init ${pilot.id}`
42698
+ };
42699
+ });
42700
+ }
42701
+ function renderConnectorReadme(pilots) {
42702
+ return `# protect-mcp connector pilots
42703
+
42704
+ These files make real tool classes visible and controllable without uploading raw prompts or payloads.
42705
+
42706
+ ${pilots.map((pilot) => `## ${pilot.name}
42707
+
42708
+ ${pilot.description}
42709
+
42710
+ Value: ${pilot.value}
42711
+
42712
+ Tools: ${pilot.tools.map((tool) => `\`${tool}\``).join(", ")}
42713
+
42714
+ Setup:
42715
+ ${pilot.setup.map((step) => `- ${step}`).join("\n")}
42716
+ ${pilot.artifacts?.length ? `
42717
+ Generated files:
42718
+ ${pilot.artifacts.map((artifact) => `- \`${artifact.path}\``).join("\n")}
42719
+ ` : ""}`).join("\n")}
42720
+ Next: run \`npx protect-mcp dashboard --open\` and review tool inventory, policy coverage, approvals, and receipts.
42721
+ `;
42722
+ }
42723
+
41324
42724
  // src/rekor-anchor.ts
41325
- var import_node_crypto5 = require("crypto");
42725
+ var import_node_crypto6 = require("crypto");
41326
42726
  var REKOR_API = "https://rekor.sigstore.dev/api/v1";
41327
42727
  async function anchorToRekor(receiptHash, signature, publicKeyPem) {
41328
42728
  const entry = {
@@ -41391,7 +42791,7 @@ async function verifyRekorAnchor(logIndex, expectedHash) {
41391
42791
  }
41392
42792
  function hashReceipt(receipt) {
41393
42793
  const canonical = JSON.stringify(receipt, Object.keys(receipt).sort());
41394
- return (0, import_node_crypto5.createHash)("sha256").update(canonical).digest("hex");
42794
+ return (0, import_node_crypto6.createHash)("sha256").update(canonical).digest("hex");
41395
42795
  }
41396
42796
  function createLogAnchorField(anchor) {
41397
42797
  return {
@@ -41404,7 +42804,7 @@ function createLogAnchorField(anchor) {
41404
42804
  }
41405
42805
 
41406
42806
  // src/selective-disclosure.ts
41407
- var import_node_crypto6 = require("crypto");
42807
+ var import_node_crypto7 = require("crypto");
41408
42808
  function redactFields(receipt, fieldsToRedact) {
41409
42809
  const redacted = JSON.parse(JSON.stringify(receipt));
41410
42810
  const salts = [];
@@ -41420,7 +42820,7 @@ function redactFields(receipt, fieldsToRedact) {
41420
42820
  if (i === parts.length - 1) {
41421
42821
  if (key in current) {
41422
42822
  const originalValue = current[key];
41423
- const salt = (0, import_node_crypto6.randomBytes)(16).toString("hex");
42823
+ const salt = (0, import_node_crypto7.randomBytes)(16).toString("hex");
41424
42824
  const commitment = computeCommitment(salt, originalValue);
41425
42825
  salts.push({ field: fieldPath, salt, originalValue });
41426
42826
  current[key] = `sha256(salt + ${typeof originalValue === "string" ? "..." : JSON.stringify(originalValue).slice(0, 20) + "..."})`;
@@ -41492,11 +42892,11 @@ function createDisclosurePackage(allSalts, fieldsToDisclose) {
41492
42892
  }
41493
42893
  function computeCommitment(salt, value) {
41494
42894
  const serialized = typeof value === "string" ? value : JSON.stringify(value);
41495
- return (0, import_node_crypto6.createHash)("sha256").update(salt + serialized).digest("hex");
42895
+ return (0, import_node_crypto7.createHash)("sha256").update(salt + serialized).digest("hex");
41496
42896
  }
41497
42897
  function hashObject(obj) {
41498
42898
  const canonical = JSON.stringify(obj, Object.keys(obj).sort());
41499
- return (0, import_node_crypto6.createHash)("sha256").update(canonical).digest("hex");
42899
+ return (0, import_node_crypto7.createHash)("sha256").update(canonical).digest("hex");
41500
42900
  }
41501
42901
 
41502
42902
  // src/huggingface-export.ts
@@ -41637,10 +43037,1070 @@ MIT
41637
43037
  }
41638
43038
 
41639
43039
  // src/webauthn-approval.ts
41640
- var import_node_crypto7 = require("crypto");
43040
+ var import_node_crypto8 = require("crypto");
43041
+
43042
+ // node_modules/@noble/curves/esm/abstract/weierstrass.js
43043
+ var divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n4) / den;
43044
+ function _splitEndoScalar(k, basis, n) {
43045
+ const [[a1, b1], [a2, b2]] = basis;
43046
+ const c1 = divNearest(b2 * k, n);
43047
+ const c2 = divNearest(-b1 * k, n);
43048
+ let k1 = k - c1 * a1 - c2 * a2;
43049
+ let k2 = -c1 * b1 - c2 * b2;
43050
+ const k1neg = k1 < _0n6;
43051
+ const k2neg = k2 < _0n6;
43052
+ if (k1neg)
43053
+ k1 = -k1;
43054
+ if (k2neg)
43055
+ k2 = -k2;
43056
+ const MAX_NUM = bitMask(Math.ceil(bitLen(n) / 2)) + _1n6;
43057
+ if (k1 < _0n6 || k1 >= MAX_NUM || k2 < _0n6 || k2 >= MAX_NUM) {
43058
+ throw new Error("splitScalar (endomorphism): failed, k=" + k);
43059
+ }
43060
+ return { k1neg, k1, k2neg, k2 };
43061
+ }
43062
+ function validateSigFormat(format) {
43063
+ if (!["compact", "recovered", "der"].includes(format))
43064
+ throw new Error('Signature format must be "compact", "recovered", or "der"');
43065
+ return format;
43066
+ }
43067
+ function validateSigOpts(opts, def) {
43068
+ const optsn = {};
43069
+ for (let optName of Object.keys(def)) {
43070
+ optsn[optName] = opts[optName] === void 0 ? def[optName] : opts[optName];
43071
+ }
43072
+ _abool2(optsn.lowS, "lowS");
43073
+ _abool2(optsn.prehash, "prehash");
43074
+ if (optsn.format !== void 0)
43075
+ validateSigFormat(optsn.format);
43076
+ return optsn;
43077
+ }
43078
+ var DERErr = class extends Error {
43079
+ constructor(m = "") {
43080
+ super(m);
43081
+ }
43082
+ };
43083
+ var DER = {
43084
+ // asn.1 DER encoding utils
43085
+ Err: DERErr,
43086
+ // Basic building block is TLV (Tag-Length-Value)
43087
+ _tlv: {
43088
+ encode: (tag, data) => {
43089
+ const { Err: E } = DER;
43090
+ if (tag < 0 || tag > 256)
43091
+ throw new E("tlv.encode: wrong tag");
43092
+ if (data.length & 1)
43093
+ throw new E("tlv.encode: unpadded data");
43094
+ const dataLen = data.length / 2;
43095
+ const len = numberToHexUnpadded(dataLen);
43096
+ if (len.length / 2 & 128)
43097
+ throw new E("tlv.encode: long form length too big");
43098
+ const lenLen = dataLen > 127 ? numberToHexUnpadded(len.length / 2 | 128) : "";
43099
+ const t = numberToHexUnpadded(tag);
43100
+ return t + lenLen + len + data;
43101
+ },
43102
+ // v - value, l - left bytes (unparsed)
43103
+ decode(tag, data) {
43104
+ const { Err: E } = DER;
43105
+ let pos = 0;
43106
+ if (tag < 0 || tag > 256)
43107
+ throw new E("tlv.encode: wrong tag");
43108
+ if (data.length < 2 || data[pos++] !== tag)
43109
+ throw new E("tlv.decode: wrong tlv");
43110
+ const first = data[pos++];
43111
+ const isLong = !!(first & 128);
43112
+ let length = 0;
43113
+ if (!isLong)
43114
+ length = first;
43115
+ else {
43116
+ const lenLen = first & 127;
43117
+ if (!lenLen)
43118
+ throw new E("tlv.decode(long): indefinite length not supported");
43119
+ if (lenLen > 4)
43120
+ throw new E("tlv.decode(long): byte length is too big");
43121
+ const lengthBytes = data.subarray(pos, pos + lenLen);
43122
+ if (lengthBytes.length !== lenLen)
43123
+ throw new E("tlv.decode: length bytes not complete");
43124
+ if (lengthBytes[0] === 0)
43125
+ throw new E("tlv.decode(long): zero leftmost byte");
43126
+ for (const b of lengthBytes)
43127
+ length = length << 8 | b;
43128
+ pos += lenLen;
43129
+ if (length < 128)
43130
+ throw new E("tlv.decode(long): not minimal encoding");
43131
+ }
43132
+ const v = data.subarray(pos, pos + length);
43133
+ if (v.length !== length)
43134
+ throw new E("tlv.decode: wrong value length");
43135
+ return { v, l: data.subarray(pos + length) };
43136
+ }
43137
+ },
43138
+ // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
43139
+ // since we always use positive integers here. It must always be empty:
43140
+ // - add zero byte if exists
43141
+ // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
43142
+ _int: {
43143
+ encode(num) {
43144
+ const { Err: E } = DER;
43145
+ if (num < _0n6)
43146
+ throw new E("integer: negative integers are not allowed");
43147
+ let hex = numberToHexUnpadded(num);
43148
+ if (Number.parseInt(hex[0], 16) & 8)
43149
+ hex = "00" + hex;
43150
+ if (hex.length & 1)
43151
+ throw new E("unexpected DER parsing assertion: unpadded hex");
43152
+ return hex;
43153
+ },
43154
+ decode(data) {
43155
+ const { Err: E } = DER;
43156
+ if (data[0] & 128)
43157
+ throw new E("invalid signature integer: negative");
43158
+ if (data[0] === 0 && !(data[1] & 128))
43159
+ throw new E("invalid signature integer: unnecessary leading zero");
43160
+ return bytesToNumberBE(data);
43161
+ }
43162
+ },
43163
+ toSig(hex) {
43164
+ const { Err: E, _int: int, _tlv: tlv } = DER;
43165
+ const data = ensureBytes("signature", hex);
43166
+ const { v: seqBytes, l: seqLeftBytes } = tlv.decode(48, data);
43167
+ if (seqLeftBytes.length)
43168
+ throw new E("invalid signature: left bytes after parsing");
43169
+ const { v: rBytes, l: rLeftBytes } = tlv.decode(2, seqBytes);
43170
+ const { v: sBytes, l: sLeftBytes } = tlv.decode(2, rLeftBytes);
43171
+ if (sLeftBytes.length)
43172
+ throw new E("invalid signature: left bytes after parsing");
43173
+ return { r: int.decode(rBytes), s: int.decode(sBytes) };
43174
+ },
43175
+ hexFromSig(sig) {
43176
+ const { _tlv: tlv, _int: int } = DER;
43177
+ const rs = tlv.encode(2, int.encode(sig.r));
43178
+ const ss = tlv.encode(2, int.encode(sig.s));
43179
+ const seq = rs + ss;
43180
+ return tlv.encode(48, seq);
43181
+ }
43182
+ };
43183
+ var _0n6 = BigInt(0);
43184
+ var _1n6 = BigInt(1);
43185
+ var _2n4 = BigInt(2);
43186
+ var _3n3 = BigInt(3);
43187
+ var _4n2 = BigInt(4);
43188
+ function _normFnElement(Fn2, key) {
43189
+ const { BYTES: expected } = Fn2;
43190
+ let num;
43191
+ if (typeof key === "bigint") {
43192
+ num = key;
43193
+ } else {
43194
+ let bytes = ensureBytes("private key", key);
43195
+ try {
43196
+ num = Fn2.fromBytes(bytes);
43197
+ } catch (error) {
43198
+ throw new Error(`invalid private key: expected ui8a of size ${expected}, got ${typeof key}`);
43199
+ }
43200
+ }
43201
+ if (!Fn2.isValidNot0(num))
43202
+ throw new Error("invalid private key: out of range [1..N-1]");
43203
+ return num;
43204
+ }
43205
+ function weierstrassN(params, extraOpts = {}) {
43206
+ const validated = _createCurveFields("weierstrass", params, extraOpts);
43207
+ const { Fp: Fp2, Fn: Fn2 } = validated;
43208
+ let CURVE = validated.CURVE;
43209
+ const { h: cofactor, n: CURVE_ORDER } = CURVE;
43210
+ _validateObject(extraOpts, {}, {
43211
+ allowInfinityPoint: "boolean",
43212
+ clearCofactor: "function",
43213
+ isTorsionFree: "function",
43214
+ fromBytes: "function",
43215
+ toBytes: "function",
43216
+ endo: "object",
43217
+ wrapPrivateKey: "boolean"
43218
+ });
43219
+ const { endo } = extraOpts;
43220
+ if (endo) {
43221
+ if (!Fp2.is0(CURVE.a) || typeof endo.beta !== "bigint" || !Array.isArray(endo.basises)) {
43222
+ throw new Error('invalid endo: expected "beta": bigint and "basises": array');
43223
+ }
43224
+ }
43225
+ const lengths = getWLengths(Fp2, Fn2);
43226
+ function assertCompressionIsSupported() {
43227
+ if (!Fp2.isOdd)
43228
+ throw new Error("compression is not supported: Field does not have .isOdd()");
43229
+ }
43230
+ function pointToBytes(_c, point, isCompressed) {
43231
+ const { x, y } = point.toAffine();
43232
+ const bx = Fp2.toBytes(x);
43233
+ _abool2(isCompressed, "isCompressed");
43234
+ if (isCompressed) {
43235
+ assertCompressionIsSupported();
43236
+ const hasEvenY = !Fp2.isOdd(y);
43237
+ return concatBytes(pprefix(hasEvenY), bx);
43238
+ } else {
43239
+ return concatBytes(Uint8Array.of(4), bx, Fp2.toBytes(y));
43240
+ }
43241
+ }
43242
+ function pointFromBytes(bytes) {
43243
+ _abytes2(bytes, void 0, "Point");
43244
+ const { publicKey: comp, publicKeyUncompressed: uncomp } = lengths;
43245
+ const length = bytes.length;
43246
+ const head = bytes[0];
43247
+ const tail = bytes.subarray(1);
43248
+ if (length === comp && (head === 2 || head === 3)) {
43249
+ const x = Fp2.fromBytes(tail);
43250
+ if (!Fp2.isValid(x))
43251
+ throw new Error("bad point: is not on curve, wrong x");
43252
+ const y2 = weierstrassEquation(x);
43253
+ let y;
43254
+ try {
43255
+ y = Fp2.sqrt(y2);
43256
+ } catch (sqrtError) {
43257
+ const err = sqrtError instanceof Error ? ": " + sqrtError.message : "";
43258
+ throw new Error("bad point: is not on curve, sqrt error" + err);
43259
+ }
43260
+ assertCompressionIsSupported();
43261
+ const isYOdd = Fp2.isOdd(y);
43262
+ const isHeadOdd = (head & 1) === 1;
43263
+ if (isHeadOdd !== isYOdd)
43264
+ y = Fp2.neg(y);
43265
+ return { x, y };
43266
+ } else if (length === uncomp && head === 4) {
43267
+ const L = Fp2.BYTES;
43268
+ const x = Fp2.fromBytes(tail.subarray(0, L));
43269
+ const y = Fp2.fromBytes(tail.subarray(L, L * 2));
43270
+ if (!isValidXY(x, y))
43271
+ throw new Error("bad point: is not on curve");
43272
+ return { x, y };
43273
+ } else {
43274
+ throw new Error(`bad point: got length ${length}, expected compressed=${comp} or uncompressed=${uncomp}`);
43275
+ }
43276
+ }
43277
+ const encodePoint = extraOpts.toBytes || pointToBytes;
43278
+ const decodePoint = extraOpts.fromBytes || pointFromBytes;
43279
+ function weierstrassEquation(x) {
43280
+ const x2 = Fp2.sqr(x);
43281
+ const x3 = Fp2.mul(x2, x);
43282
+ return Fp2.add(Fp2.add(x3, Fp2.mul(x, CURVE.a)), CURVE.b);
43283
+ }
43284
+ function isValidXY(x, y) {
43285
+ const left = Fp2.sqr(y);
43286
+ const right = weierstrassEquation(x);
43287
+ return Fp2.eql(left, right);
43288
+ }
43289
+ if (!isValidXY(CURVE.Gx, CURVE.Gy))
43290
+ throw new Error("bad curve params: generator point");
43291
+ const _4a3 = Fp2.mul(Fp2.pow(CURVE.a, _3n3), _4n2);
43292
+ const _27b2 = Fp2.mul(Fp2.sqr(CURVE.b), BigInt(27));
43293
+ if (Fp2.is0(Fp2.add(_4a3, _27b2)))
43294
+ throw new Error("bad curve params: a or b");
43295
+ function acoord(title, n, banZero = false) {
43296
+ if (!Fp2.isValid(n) || banZero && Fp2.is0(n))
43297
+ throw new Error(`bad point coordinate ${title}`);
43298
+ return n;
43299
+ }
43300
+ function aprjpoint(other) {
43301
+ if (!(other instanceof Point))
43302
+ throw new Error("ProjectivePoint expected");
43303
+ }
43304
+ function splitEndoScalarN(k) {
43305
+ if (!endo || !endo.basises)
43306
+ throw new Error("no endo");
43307
+ return _splitEndoScalar(k, endo.basises, Fn2.ORDER);
43308
+ }
43309
+ const toAffineMemo = memoized((p, iz) => {
43310
+ const { X, Y, Z } = p;
43311
+ if (Fp2.eql(Z, Fp2.ONE))
43312
+ return { x: X, y: Y };
43313
+ const is0 = p.is0();
43314
+ if (iz == null)
43315
+ iz = is0 ? Fp2.ONE : Fp2.inv(Z);
43316
+ const x = Fp2.mul(X, iz);
43317
+ const y = Fp2.mul(Y, iz);
43318
+ const zz = Fp2.mul(Z, iz);
43319
+ if (is0)
43320
+ return { x: Fp2.ZERO, y: Fp2.ZERO };
43321
+ if (!Fp2.eql(zz, Fp2.ONE))
43322
+ throw new Error("invZ was invalid");
43323
+ return { x, y };
43324
+ });
43325
+ const assertValidMemo = memoized((p) => {
43326
+ if (p.is0()) {
43327
+ if (extraOpts.allowInfinityPoint && !Fp2.is0(p.Y))
43328
+ return;
43329
+ throw new Error("bad point: ZERO");
43330
+ }
43331
+ const { x, y } = p.toAffine();
43332
+ if (!Fp2.isValid(x) || !Fp2.isValid(y))
43333
+ throw new Error("bad point: x or y not field elements");
43334
+ if (!isValidXY(x, y))
43335
+ throw new Error("bad point: equation left != right");
43336
+ if (!p.isTorsionFree())
43337
+ throw new Error("bad point: not in prime-order subgroup");
43338
+ return true;
43339
+ });
43340
+ function finishEndo(endoBeta, k1p, k2p, k1neg, k2neg) {
43341
+ k2p = new Point(Fp2.mul(k2p.X, endoBeta), k2p.Y, k2p.Z);
43342
+ k1p = negateCt(k1neg, k1p);
43343
+ k2p = negateCt(k2neg, k2p);
43344
+ return k1p.add(k2p);
43345
+ }
43346
+ class Point {
43347
+ /** Does NOT validate if the point is valid. Use `.assertValidity()`. */
43348
+ constructor(X, Y, Z) {
43349
+ this.X = acoord("x", X);
43350
+ this.Y = acoord("y", Y, true);
43351
+ this.Z = acoord("z", Z);
43352
+ Object.freeze(this);
43353
+ }
43354
+ static CURVE() {
43355
+ return CURVE;
43356
+ }
43357
+ /** Does NOT validate if the point is valid. Use `.assertValidity()`. */
43358
+ static fromAffine(p) {
43359
+ const { x, y } = p || {};
43360
+ if (!p || !Fp2.isValid(x) || !Fp2.isValid(y))
43361
+ throw new Error("invalid affine point");
43362
+ if (p instanceof Point)
43363
+ throw new Error("projective point not allowed");
43364
+ if (Fp2.is0(x) && Fp2.is0(y))
43365
+ return Point.ZERO;
43366
+ return new Point(x, y, Fp2.ONE);
43367
+ }
43368
+ static fromBytes(bytes) {
43369
+ const P = Point.fromAffine(decodePoint(_abytes2(bytes, void 0, "point")));
43370
+ P.assertValidity();
43371
+ return P;
43372
+ }
43373
+ static fromHex(hex) {
43374
+ return Point.fromBytes(ensureBytes("pointHex", hex));
43375
+ }
43376
+ get x() {
43377
+ return this.toAffine().x;
43378
+ }
43379
+ get y() {
43380
+ return this.toAffine().y;
43381
+ }
43382
+ /**
43383
+ *
43384
+ * @param windowSize
43385
+ * @param isLazy true will defer table computation until the first multiplication
43386
+ * @returns
43387
+ */
43388
+ precompute(windowSize = 8, isLazy = true) {
43389
+ wnaf.createCache(this, windowSize);
43390
+ if (!isLazy)
43391
+ this.multiply(_3n3);
43392
+ return this;
43393
+ }
43394
+ // TODO: return `this`
43395
+ /** A point on curve is valid if it conforms to equation. */
43396
+ assertValidity() {
43397
+ assertValidMemo(this);
43398
+ }
43399
+ hasEvenY() {
43400
+ const { y } = this.toAffine();
43401
+ if (!Fp2.isOdd)
43402
+ throw new Error("Field doesn't support isOdd");
43403
+ return !Fp2.isOdd(y);
43404
+ }
43405
+ /** Compare one point to another. */
43406
+ equals(other) {
43407
+ aprjpoint(other);
43408
+ const { X: X1, Y: Y1, Z: Z1 } = this;
43409
+ const { X: X2, Y: Y2, Z: Z2 } = other;
43410
+ const U1 = Fp2.eql(Fp2.mul(X1, Z2), Fp2.mul(X2, Z1));
43411
+ const U2 = Fp2.eql(Fp2.mul(Y1, Z2), Fp2.mul(Y2, Z1));
43412
+ return U1 && U2;
43413
+ }
43414
+ /** Flips point to one corresponding to (x, -y) in Affine coordinates. */
43415
+ negate() {
43416
+ return new Point(this.X, Fp2.neg(this.Y), this.Z);
43417
+ }
43418
+ // Renes-Costello-Batina exception-free doubling formula.
43419
+ // There is 30% faster Jacobian formula, but it is not complete.
43420
+ // https://eprint.iacr.org/2015/1060, algorithm 3
43421
+ // Cost: 8M + 3S + 3*a + 2*b3 + 15add.
43422
+ double() {
43423
+ const { a, b } = CURVE;
43424
+ const b3 = Fp2.mul(b, _3n3);
43425
+ const { X: X1, Y: Y1, Z: Z1 } = this;
43426
+ let X3 = Fp2.ZERO, Y3 = Fp2.ZERO, Z3 = Fp2.ZERO;
43427
+ let t0 = Fp2.mul(X1, X1);
43428
+ let t1 = Fp2.mul(Y1, Y1);
43429
+ let t2 = Fp2.mul(Z1, Z1);
43430
+ let t3 = Fp2.mul(X1, Y1);
43431
+ t3 = Fp2.add(t3, t3);
43432
+ Z3 = Fp2.mul(X1, Z1);
43433
+ Z3 = Fp2.add(Z3, Z3);
43434
+ X3 = Fp2.mul(a, Z3);
43435
+ Y3 = Fp2.mul(b3, t2);
43436
+ Y3 = Fp2.add(X3, Y3);
43437
+ X3 = Fp2.sub(t1, Y3);
43438
+ Y3 = Fp2.add(t1, Y3);
43439
+ Y3 = Fp2.mul(X3, Y3);
43440
+ X3 = Fp2.mul(t3, X3);
43441
+ Z3 = Fp2.mul(b3, Z3);
43442
+ t2 = Fp2.mul(a, t2);
43443
+ t3 = Fp2.sub(t0, t2);
43444
+ t3 = Fp2.mul(a, t3);
43445
+ t3 = Fp2.add(t3, Z3);
43446
+ Z3 = Fp2.add(t0, t0);
43447
+ t0 = Fp2.add(Z3, t0);
43448
+ t0 = Fp2.add(t0, t2);
43449
+ t0 = Fp2.mul(t0, t3);
43450
+ Y3 = Fp2.add(Y3, t0);
43451
+ t2 = Fp2.mul(Y1, Z1);
43452
+ t2 = Fp2.add(t2, t2);
43453
+ t0 = Fp2.mul(t2, t3);
43454
+ X3 = Fp2.sub(X3, t0);
43455
+ Z3 = Fp2.mul(t2, t1);
43456
+ Z3 = Fp2.add(Z3, Z3);
43457
+ Z3 = Fp2.add(Z3, Z3);
43458
+ return new Point(X3, Y3, Z3);
43459
+ }
43460
+ // Renes-Costello-Batina exception-free addition formula.
43461
+ // There is 30% faster Jacobian formula, but it is not complete.
43462
+ // https://eprint.iacr.org/2015/1060, algorithm 1
43463
+ // Cost: 12M + 0S + 3*a + 3*b3 + 23add.
43464
+ add(other) {
43465
+ aprjpoint(other);
43466
+ const { X: X1, Y: Y1, Z: Z1 } = this;
43467
+ const { X: X2, Y: Y2, Z: Z2 } = other;
43468
+ let X3 = Fp2.ZERO, Y3 = Fp2.ZERO, Z3 = Fp2.ZERO;
43469
+ const a = CURVE.a;
43470
+ const b3 = Fp2.mul(CURVE.b, _3n3);
43471
+ let t0 = Fp2.mul(X1, X2);
43472
+ let t1 = Fp2.mul(Y1, Y2);
43473
+ let t2 = Fp2.mul(Z1, Z2);
43474
+ let t3 = Fp2.add(X1, Y1);
43475
+ let t4 = Fp2.add(X2, Y2);
43476
+ t3 = Fp2.mul(t3, t4);
43477
+ t4 = Fp2.add(t0, t1);
43478
+ t3 = Fp2.sub(t3, t4);
43479
+ t4 = Fp2.add(X1, Z1);
43480
+ let t5 = Fp2.add(X2, Z2);
43481
+ t4 = Fp2.mul(t4, t5);
43482
+ t5 = Fp2.add(t0, t2);
43483
+ t4 = Fp2.sub(t4, t5);
43484
+ t5 = Fp2.add(Y1, Z1);
43485
+ X3 = Fp2.add(Y2, Z2);
43486
+ t5 = Fp2.mul(t5, X3);
43487
+ X3 = Fp2.add(t1, t2);
43488
+ t5 = Fp2.sub(t5, X3);
43489
+ Z3 = Fp2.mul(a, t4);
43490
+ X3 = Fp2.mul(b3, t2);
43491
+ Z3 = Fp2.add(X3, Z3);
43492
+ X3 = Fp2.sub(t1, Z3);
43493
+ Z3 = Fp2.add(t1, Z3);
43494
+ Y3 = Fp2.mul(X3, Z3);
43495
+ t1 = Fp2.add(t0, t0);
43496
+ t1 = Fp2.add(t1, t0);
43497
+ t2 = Fp2.mul(a, t2);
43498
+ t4 = Fp2.mul(b3, t4);
43499
+ t1 = Fp2.add(t1, t2);
43500
+ t2 = Fp2.sub(t0, t2);
43501
+ t2 = Fp2.mul(a, t2);
43502
+ t4 = Fp2.add(t4, t2);
43503
+ t0 = Fp2.mul(t1, t4);
43504
+ Y3 = Fp2.add(Y3, t0);
43505
+ t0 = Fp2.mul(t5, t4);
43506
+ X3 = Fp2.mul(t3, X3);
43507
+ X3 = Fp2.sub(X3, t0);
43508
+ t0 = Fp2.mul(t3, t1);
43509
+ Z3 = Fp2.mul(t5, Z3);
43510
+ Z3 = Fp2.add(Z3, t0);
43511
+ return new Point(X3, Y3, Z3);
43512
+ }
43513
+ subtract(other) {
43514
+ return this.add(other.negate());
43515
+ }
43516
+ is0() {
43517
+ return this.equals(Point.ZERO);
43518
+ }
43519
+ /**
43520
+ * Constant time multiplication.
43521
+ * Uses wNAF method. Windowed method may be 10% faster,
43522
+ * but takes 2x longer to generate and consumes 2x memory.
43523
+ * Uses precomputes when available.
43524
+ * Uses endomorphism for Koblitz curves.
43525
+ * @param scalar by which the point would be multiplied
43526
+ * @returns New point
43527
+ */
43528
+ multiply(scalar) {
43529
+ const { endo: endo2 } = extraOpts;
43530
+ if (!Fn2.isValidNot0(scalar))
43531
+ throw new Error("invalid scalar: out of range");
43532
+ let point, fake;
43533
+ const mul = (n) => wnaf.cached(this, n, (p) => normalizeZ(Point, p));
43534
+ if (endo2) {
43535
+ const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar);
43536
+ const { p: k1p, f: k1f } = mul(k1);
43537
+ const { p: k2p, f: k2f } = mul(k2);
43538
+ fake = k1f.add(k2f);
43539
+ point = finishEndo(endo2.beta, k1p, k2p, k1neg, k2neg);
43540
+ } else {
43541
+ const { p, f } = mul(scalar);
43542
+ point = p;
43543
+ fake = f;
43544
+ }
43545
+ return normalizeZ(Point, [point, fake])[0];
43546
+ }
43547
+ /**
43548
+ * Non-constant-time multiplication. Uses double-and-add algorithm.
43549
+ * It's faster, but should only be used when you don't care about
43550
+ * an exposed secret key e.g. sig verification, which works over *public* keys.
43551
+ */
43552
+ multiplyUnsafe(sc) {
43553
+ const { endo: endo2 } = extraOpts;
43554
+ const p = this;
43555
+ if (!Fn2.isValid(sc))
43556
+ throw new Error("invalid scalar: out of range");
43557
+ if (sc === _0n6 || p.is0())
43558
+ return Point.ZERO;
43559
+ if (sc === _1n6)
43560
+ return p;
43561
+ if (wnaf.hasCache(this))
43562
+ return this.multiply(sc);
43563
+ if (endo2) {
43564
+ const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(sc);
43565
+ const { p1, p2 } = mulEndoUnsafe(Point, p, k1, k2);
43566
+ return finishEndo(endo2.beta, p1, p2, k1neg, k2neg);
43567
+ } else {
43568
+ return wnaf.unsafe(p, sc);
43569
+ }
43570
+ }
43571
+ multiplyAndAddUnsafe(Q, a, b) {
43572
+ const sum = this.multiplyUnsafe(a).add(Q.multiplyUnsafe(b));
43573
+ return sum.is0() ? void 0 : sum;
43574
+ }
43575
+ /**
43576
+ * Converts Projective point to affine (x, y) coordinates.
43577
+ * @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch
43578
+ */
43579
+ toAffine(invertedZ) {
43580
+ return toAffineMemo(this, invertedZ);
43581
+ }
43582
+ /**
43583
+ * Checks whether Point is free of torsion elements (is in prime subgroup).
43584
+ * Always torsion-free for cofactor=1 curves.
43585
+ */
43586
+ isTorsionFree() {
43587
+ const { isTorsionFree } = extraOpts;
43588
+ if (cofactor === _1n6)
43589
+ return true;
43590
+ if (isTorsionFree)
43591
+ return isTorsionFree(Point, this);
43592
+ return wnaf.unsafe(this, CURVE_ORDER).is0();
43593
+ }
43594
+ clearCofactor() {
43595
+ const { clearCofactor } = extraOpts;
43596
+ if (cofactor === _1n6)
43597
+ return this;
43598
+ if (clearCofactor)
43599
+ return clearCofactor(Point, this);
43600
+ return this.multiplyUnsafe(cofactor);
43601
+ }
43602
+ isSmallOrder() {
43603
+ return this.multiplyUnsafe(cofactor).is0();
43604
+ }
43605
+ toBytes(isCompressed = true) {
43606
+ _abool2(isCompressed, "isCompressed");
43607
+ this.assertValidity();
43608
+ return encodePoint(Point, this, isCompressed);
43609
+ }
43610
+ toHex(isCompressed = true) {
43611
+ return bytesToHex(this.toBytes(isCompressed));
43612
+ }
43613
+ toString() {
43614
+ return `<Point ${this.is0() ? "ZERO" : this.toHex()}>`;
43615
+ }
43616
+ // TODO: remove
43617
+ get px() {
43618
+ return this.X;
43619
+ }
43620
+ get py() {
43621
+ return this.X;
43622
+ }
43623
+ get pz() {
43624
+ return this.Z;
43625
+ }
43626
+ toRawBytes(isCompressed = true) {
43627
+ return this.toBytes(isCompressed);
43628
+ }
43629
+ _setWindowSize(windowSize) {
43630
+ this.precompute(windowSize);
43631
+ }
43632
+ static normalizeZ(points) {
43633
+ return normalizeZ(Point, points);
43634
+ }
43635
+ static msm(points, scalars) {
43636
+ return pippenger(Point, Fn2, points, scalars);
43637
+ }
43638
+ static fromPrivateKey(privateKey) {
43639
+ return Point.BASE.multiply(_normFnElement(Fn2, privateKey));
43640
+ }
43641
+ }
43642
+ Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp2.ONE);
43643
+ Point.ZERO = new Point(Fp2.ZERO, Fp2.ONE, Fp2.ZERO);
43644
+ Point.Fp = Fp2;
43645
+ Point.Fn = Fn2;
43646
+ const bits = Fn2.BITS;
43647
+ const wnaf = new wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits);
43648
+ Point.BASE.precompute(8);
43649
+ return Point;
43650
+ }
43651
+ function pprefix(hasEvenY) {
43652
+ return Uint8Array.of(hasEvenY ? 2 : 3);
43653
+ }
43654
+ function getWLengths(Fp2, Fn2) {
43655
+ return {
43656
+ secretKey: Fn2.BYTES,
43657
+ publicKey: 1 + Fp2.BYTES,
43658
+ publicKeyUncompressed: 1 + 2 * Fp2.BYTES,
43659
+ publicKeyHasPrefix: true,
43660
+ signature: 2 * Fn2.BYTES
43661
+ };
43662
+ }
43663
+ function ecdh(Point, ecdhOpts = {}) {
43664
+ const { Fn: Fn2 } = Point;
43665
+ const randomBytes_ = ecdhOpts.randomBytes || randomBytes2;
43666
+ const lengths = Object.assign(getWLengths(Point.Fp, Fn2), { seed: getMinHashLength(Fn2.ORDER) });
43667
+ function isValidSecretKey(secretKey) {
43668
+ try {
43669
+ return !!_normFnElement(Fn2, secretKey);
43670
+ } catch (error) {
43671
+ return false;
43672
+ }
43673
+ }
43674
+ function isValidPublicKey(publicKey, isCompressed) {
43675
+ const { publicKey: comp, publicKeyUncompressed } = lengths;
43676
+ try {
43677
+ const l = publicKey.length;
43678
+ if (isCompressed === true && l !== comp)
43679
+ return false;
43680
+ if (isCompressed === false && l !== publicKeyUncompressed)
43681
+ return false;
43682
+ return !!Point.fromBytes(publicKey);
43683
+ } catch (error) {
43684
+ return false;
43685
+ }
43686
+ }
43687
+ function randomSecretKey(seed = randomBytes_(lengths.seed)) {
43688
+ return mapHashToField(_abytes2(seed, lengths.seed, "seed"), Fn2.ORDER);
43689
+ }
43690
+ function getPublicKey(secretKey, isCompressed = true) {
43691
+ return Point.BASE.multiply(_normFnElement(Fn2, secretKey)).toBytes(isCompressed);
43692
+ }
43693
+ function keygen(seed) {
43694
+ const secretKey = randomSecretKey(seed);
43695
+ return { secretKey, publicKey: getPublicKey(secretKey) };
43696
+ }
43697
+ function isProbPub(item) {
43698
+ if (typeof item === "bigint")
43699
+ return false;
43700
+ if (item instanceof Point)
43701
+ return true;
43702
+ const { secretKey, publicKey, publicKeyUncompressed } = lengths;
43703
+ if (Fn2.allowedLengths || secretKey === publicKey)
43704
+ return void 0;
43705
+ const l = ensureBytes("key", item).length;
43706
+ return l === publicKey || l === publicKeyUncompressed;
43707
+ }
43708
+ function getSharedSecret(secretKeyA, publicKeyB, isCompressed = true) {
43709
+ if (isProbPub(secretKeyA) === true)
43710
+ throw new Error("first arg must be private key");
43711
+ if (isProbPub(publicKeyB) === false)
43712
+ throw new Error("second arg must be public key");
43713
+ const s = _normFnElement(Fn2, secretKeyA);
43714
+ const b = Point.fromHex(publicKeyB);
43715
+ return b.multiply(s).toBytes(isCompressed);
43716
+ }
43717
+ const utils = {
43718
+ isValidSecretKey,
43719
+ isValidPublicKey,
43720
+ randomSecretKey,
43721
+ // TODO: remove
43722
+ isValidPrivateKey: isValidSecretKey,
43723
+ randomPrivateKey: randomSecretKey,
43724
+ normPrivateKeyToScalar: (key) => _normFnElement(Fn2, key),
43725
+ precompute(windowSize = 8, point = Point.BASE) {
43726
+ return point.precompute(windowSize, false);
43727
+ }
43728
+ };
43729
+ return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths });
43730
+ }
43731
+ function ecdsa(Point, hash, ecdsaOpts = {}) {
43732
+ ahash(hash);
43733
+ _validateObject(ecdsaOpts, {}, {
43734
+ hmac: "function",
43735
+ lowS: "boolean",
43736
+ randomBytes: "function",
43737
+ bits2int: "function",
43738
+ bits2int_modN: "function"
43739
+ });
43740
+ const randomBytes6 = ecdsaOpts.randomBytes || randomBytes2;
43741
+ const hmac2 = ecdsaOpts.hmac || ((key, ...msgs) => hmac(hash, key, concatBytes(...msgs)));
43742
+ const { Fp: Fp2, Fn: Fn2 } = Point;
43743
+ const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn2;
43744
+ const { keygen, getPublicKey, getSharedSecret, utils, lengths } = ecdh(Point, ecdsaOpts);
43745
+ const defaultSigOpts = {
43746
+ prehash: false,
43747
+ lowS: typeof ecdsaOpts.lowS === "boolean" ? ecdsaOpts.lowS : false,
43748
+ format: void 0,
43749
+ //'compact' as ECDSASigFormat,
43750
+ extraEntropy: false
43751
+ };
43752
+ const defaultSigOpts_format = "compact";
43753
+ function isBiggerThanHalfOrder(number) {
43754
+ const HALF = CURVE_ORDER >> _1n6;
43755
+ return number > HALF;
43756
+ }
43757
+ function validateRS(title, num) {
43758
+ if (!Fn2.isValidNot0(num))
43759
+ throw new Error(`invalid signature ${title}: out of range 1..Point.Fn.ORDER`);
43760
+ return num;
43761
+ }
43762
+ function validateSigLength(bytes, format) {
43763
+ validateSigFormat(format);
43764
+ const size = lengths.signature;
43765
+ const sizer = format === "compact" ? size : format === "recovered" ? size + 1 : void 0;
43766
+ return _abytes2(bytes, sizer, `${format} signature`);
43767
+ }
43768
+ class Signature {
43769
+ constructor(r, s, recovery) {
43770
+ this.r = validateRS("r", r);
43771
+ this.s = validateRS("s", s);
43772
+ if (recovery != null)
43773
+ this.recovery = recovery;
43774
+ Object.freeze(this);
43775
+ }
43776
+ static fromBytes(bytes, format = defaultSigOpts_format) {
43777
+ validateSigLength(bytes, format);
43778
+ let recid;
43779
+ if (format === "der") {
43780
+ const { r: r2, s: s2 } = DER.toSig(_abytes2(bytes));
43781
+ return new Signature(r2, s2);
43782
+ }
43783
+ if (format === "recovered") {
43784
+ recid = bytes[0];
43785
+ format = "compact";
43786
+ bytes = bytes.subarray(1);
43787
+ }
43788
+ const L = Fn2.BYTES;
43789
+ const r = bytes.subarray(0, L);
43790
+ const s = bytes.subarray(L, L * 2);
43791
+ return new Signature(Fn2.fromBytes(r), Fn2.fromBytes(s), recid);
43792
+ }
43793
+ static fromHex(hex, format) {
43794
+ return this.fromBytes(hexToBytes(hex), format);
43795
+ }
43796
+ addRecoveryBit(recovery) {
43797
+ return new Signature(this.r, this.s, recovery);
43798
+ }
43799
+ recoverPublicKey(messageHash) {
43800
+ const FIELD_ORDER = Fp2.ORDER;
43801
+ const { r, s, recovery: rec } = this;
43802
+ if (rec == null || ![0, 1, 2, 3].includes(rec))
43803
+ throw new Error("recovery id invalid");
43804
+ const hasCofactor = CURVE_ORDER * _2n4 < FIELD_ORDER;
43805
+ if (hasCofactor && rec > 1)
43806
+ throw new Error("recovery id is ambiguous for h>1 curve");
43807
+ const radj = rec === 2 || rec === 3 ? r + CURVE_ORDER : r;
43808
+ if (!Fp2.isValid(radj))
43809
+ throw new Error("recovery id 2 or 3 invalid");
43810
+ const x = Fp2.toBytes(radj);
43811
+ const R = Point.fromBytes(concatBytes(pprefix((rec & 1) === 0), x));
43812
+ const ir = Fn2.inv(radj);
43813
+ const h = bits2int_modN(ensureBytes("msgHash", messageHash));
43814
+ const u1 = Fn2.create(-h * ir);
43815
+ const u2 = Fn2.create(s * ir);
43816
+ const Q = Point.BASE.multiplyUnsafe(u1).add(R.multiplyUnsafe(u2));
43817
+ if (Q.is0())
43818
+ throw new Error("point at infinify");
43819
+ Q.assertValidity();
43820
+ return Q;
43821
+ }
43822
+ // Signatures should be low-s, to prevent malleability.
43823
+ hasHighS() {
43824
+ return isBiggerThanHalfOrder(this.s);
43825
+ }
43826
+ toBytes(format = defaultSigOpts_format) {
43827
+ validateSigFormat(format);
43828
+ if (format === "der")
43829
+ return hexToBytes(DER.hexFromSig(this));
43830
+ const r = Fn2.toBytes(this.r);
43831
+ const s = Fn2.toBytes(this.s);
43832
+ if (format === "recovered") {
43833
+ if (this.recovery == null)
43834
+ throw new Error("recovery bit must be present");
43835
+ return concatBytes(Uint8Array.of(this.recovery), r, s);
43836
+ }
43837
+ return concatBytes(r, s);
43838
+ }
43839
+ toHex(format) {
43840
+ return bytesToHex(this.toBytes(format));
43841
+ }
43842
+ // TODO: remove
43843
+ assertValidity() {
43844
+ }
43845
+ static fromCompact(hex) {
43846
+ return Signature.fromBytes(ensureBytes("sig", hex), "compact");
43847
+ }
43848
+ static fromDER(hex) {
43849
+ return Signature.fromBytes(ensureBytes("sig", hex), "der");
43850
+ }
43851
+ normalizeS() {
43852
+ return this.hasHighS() ? new Signature(this.r, Fn2.neg(this.s), this.recovery) : this;
43853
+ }
43854
+ toDERRawBytes() {
43855
+ return this.toBytes("der");
43856
+ }
43857
+ toDERHex() {
43858
+ return bytesToHex(this.toBytes("der"));
43859
+ }
43860
+ toCompactRawBytes() {
43861
+ return this.toBytes("compact");
43862
+ }
43863
+ toCompactHex() {
43864
+ return bytesToHex(this.toBytes("compact"));
43865
+ }
43866
+ }
43867
+ const bits2int = ecdsaOpts.bits2int || function bits2int_def(bytes) {
43868
+ if (bytes.length > 8192)
43869
+ throw new Error("input is too large");
43870
+ const num = bytesToNumberBE(bytes);
43871
+ const delta = bytes.length * 8 - fnBits;
43872
+ return delta > 0 ? num >> BigInt(delta) : num;
43873
+ };
43874
+ const bits2int_modN = ecdsaOpts.bits2int_modN || function bits2int_modN_def(bytes) {
43875
+ return Fn2.create(bits2int(bytes));
43876
+ };
43877
+ const ORDER_MASK = bitMask(fnBits);
43878
+ function int2octets(num) {
43879
+ aInRange("num < 2^" + fnBits, num, _0n6, ORDER_MASK);
43880
+ return Fn2.toBytes(num);
43881
+ }
43882
+ function validateMsgAndHash(message, prehash) {
43883
+ _abytes2(message, void 0, "message");
43884
+ return prehash ? _abytes2(hash(message), void 0, "prehashed message") : message;
43885
+ }
43886
+ function prepSig(message, privateKey, opts) {
43887
+ if (["recovered", "canonical"].some((k) => k in opts))
43888
+ throw new Error("sign() legacy options not supported");
43889
+ const { lowS, prehash, extraEntropy } = validateSigOpts(opts, defaultSigOpts);
43890
+ message = validateMsgAndHash(message, prehash);
43891
+ const h1int = bits2int_modN(message);
43892
+ const d = _normFnElement(Fn2, privateKey);
43893
+ const seedArgs = [int2octets(d), int2octets(h1int)];
43894
+ if (extraEntropy != null && extraEntropy !== false) {
43895
+ const e = extraEntropy === true ? randomBytes6(lengths.secretKey) : extraEntropy;
43896
+ seedArgs.push(ensureBytes("extraEntropy", e));
43897
+ }
43898
+ const seed = concatBytes(...seedArgs);
43899
+ const m = h1int;
43900
+ function k2sig(kBytes) {
43901
+ const k = bits2int(kBytes);
43902
+ if (!Fn2.isValidNot0(k))
43903
+ return;
43904
+ const ik = Fn2.inv(k);
43905
+ const q = Point.BASE.multiply(k).toAffine();
43906
+ const r = Fn2.create(q.x);
43907
+ if (r === _0n6)
43908
+ return;
43909
+ const s = Fn2.create(ik * Fn2.create(m + r * d));
43910
+ if (s === _0n6)
43911
+ return;
43912
+ let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n6);
43913
+ let normS = s;
43914
+ if (lowS && isBiggerThanHalfOrder(s)) {
43915
+ normS = Fn2.neg(s);
43916
+ recovery ^= 1;
43917
+ }
43918
+ return new Signature(r, normS, recovery);
43919
+ }
43920
+ return { seed, k2sig };
43921
+ }
43922
+ function sign(message, secretKey, opts = {}) {
43923
+ message = ensureBytes("message", message);
43924
+ const { seed, k2sig } = prepSig(message, secretKey, opts);
43925
+ const drbg = createHmacDrbg(hash.outputLen, Fn2.BYTES, hmac2);
43926
+ const sig = drbg(seed, k2sig);
43927
+ return sig;
43928
+ }
43929
+ function tryParsingSig(sg) {
43930
+ let sig = void 0;
43931
+ const isHex = typeof sg === "string" || isBytes(sg);
43932
+ const isObj = !isHex && sg !== null && typeof sg === "object" && typeof sg.r === "bigint" && typeof sg.s === "bigint";
43933
+ if (!isHex && !isObj)
43934
+ throw new Error("invalid signature, expected Uint8Array, hex string or Signature instance");
43935
+ if (isObj) {
43936
+ sig = new Signature(sg.r, sg.s);
43937
+ } else if (isHex) {
43938
+ try {
43939
+ sig = Signature.fromBytes(ensureBytes("sig", sg), "der");
43940
+ } catch (derError) {
43941
+ if (!(derError instanceof DER.Err))
43942
+ throw derError;
43943
+ }
43944
+ if (!sig) {
43945
+ try {
43946
+ sig = Signature.fromBytes(ensureBytes("sig", sg), "compact");
43947
+ } catch (error) {
43948
+ return false;
43949
+ }
43950
+ }
43951
+ }
43952
+ if (!sig)
43953
+ return false;
43954
+ return sig;
43955
+ }
43956
+ function verify(signature, message, publicKey, opts = {}) {
43957
+ const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts);
43958
+ publicKey = ensureBytes("publicKey", publicKey);
43959
+ message = validateMsgAndHash(ensureBytes("message", message), prehash);
43960
+ if ("strict" in opts)
43961
+ throw new Error("options.strict was renamed to lowS");
43962
+ const sig = format === void 0 ? tryParsingSig(signature) : Signature.fromBytes(ensureBytes("sig", signature), format);
43963
+ if (sig === false)
43964
+ return false;
43965
+ try {
43966
+ const P = Point.fromBytes(publicKey);
43967
+ if (lowS && sig.hasHighS())
43968
+ return false;
43969
+ const { r, s } = sig;
43970
+ const h = bits2int_modN(message);
43971
+ const is = Fn2.inv(s);
43972
+ const u1 = Fn2.create(h * is);
43973
+ const u2 = Fn2.create(r * is);
43974
+ const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2));
43975
+ if (R.is0())
43976
+ return false;
43977
+ const v = Fn2.create(R.x);
43978
+ return v === r;
43979
+ } catch (e) {
43980
+ return false;
43981
+ }
43982
+ }
43983
+ function recoverPublicKey(signature, message, opts = {}) {
43984
+ const { prehash } = validateSigOpts(opts, defaultSigOpts);
43985
+ message = validateMsgAndHash(message, prehash);
43986
+ return Signature.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
43987
+ }
43988
+ return Object.freeze({
43989
+ keygen,
43990
+ getPublicKey,
43991
+ getSharedSecret,
43992
+ utils,
43993
+ lengths,
43994
+ Point,
43995
+ sign,
43996
+ verify,
43997
+ recoverPublicKey,
43998
+ Signature,
43999
+ hash
44000
+ });
44001
+ }
44002
+ function _weierstrass_legacy_opts_to_new(c) {
44003
+ const CURVE = {
44004
+ a: c.a,
44005
+ b: c.b,
44006
+ p: c.Fp.ORDER,
44007
+ n: c.n,
44008
+ h: c.h,
44009
+ Gx: c.Gx,
44010
+ Gy: c.Gy
44011
+ };
44012
+ const Fp2 = c.Fp;
44013
+ let allowedLengths = c.allowedPrivateKeyLengths ? Array.from(new Set(c.allowedPrivateKeyLengths.map((l) => Math.ceil(l / 2)))) : void 0;
44014
+ const Fn2 = Field(CURVE.n, {
44015
+ BITS: c.nBitLength,
44016
+ allowedLengths,
44017
+ modFromBytes: c.wrapPrivateKey
44018
+ });
44019
+ const curveOpts = {
44020
+ Fp: Fp2,
44021
+ Fn: Fn2,
44022
+ allowInfinityPoint: c.allowInfinityPoint,
44023
+ endo: c.endo,
44024
+ isTorsionFree: c.isTorsionFree,
44025
+ clearCofactor: c.clearCofactor,
44026
+ fromBytes: c.fromBytes,
44027
+ toBytes: c.toBytes
44028
+ };
44029
+ return { CURVE, curveOpts };
44030
+ }
44031
+ function _ecdsa_legacy_opts_to_new(c) {
44032
+ const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);
44033
+ const ecdsaOpts = {
44034
+ hmac: c.hmac,
44035
+ randomBytes: c.randomBytes,
44036
+ lowS: c.lowS,
44037
+ bits2int: c.bits2int,
44038
+ bits2int_modN: c.bits2int_modN
44039
+ };
44040
+ return { CURVE, curveOpts, hash: c.hash, ecdsaOpts };
44041
+ }
44042
+ function _ecdsa_new_output_to_legacy(c, _ecdsa) {
44043
+ const Point = _ecdsa.Point;
44044
+ return Object.assign({}, _ecdsa, {
44045
+ ProjectivePoint: Point,
44046
+ CURVE: Object.assign({}, c, nLength(Point.Fn.ORDER, Point.Fn.BITS))
44047
+ });
44048
+ }
44049
+ function weierstrass(c) {
44050
+ const { CURVE, curveOpts, hash, ecdsaOpts } = _ecdsa_legacy_opts_to_new(c);
44051
+ const Point = weierstrassN(CURVE, curveOpts);
44052
+ const signs = ecdsa(Point, hash, ecdsaOpts);
44053
+ return _ecdsa_new_output_to_legacy(c, signs);
44054
+ }
44055
+
44056
+ // node_modules/@noble/curves/esm/_shortw_utils.js
44057
+ function createCurve(curveDef, defHash) {
44058
+ const create = (hash) => weierstrass({ ...curveDef, hash });
44059
+ return { ...create(defHash), create };
44060
+ }
44061
+
44062
+ // node_modules/@noble/curves/esm/nist.js
44063
+ var p256_CURVE = {
44064
+ p: BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"),
44065
+ n: BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"),
44066
+ h: BigInt(1),
44067
+ a: BigInt("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"),
44068
+ b: BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"),
44069
+ Gx: BigInt("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"),
44070
+ Gy: BigInt("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5")
44071
+ };
44072
+ var p384_CURVE = {
44073
+ p: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"),
44074
+ n: BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973"),
44075
+ h: BigInt(1),
44076
+ a: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc"),
44077
+ b: BigInt("0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"),
44078
+ Gx: BigInt("0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"),
44079
+ Gy: BigInt("0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f")
44080
+ };
44081
+ var p521_CURVE = {
44082
+ p: BigInt("0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
44083
+ n: BigInt("0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409"),
44084
+ h: BigInt(1),
44085
+ a: BigInt("0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"),
44086
+ b: BigInt("0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00"),
44087
+ Gx: BigInt("0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66"),
44088
+ Gy: BigInt("0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650")
44089
+ };
44090
+ var Fp256 = Field(p256_CURVE.p);
44091
+ var Fp384 = Field(p384_CURVE.p);
44092
+ var Fp521 = Field(p521_CURVE.p);
44093
+ var p256 = createCurve({ ...p256_CURVE, Fp: Fp256, lowS: false }, sha256);
44094
+ var p384 = createCurve({ ...p384_CURVE, Fp: Fp384, lowS: false }, sha384);
44095
+ var p521 = createCurve({ ...p521_CURVE, Fp: Fp521, lowS: false, allowedPrivateKeyLengths: [130, 131, 132] }, sha512);
44096
+
44097
+ // node_modules/@noble/curves/esm/p256.js
44098
+ var p2562 = p256;
44099
+
44100
+ // src/webauthn-approval.ts
41641
44101
  function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblind.com", timeoutSeconds = 300) {
41642
- const challengeBytes = (0, import_node_crypto7.randomBytes)(32);
41643
- const contextHash = (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify({ requestId, toolName, agentId, timestamp: Date.now() })).digest("hex");
44102
+ const challengeBytes = (0, import_node_crypto8.randomBytes)(32);
44103
+ const contextHash = (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify({ requestId, toolName, agentId, timestamp: Date.now() })).digest("hex");
41644
44104
  return {
41645
44105
  challenge: base64urlEncode(challengeBytes),
41646
44106
  requestId,
@@ -41655,57 +44115,86 @@ function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblin
41655
44115
  function toCredentialRequestOptions(challenge, allowCredentials) {
41656
44116
  return {
41657
44117
  publicKey: {
41658
- challenge: base64urlDecode(challenge.challenge).buffer,
44118
+ challenge: base64urlDecode2(challenge.challenge).buffer,
41659
44119
  rpId: challenge.rpId,
41660
44120
  timeout: challenge.timeoutSeconds * 1e3,
41661
44121
  userVerification: "required",
41662
44122
  // Always require biometric
41663
44123
  ...allowCredentials ? {
41664
44124
  allowCredentials: allowCredentials.map((c) => ({
41665
- id: base64urlDecode(c.id).buffer,
44125
+ id: base64urlDecode2(c.id).buffer,
41666
44126
  type: "public-key"
41667
44127
  }))
41668
44128
  } : {}
41669
44129
  }
41670
44130
  };
41671
44131
  }
41672
- function verifyApprovalAssertion(challenge, assertion) {
44132
+ function verifyApprovalAssertion(challenge, assertion, credentialPublicKey, opts = {}) {
44133
+ const now = opts.now ?? Date.now();
44134
+ const fail = (reason, partial = {}) => ({
44135
+ valid: false,
44136
+ reason,
44137
+ credentialId: assertion.credentialId,
44138
+ authenticatorType: "unknown",
44139
+ userVerified: false,
44140
+ signCount: 0,
44141
+ contextHash: challenge.contextHash,
44142
+ approvedAt: new Date(now).toISOString(),
44143
+ ...partial
44144
+ });
41673
44145
  const createdAt = new Date(challenge.createdAt).getTime();
41674
- const now = Date.now();
41675
- if (now - createdAt > challenge.timeoutSeconds * 1e3) {
41676
- return {
41677
- valid: false,
41678
- credentialId: assertion.credentialId,
41679
- authenticatorType: "unknown",
41680
- userVerified: false,
41681
- signCount: 0,
41682
- contextHash: challenge.contextHash,
41683
- approvedAt: (/* @__PURE__ */ new Date()).toISOString()
41684
- };
41685
- }
41686
- const authData = base64urlDecode(assertion.authenticatorData);
44146
+ if (now - createdAt > challenge.timeoutSeconds * 1e3) return fail("challenge_expired");
44147
+ if (!credentialPublicKey?.publicKeyHex) return fail("missing_credential_public_key");
44148
+ const clientDataBytes = base64urlDecode2(assertion.clientDataJSON);
44149
+ let clientData;
44150
+ try {
44151
+ clientData = JSON.parse(Buffer.from(clientDataBytes).toString("utf8"));
44152
+ } catch {
44153
+ return fail("client_data_parse_error");
44154
+ }
44155
+ if (clientData.type !== "webauthn.get") return fail("wrong_client_data_type");
44156
+ if (!constantTimeStrEqual(clientData.challenge ?? "", challenge.challenge)) return fail("challenge_mismatch");
44157
+ const allowedOrigins = opts.expectedOrigin ? Array.isArray(opts.expectedOrigin) ? opts.expectedOrigin : [opts.expectedOrigin] : [`https://${challenge.rpId}`];
44158
+ if (!clientData.origin || !allowedOrigins.includes(clientData.origin)) return fail("origin_mismatch");
44159
+ const authData = base64urlDecode2(assertion.authenticatorData);
44160
+ if (authData.length < 37) return fail("authenticator_data_too_short");
44161
+ const rpIdHash = authData.slice(0, 32);
44162
+ const expectedRpIdHash = sha2562(new TextEncoder().encode(challenge.rpId));
44163
+ if (!bytesEqual(rpIdHash, expectedRpIdHash)) return fail("rp_id_hash_mismatch");
41687
44164
  const flags = authData[32];
41688
44165
  const userPresent = !!(flags & 1);
41689
44166
  const userVerified = !!(flags & 4);
41690
- const attestedCredData = !!(flags & 64);
41691
- const signCount = authData.length >= 37 ? authData[33] << 24 | authData[34] << 16 | authData[35] << 8 | authData[36] : 0;
41692
- let authenticatorType = "unknown";
44167
+ if (!userPresent) return fail("user_not_present");
44168
+ if ((opts.requireUserVerification ?? true) && !userVerified) return fail("user_verification_required", { userVerified });
44169
+ const signCount = authData[33] << 24 | authData[34] << 16 | authData[35] << 8 | authData[36];
44170
+ if (typeof opts.prevSignCount === "number" && signCount !== 0 && signCount <= opts.prevSignCount) {
44171
+ return fail("sign_count_regression", { userVerified, signCount });
44172
+ }
44173
+ const signedData = concatBytes2(authData, sha2562(clientDataBytes));
44174
+ const sigBytes = base64urlDecode2(assertion.signature);
44175
+ let sigOk = false;
41693
44176
  try {
41694
- const clientData = JSON.parse(Buffer.from(base64urlDecode(assertion.clientDataJSON)).toString());
41695
- if (clientData.type === "webauthn.get") {
41696
- authenticatorType = "platform";
44177
+ if (credentialPublicKey.alg === -7) {
44178
+ sigOk = p2562.verify(sigBytes, sha2562(signedData), hexToBytes(credentialPublicKey.publicKeyHex), { format: "der" });
44179
+ } else if (credentialPublicKey.alg === -8) {
44180
+ sigOk = ed25519.verify(sigBytes, signedData, hexToBytes(credentialPublicKey.publicKeyHex));
44181
+ } else {
44182
+ return fail("unsupported_algorithm", { userVerified, signCount });
41697
44183
  }
41698
44184
  } catch {
44185
+ sigOk = false;
41699
44186
  }
44187
+ if (!sigOk) return fail("invalid_signature", { userVerified, signCount });
41700
44188
  return {
41701
- valid: userPresent,
41702
- // At minimum, user must be present
44189
+ valid: true,
41703
44190
  credentialId: assertion.credentialId,
41704
- authenticatorType,
44191
+ // Heuristic: platform authenticators (TouchID/FaceID/Hello) report UV; roaming
44192
+ // keys without a PIN are UP-only. Attachment is authoritative only at registration.
44193
+ authenticatorType: userVerified ? "platform" : "cross-platform",
41705
44194
  userVerified,
41706
44195
  signCount,
41707
44196
  contextHash: challenge.contextHash,
41708
- approvedAt: (/* @__PURE__ */ new Date()).toISOString()
44197
+ approvedAt: new Date(now).toISOString()
41709
44198
  };
41710
44199
  }
41711
44200
  function createApprovalReceiptPayload(challenge, result) {
@@ -41720,17 +44209,33 @@ function createApprovalReceiptPayload(challenge, result) {
41720
44209
  context_hash: result.contextHash,
41721
44210
  approved_at: result.approvedAt,
41722
44211
  // Hash the credential ID for privacy — don't store the raw ID
41723
- credential_id_hash: (0, import_node_crypto7.createHash)("sha256").update(result.credentialId).digest("hex").slice(0, 16)
44212
+ credential_id_hash: (0, import_node_crypto8.createHash)("sha256").update(result.credentialId).digest("hex").slice(0, 16)
41724
44213
  };
41725
44214
  }
41726
44215
  function base64urlEncode(buffer) {
41727
44216
  return Buffer.from(buffer).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
41728
44217
  }
41729
- function base64urlDecode(str) {
44218
+ function base64urlDecode2(str) {
41730
44219
  const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
41731
44220
  const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
41732
44221
  return new Uint8Array(Buffer.from(padded, "base64"));
41733
44222
  }
44223
+ function concatBytes2(a, b) {
44224
+ const out = new Uint8Array(a.length + b.length);
44225
+ out.set(a, 0);
44226
+ out.set(b, a.length);
44227
+ return out;
44228
+ }
44229
+ function bytesEqual(a, b) {
44230
+ if (a.length !== b.length) return false;
44231
+ return (0, import_node_crypto8.timingSafeEqual)(Buffer.from(a), Buffer.from(b));
44232
+ }
44233
+ function constantTimeStrEqual(a, b) {
44234
+ const ab = Buffer.from(a, "utf8");
44235
+ const bb = Buffer.from(b, "utf8");
44236
+ if (ab.length !== bb.length) return false;
44237
+ return (0, import_node_crypto8.timingSafeEqual)(ab, bb);
44238
+ }
41734
44239
 
41735
44240
  // src/did-vc.ts
41736
44241
  function ed25519ToDIDKey(publicKeyHex) {
@@ -41974,7 +44479,7 @@ function evaluatePolicy(tool, policy) {
41974
44479
  }
41975
44480
 
41976
44481
  // src/evidence-authenticity.ts
41977
- var import_node_crypto8 = require("crypto");
44482
+ var import_node_crypto9 = require("crypto");
41978
44483
  async function createEvidenceAttestation(input) {
41979
44484
  const tlsNotaryAvailable = await isTLSNotaryAvailable();
41980
44485
  if (tlsNotaryAvailable) {
@@ -42034,14 +44539,14 @@ async function verifyEvidenceAttestation(attestation) {
42034
44539
  }
42035
44540
  }
42036
44541
  function hashResponseBody(body) {
42037
- return (0, import_node_crypto8.createHash)("sha256").update(typeof body === "string" ? body : body).digest("hex");
44542
+ return (0, import_node_crypto9.createHash)("sha256").update(typeof body === "string" ? body : body).digest("hex");
42038
44543
  }
42039
44544
  function createAttestationField(attestation) {
42040
44545
  return {
42041
44546
  evidence_authenticity: {
42042
44547
  version: attestation.version,
42043
44548
  method: attestation.method,
42044
- url_hash: (0, import_node_crypto8.createHash)("sha256").update(attestation.url).digest("hex").slice(0, 16),
44549
+ url_hash: (0, import_node_crypto9.createHash)("sha256").update(attestation.url).digest("hex").slice(0, 16),
42045
44550
  response_hash: attestation.responseHash,
42046
44551
  fetched_at: attestation.fetchedAt,
42047
44552
  verified: attestation.verified,
@@ -42072,7 +44577,7 @@ async function createTLSNotaryAttestation(input) {
42072
44577
  }
42073
44578
 
42074
44579
  // src/c2pa-credentials.ts
42075
- var import_node_crypto9 = require("crypto");
44580
+ var import_node_crypto10 = require("crypto");
42076
44581
  function createC2PAManifest(receipts, options) {
42077
44582
  const generator = options.generator || "protect-mcp";
42078
44583
  const version = options.version || "0.3.3";
@@ -42086,7 +44591,7 @@ function createC2PAManifest(receipts, options) {
42086
44591
  (r) => r.payload?.decision === "deny"
42087
44592
  );
42088
44593
  const receiptHashes = receipts.map(
42089
- (r) => (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify(r)).digest("hex")
44594
+ (r) => (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify(r)).digest("hex")
42090
44595
  );
42091
44596
  const merkleRoot2 = computeMerkleRoot(receiptHashes);
42092
44597
  const assertions = [
@@ -42228,7 +44733,7 @@ function computeMerkleRoot(hashes) {
42228
44733
  const left = hashes[i];
42229
44734
  const right = i + 1 < hashes.length ? hashes[i + 1] : left;
42230
44735
  nextLevel.push(
42231
- (0, import_node_crypto9.createHash)("sha256").update(left + right).digest("hex")
44736
+ (0, import_node_crypto10.createHash)("sha256").update(left + right).digest("hex")
42232
44737
  );
42233
44738
  }
42234
44739
  return computeMerkleRoot(nextLevel);
@@ -42281,7 +44786,7 @@ function toManifoldFormat(prediction) {
42281
44786
  }
42282
44787
 
42283
44788
  // src/agent-exchange.ts
42284
- var import_node_crypto10 = require("crypto");
44789
+ var import_node_crypto11 = require("crypto");
42285
44790
  var ReceiptPropagator = class {
42286
44791
  issuer;
42287
44792
  signer;
@@ -42302,7 +44807,7 @@ var ReceiptPropagator = class {
42302
44807
  delegate(delegateId, options) {
42303
44808
  const now = /* @__PURE__ */ new Date();
42304
44809
  const receipt = {
42305
- receipt_id: `del_${(0, import_node_crypto10.randomUUID)().slice(0, 12)}`,
44810
+ receipt_id: `del_${(0, import_node_crypto11.randomUUID)().slice(0, 12)}`,
42306
44811
  receipt_type: "delegation",
42307
44812
  issuer_id: this.issuer,
42308
44813
  event_time: now.toISOString(),
@@ -42359,7 +44864,7 @@ var ReceiptPropagator = class {
42359
44864
  const currentCount = this.delegationCallCounts.get(options.delegation_receipt) || 0;
42360
44865
  this.delegationCallCounts.set(options.delegation_receipt, currentCount + 1);
42361
44866
  const receipt = {
42362
- receipt_id: `act_${(0, import_node_crypto10.randomUUID)().slice(0, 12)}`,
44867
+ receipt_id: `act_${(0, import_node_crypto11.randomUUID)().slice(0, 12)}`,
42363
44868
  receipt_type: "execution",
42364
44869
  issuer_id: this.issuer,
42365
44870
  event_time: (/* @__PURE__ */ new Date()).toISOString(),
@@ -42451,7 +44956,7 @@ function createReceiptChannel(orchestratorId) {
42451
44956
  async withDelegation(delegateId, tools, fn, options) {
42452
44957
  const delegation = propagator.delegate(delegateId, {
42453
44958
  tools,
42454
- scope: options?.scope || `task-${(0, import_node_crypto10.randomUUID)().slice(0, 8)}`,
44959
+ scope: options?.scope || `task-${(0, import_node_crypto11.randomUUID)().slice(0, 8)}`,
42455
44960
  ttl: options?.ttl || 3600,
42456
44961
  maxCalls: options?.maxCalls
42457
44962
  });
@@ -42601,6 +45106,48 @@ var TOOLS = [
42601
45106
  },
42602
45107
  required: ["environment"]
42603
45108
  }
45109
+ },
45110
+ {
45111
+ name: "github_create_pr",
45112
+ description: "Create a GitHub pull request",
45113
+ inputSchema: {
45114
+ type: "object",
45115
+ properties: {
45116
+ repo: { type: "string", description: "Repository name" },
45117
+ branch: { type: "string", description: "Source branch" },
45118
+ title: { type: "string", description: "Pull request title" }
45119
+ },
45120
+ required: ["repo", "branch", "title"]
45121
+ }
45122
+ },
45123
+ {
45124
+ name: "send_email",
45125
+ description: "Send an email",
45126
+ inputSchema: {
45127
+ type: "object",
45128
+ properties: {
45129
+ to: { type: "string", description: "Recipient email address" },
45130
+ subject: { type: "string", description: "Subject line" },
45131
+ body: { type: "string", description: "Message body" }
45132
+ },
45133
+ required: ["to", "subject"]
45134
+ }
45135
+ },
45136
+ {
45137
+ name: "pms_book_fill",
45138
+ description: "Book a fill into the mock portfolio management system",
45139
+ inputSchema: {
45140
+ type: "object",
45141
+ properties: {
45142
+ account: { type: "string", description: "Portfolio or fund account" },
45143
+ symbol: { type: "string", description: "Instrument symbol" },
45144
+ side: { type: "string", enum: ["BUY", "SELL"] },
45145
+ quantity: { type: "number" },
45146
+ price: { type: "number" },
45147
+ strategy: { type: "string" }
45148
+ },
45149
+ required: ["account", "symbol", "side", "quantity", "price"]
45150
+ }
42604
45151
  }
42605
45152
  ];
42606
45153
  function handleRequest(request) {
@@ -42610,7 +45157,7 @@ function handleRequest(request) {
42610
45157
  id: request.id,
42611
45158
  result: {
42612
45159
  protocolVersion: "2024-11-05",
42613
- serverInfo: { name: "protect-mcp-demo", version: "0.5.3" },
45160
+ serverInfo: { name: "protect-mcp-demo", version: process.env.PROTECT_MCP_VERSION || "0.5.3" },
42614
45161
  capabilities: { tools: {} }
42615
45162
  }
42616
45163
  });
@@ -42648,6 +45195,15 @@ Contents: Hello from protect-mcp demo server!`;
42648
45195
  case "deploy":
42649
45196
  resultText = `[demo] Deployed to ${args.environment || "staging"}${args.reason ? ` (reason: ${args.reason})` : ""}`;
42650
45197
  break;
45198
+ case "github_create_pr":
45199
+ resultText = `[demo] Created PR in ${args.repo || "scopeblind/demo"} from ${args.branch || "agent/demo"}: ${args.title || "Agent change"}`;
45200
+ break;
45201
+ case "send_email":
45202
+ resultText = `[demo] Drafted email to ${args.to || "pm@example.com"}: ${args.subject || "Agent update"}`;
45203
+ break;
45204
+ case "pms_book_fill":
45205
+ resultText = `[demo] Booked ${args.side || "BUY"} ${args.quantity || 0} ${args.symbol || "AAPL"} @ ${args.price || 0} into ${args.account || "Demo Fund"} (${args.strategy || "Unassigned"})`;
45206
+ break;
42651
45207
  default:
42652
45208
  resultText = `[demo] Unknown tool: ${toolName}`;
42653
45209
  }
@@ -42687,7 +45243,7 @@ function createSandboxServer() {
42687
45243
  const { z } = require_zod();
42688
45244
  const server = new McpServer({
42689
45245
  name: "protect-mcp",
42690
- version: "0.4.5",
45246
+ version: process.env.PROTECT_MCP_VERSION || "0.4.5",
42691
45247
  description: "Security gateway for MCP servers. Per-tool policies, Ed25519-signed receipts, human approval gates, trust tiers."
42692
45248
  });
42693
45249
  server.tool(
@@ -42720,12 +45276,38 @@ function createSandboxServer() {
42720
45276
  { environment: z.enum(["staging", "production"]).describe("Target environment"), reason: z.string().optional().describe("Deployment reason") },
42721
45277
  async (args) => ({ content: [{ type: "text", text: `[demo] Deployed to ${args.environment}` }] })
42722
45278
  );
45279
+ server.tool("github_create_pr", "Create a GitHub pull request", {
45280
+ repo: z.string(),
45281
+ branch: z.string(),
45282
+ title: z.string()
45283
+ }, async (args) => ({
45284
+ content: [{ type: "text", text: `[demo] PR created in ${args.repo} from ${args.branch}: ${args.title}` }]
45285
+ }));
45286
+ server.tool("send_email", "Send an email", {
45287
+ to: z.string(),
45288
+ subject: z.string(),
45289
+ body: z.string().optional()
45290
+ }, async (args) => ({
45291
+ content: [{ type: "text", text: `[demo] Email prepared for ${args.to}: ${args.subject}` }]
45292
+ }));
45293
+ server.tool("pms_book_fill", "Book a fill into a mock PMS", {
45294
+ account: z.string(),
45295
+ symbol: z.string(),
45296
+ side: z.enum(["BUY", "SELL"]),
45297
+ quantity: z.number(),
45298
+ price: z.number(),
45299
+ strategy: z.string().optional()
45300
+ }, async (args) => ({
45301
+ content: [{ type: "text", text: `[demo] Booked ${args.side} ${args.quantity} ${args.symbol} @ ${args.price} into ${args.account}` }]
45302
+ }));
42723
45303
  return server;
42724
45304
  }
42725
45305
  // Annotate the CommonJS export names for ESM import in node:
42726
45306
  0 && (module.exports = {
42727
45307
  BUILTIN_PATTERNS,
45308
+ CONNECTOR_PILOTS,
42728
45309
  ConfidentialGate,
45310
+ POLICY_PACKS,
42729
45311
  ProtectGateway,
42730
45312
  ReceiptPropagator,
42731
45313
  ScopeBlindBridge,
@@ -42735,6 +45317,9 @@ function createSandboxServer() {
42735
45317
  collectSignedReceipts,
42736
45318
  computeCalibration,
42737
45319
  confidentialInference,
45320
+ connectorDirectory,
45321
+ connectorDoctor,
45322
+ connectorPilotIds,
42738
45323
  createApprovalChallenge,
42739
45324
  createApprovalReceiptPayload,
42740
45325
  createAttestationField,
@@ -42746,6 +45331,7 @@ function createSandboxServer() {
42746
45331
  createReceiptChannel,
42747
45332
  createSandbox,
42748
45333
  createSandboxServer,
45334
+ createSelectiveDisclosurePackage,
42749
45335
  destroySandbox,
42750
45336
  discloseField,
42751
45337
  ed25519ToDIDKey,
@@ -42766,6 +45352,8 @@ function createSandboxServer() {
42766
45352
  generateSampleCedarPolicy,
42767
45353
  generateSchemaStub,
42768
45354
  generateVerifyReceiptSkill,
45355
+ getConnectorPilot,
45356
+ getPolicyPack,
42769
45357
  getScopeBlindBridge,
42770
45358
  getSignerInfo,
42771
45359
  getToolPolicy,
@@ -42786,8 +45374,10 @@ function createSandboxServer() {
42786
45374
  parseLogFile,
42787
45375
  parseNotificationConfigFromEnv,
42788
45376
  parseRateLimit,
45377
+ policyPackIds,
42789
45378
  policySetFromSource,
42790
45379
  queryExternalPDP,
45380
+ readInstalledConnectorPilots,
42791
45381
  receiptToVP,
42792
45382
  receiptsToHFRows,
42793
45383
  redactFields,
@@ -42811,7 +45401,9 @@ function createSandboxServer() {
42811
45401
  verifyApprovalAssertion,
42812
45402
  verifyCommitment,
42813
45403
  verifyEvidenceAttestation,
42814
- verifyRekorAnchor
45404
+ verifyRekorAnchor,
45405
+ verifySelectiveDisclosurePackage,
45406
+ writeConnectorPilots
42815
45407
  });
42816
45408
  /**
42817
45409
  * scopeblind-bridge.ts
@@ -42853,5 +45445,9 @@ function createSandboxServer() {
42853
45445
  @noble/curves/esm/abstract/curve.js:
42854
45446
  @noble/curves/esm/abstract/edwards.js:
42855
45447
  @noble/curves/esm/ed25519.js:
45448
+ @noble/curves/esm/abstract/weierstrass.js:
45449
+ @noble/curves/esm/_shortw_utils.js:
45450
+ @noble/curves/esm/nist.js:
45451
+ @noble/curves/esm/p256.js:
42856
45452
  (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
42857
45453
  */