protect-mcp 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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)
@@ -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,
@@ -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
  };
@@ -39058,6 +39163,50 @@ function collectPath(leaves, index, out) {
39058
39163
  out.push(merkleRoot(leaves.slice(0, k)));
39059
39164
  }
39060
39165
  }
39166
+ function verifyProof(expectedRootHex, leafHash, proof) {
39167
+ if (proof.index < 0 || proof.index >= proof.treeSize) return false;
39168
+ if (proof.treeSize === 1) {
39169
+ return proof.siblings.length === 0 && bytesToHex(leafHash).toLowerCase() === expectedRootHex.toLowerCase();
39170
+ }
39171
+ let result;
39172
+ try {
39173
+ result = reconstructRoot(
39174
+ leafHash,
39175
+ proof.index,
39176
+ proof.treeSize,
39177
+ proof.siblings
39178
+ );
39179
+ } catch {
39180
+ return false;
39181
+ }
39182
+ return bytesToHex(result).toLowerCase() === expectedRootHex.toLowerCase();
39183
+ }
39184
+ function reconstructRoot(leafHash, index, treeSize, siblings) {
39185
+ if (treeSize === 1) {
39186
+ if (siblings.length !== 0) {
39187
+ throw new Error("reconstructRoot: extra siblings at single-leaf level");
39188
+ }
39189
+ return leafHash;
39190
+ }
39191
+ if (siblings.length === 0) {
39192
+ throw new Error("reconstructRoot: ran out of siblings before single-leaf");
39193
+ }
39194
+ const k = largestPowerOfTwoLessThan(treeSize);
39195
+ const outermostSibling = hexToBytes(siblings[siblings.length - 1]);
39196
+ const innerSiblings = siblings.slice(0, -1);
39197
+ if (index < k) {
39198
+ const leftHash = reconstructRoot(leafHash, index, k, innerSiblings);
39199
+ return hashInternal(leftHash, outermostSibling);
39200
+ } else {
39201
+ const rightHash = reconstructRoot(
39202
+ leafHash,
39203
+ index - k,
39204
+ treeSize - k,
39205
+ innerSiblings
39206
+ );
39207
+ return hashInternal(outermostSibling, rightHash);
39208
+ }
39209
+ }
39061
39210
  function largestPowerOfTwoLessThan(n) {
39062
39211
  if (n < 2) {
39063
39212
  throw new Error(`largestPowerOfTwoLessThan: n must be >= 2 (got ${n})`);
@@ -39085,6 +39234,17 @@ function base64urlNoPad(bytes) {
39085
39234
  const std = typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : btoa(String.fromCharCode(...bytes));
39086
39235
  return std.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
39087
39236
  }
39237
+ function base64urlDecode(s) {
39238
+ const std = s.replace(/-/g, "+").replace(/_/g, "/");
39239
+ const padded = std + "=".repeat((4 - std.length % 4) % 4);
39240
+ if (typeof Buffer !== "undefined") {
39241
+ return new Uint8Array(Buffer.from(padded, "base64"));
39242
+ }
39243
+ const bin = atob(padded);
39244
+ const out = new Uint8Array(bin.length);
39245
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
39246
+ return out;
39247
+ }
39088
39248
  function encodeLeaf(field) {
39089
39249
  const obj = {
39090
39250
  name: field.name,
@@ -39218,11 +39378,128 @@ function discloseField(receiptHash, fieldName, openings) {
39218
39378
  proof
39219
39379
  };
39220
39380
  }
39381
+ function createSelectiveDisclosurePackage(receipt, fieldNames, openings) {
39382
+ const receiptHash = receiptHashHex(receipt);
39383
+ const committedFieldsRoot = typeof receipt.committed_fields_root === "string" ? receipt.committed_fields_root : "";
39384
+ if (!committedFieldsRoot) {
39385
+ throw new Error("selective disclosure requires a committed receipt with committed_fields_root");
39386
+ }
39387
+ const committedFieldNames = committedFieldNamesFromReceipt(receipt, openings);
39388
+ const uniqueFields = Array.from(new Set(fieldNames));
39389
+ for (const fieldName of uniqueFields) {
39390
+ if (!committedFieldNames.includes(fieldName)) {
39391
+ throw new Error(`selective disclosure: field "${fieldName}" is not committed by this receipt`);
39392
+ }
39393
+ }
39394
+ const disclosures = uniqueFields.map((fieldName) => discloseField(receiptHash, fieldName, openings));
39395
+ const hiddenFields = committedFieldNames.filter((fieldName) => !uniqueFields.includes(fieldName));
39396
+ return {
39397
+ type: "scopeblind.selective_disclosure.v0",
39398
+ version: 0,
39399
+ parent_receipt_hash: receiptHash,
39400
+ committed_fields_root: committedFieldsRoot,
39401
+ disclosed_fields: uniqueFields,
39402
+ hidden_fields: hiddenFields,
39403
+ disclosures,
39404
+ verifier_explanation: {
39405
+ summary: "This package opens selected committed receipt fields and leaves the rest hidden.",
39406
+ disclosed: uniqueFields.length ? `Disclosed fields: ${uniqueFields.join(", ")}.` : "No fields were disclosed.",
39407
+ hidden: hiddenFields.length ? `Hidden committed fields: ${hiddenFields.join(", ")}. Their salted commitments remain bound to the signed receipt root.` : "No committed fields remain hidden.",
39408
+ limitation: "Selective Disclosure v0 uses salted SHA-256 commitments and Merkle proofs. It is not a full zero-knowledge proof."
39409
+ }
39410
+ };
39411
+ }
39412
+ function verifySelectiveDisclosurePackage(receipt, disclosure) {
39413
+ const errors = [];
39414
+ if (disclosure.type !== "scopeblind.selective_disclosure.v0") {
39415
+ errors.push("disclosure.type is not scopeblind.selective_disclosure.v0");
39416
+ }
39417
+ const actualReceiptHash = receiptHashHex(receipt);
39418
+ const receiptHashValid = disclosure.parent_receipt_hash === actualReceiptHash;
39419
+ if (!receiptHashValid) {
39420
+ errors.push("parent_receipt_hash does not match the supplied receipt");
39421
+ }
39422
+ const root = typeof receipt.committed_fields_root === "string" ? receipt.committed_fields_root : "";
39423
+ const commitmentRootValid = Boolean(root) && disclosure.committed_fields_root === root;
39424
+ if (!commitmentRootValid) {
39425
+ errors.push("committed_fields_root does not match the supplied receipt");
39426
+ }
39427
+ const signatureValid = verifyCommittedReceiptSignature(receipt);
39428
+ if (signatureValid === false) {
39429
+ errors.push("receipt signature failed verification");
39430
+ }
39431
+ const committedFieldNames = committedFieldNamesFromReceipt(receipt, {});
39432
+ const disclosed = /* @__PURE__ */ new Set();
39433
+ for (const item of disclosure.disclosures || []) {
39434
+ if (item.parent_receipt_hash !== disclosure.parent_receipt_hash) {
39435
+ errors.push(`disclosure for "${item.name}" targets a different receipt hash`);
39436
+ continue;
39437
+ }
39438
+ if (!committedFieldNames.includes(item.name)) {
39439
+ errors.push(`field "${item.name}" is not listed in committed_field_names`);
39440
+ continue;
39441
+ }
39442
+ const leafBytes = encodeLeaf({
39443
+ name: item.name,
39444
+ salt: base64urlDecode(item.salt),
39445
+ value: item.value
39446
+ });
39447
+ const ok = root ? verifyProof(root, hashLeaf(leafBytes), item.proof) : false;
39448
+ if (!ok) {
39449
+ errors.push(`field "${item.name}" failed Merkle inclusion verification`);
39450
+ } else {
39451
+ disclosed.add(item.name);
39452
+ }
39453
+ }
39454
+ const disclosedFields = Array.from(disclosed);
39455
+ const hiddenFields = committedFieldNames.filter((fieldName) => !disclosed.has(fieldName));
39456
+ const valid = errors.length === 0 && receiptHashValid && commitmentRootValid && signatureValid !== false;
39457
+ const explanation = [
39458
+ valid ? "Selective disclosure verified: the disclosed fields open to the signed receipt commitment root." : "Selective disclosure failed verification.",
39459
+ 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.",
39460
+ disclosedFields.length ? `Disclosed fields: ${disclosedFields.join(", ")}.` : "No fields were disclosed.",
39461
+ hiddenFields.length ? `Hidden fields: ${hiddenFields.join(", ")}. These remain private but bound to the same commitment root.` : "No committed fields remain hidden.",
39462
+ "Limitation: this is salted commitment disclosure, not full zero-knowledge."
39463
+ ];
39464
+ return {
39465
+ valid,
39466
+ receipt_hash_valid: receiptHashValid,
39467
+ signature_valid: signatureValid,
39468
+ commitment_root_valid: commitmentRootValid,
39469
+ disclosed_fields: disclosedFields,
39470
+ hidden_fields: hiddenFields,
39471
+ errors,
39472
+ explanation
39473
+ };
39474
+ }
39475
+ function committedFieldNamesFromReceipt(receipt, openings) {
39476
+ const fromReceipt = Array.isArray(receipt.committed_field_names) ? receipt.committed_field_names.filter((fieldName) => typeof fieldName === "string") : [];
39477
+ const names = fromReceipt.length ? fromReceipt : Object.keys(openings);
39478
+ return Array.from(new Set(names)).sort();
39479
+ }
39480
+ function receiptHashHex(receipt) {
39481
+ return bytesToHex(sha2562(new TextEncoder().encode(jcs(receipt))));
39482
+ }
39483
+ function verifyCommittedReceiptSignature(receipt) {
39484
+ const signature = receipt.signature;
39485
+ if (!signature || typeof signature !== "object") return null;
39486
+ const sig = signature;
39487
+ if (sig.alg !== "EdDSA" || typeof sig.sig !== "string" || typeof sig.public_key !== "string") {
39488
+ return null;
39489
+ }
39490
+ const { signature: _signature, ...payloadWithoutSig } = receipt;
39491
+ const messageHash = sha2562(new TextEncoder().encode(jcs(payloadWithoutSig)));
39492
+ try {
39493
+ return ed25519.verify(base64urlDecode(sig.sig), messageHash, hexToBytes(sig.public_key));
39494
+ } catch {
39495
+ return false;
39496
+ }
39497
+ }
39221
39498
 
39222
39499
  // src/bundle.ts
39223
39500
  function createAuditBundle(opts) {
39224
39501
  const receipts = opts.receipts.filter(
39225
- (r) => r && typeof r === "object" && typeof r.signature === "string"
39502
+ (r) => r && typeof r === "object" && (typeof r.signature === "string" || r.signature !== null && typeof r.signature === "object")
39226
39503
  );
39227
39504
  if (receipts.length === 0) {
39228
39505
  throw new Error("Audit bundle requires at least one signed receipt");
@@ -39251,10 +39528,18 @@ function createAuditBundle(opts) {
39251
39528
  time_range: timeRange,
39252
39529
  receipts,
39253
39530
  anchors: opts.anchors || [],
39531
+ selective_disclosures: opts.selectiveDisclosures || [],
39532
+ privacy: {
39533
+ selective_disclosure: {
39534
+ supported: true,
39535
+ model: "salted_commitments_merkle_v0",
39536
+ statement: "Committed receipts may disclose selected fields with salted Merkle openings. Undisclosed committed fields remain hidden while staying bound to the signed commitment root."
39537
+ }
39538
+ },
39254
39539
  verification: {
39255
39540
  algorithm: "ed25519",
39256
39541
  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`
39542
+ 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
39543
  }
39259
39544
  };
39260
39545
  }
@@ -39717,7 +40002,7 @@ function validateEvidenceReceipt(receipt) {
39717
40002
 
39718
40003
  // src/hook-server.ts
39719
40004
  var import_node_http2 = require("http");
39720
- var import_node_crypto4 = require("crypto");
40005
+ var import_node_crypto5 = require("crypto");
39721
40006
  var import_node_fs9 = require("fs");
39722
40007
  var import_node_path5 = require("path");
39723
40008
 
@@ -39900,7 +40185,7 @@ function computePayloadDigest(input) {
39900
40185
  return void 0;
39901
40186
  }
39902
40187
  return {
39903
- input_hash: (0, import_node_crypto4.createHash)("sha256").update(content).digest("hex"),
40188
+ input_hash: (0, import_node_crypto5.createHash)("sha256").update(content).digest("hex"),
39904
40189
  input_size: size,
39905
40190
  truncated: true,
39906
40191
  preview: content.slice(0, 256)
@@ -39913,7 +40198,7 @@ function computeOutputDigest(output) {
39913
40198
  return void 0;
39914
40199
  }
39915
40200
  return {
39916
- output_hash: (0, import_node_crypto4.createHash)("sha256").update(content).digest("hex"),
40201
+ output_hash: (0, import_node_crypto5.createHash)("sha256").update(content).digest("hex"),
39917
40202
  output_size: size
39918
40203
  };
39919
40204
  }
@@ -39936,13 +40221,14 @@ function detectSandboxState() {
39936
40221
  async function handlePreToolUse(input, state) {
39937
40222
  const hookStart = Date.now();
39938
40223
  const toolName = input.toolName || "unknown";
39939
- const requestId = input.toolUseId || (0, import_node_crypto4.randomUUID)().slice(0, 12);
40224
+ const requestId = input.toolUseId || (0, import_node_crypto5.randomUUID)().slice(0, 12);
39940
40225
  state.inflightTools.set(requestId, {
39941
40226
  tool: toolName,
39942
40227
  startedAt: hookStart,
39943
40228
  requestId
39944
40229
  });
39945
40230
  const payloadDigest = computePayloadDigest(input.toolInput);
40231
+ const actionReadback = buildActionReadback(toolName, input.toolInput || {});
39946
40232
  const swarm = {
39947
40233
  ...state.swarmContext,
39948
40234
  ...input.agentId && { agent_id: input.agentId },
@@ -39960,7 +40246,10 @@ async function handlePreToolUse(input, state) {
39960
40246
  context: {
39961
40247
  hook_event: "PreToolUse",
39962
40248
  ...input.toolInput || {}
39963
- }
40249
+ },
40250
+ // Also expose the raw tool input under context.input so policies written
40251
+ // against the documented nested shape match on the hook path too.
40252
+ toolInput: input.toolInput || {}
39964
40253
  });
39965
40254
  if (!cedarDecision.allowed) {
39966
40255
  const reason = cedarDecision.reason || "cedar_deny";
@@ -39979,6 +40268,7 @@ async function handlePreToolUse(input, state) {
39979
40268
  swarm: swarm.team_name ? swarm : void 0,
39980
40269
  timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
39981
40270
  payload_digest: payloadDigest,
40271
+ action_readback: actionReadback,
39982
40272
  deny_iteration: denyCount,
39983
40273
  sandbox_state: detectSandboxState(),
39984
40274
  plan_receipt_id: state.activePlanReceiptId || void 0
@@ -39999,10 +40289,30 @@ async function handlePreToolUse(input, state) {
39999
40289
  };
40000
40290
  }
40001
40291
  } catch (err) {
40002
- if (state.verbose) {
40003
- process.stderr.write(`[PROTECT_MCP] Cedar eval error: ${err instanceof Error ? err.message : err}
40004
- `);
40005
- }
40292
+ const hookLatency2 = Date.now() - hookStart;
40293
+ process.stderr.write(
40294
+ `[PROTECT_MCP] Cedar eval threw for "${toolName}", failing closed: ${err instanceof Error ? err.message : err}
40295
+ `
40296
+ );
40297
+ emitDecisionLog(state, {
40298
+ tool: toolName,
40299
+ decision: "deny",
40300
+ reason_code: "cedar_eval_error",
40301
+ request_id: requestId,
40302
+ hook_event: "PreToolUse",
40303
+ swarm: swarm.team_name ? swarm : void 0,
40304
+ timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
40305
+ payload_digest: payloadDigest,
40306
+ action_readback: actionReadback,
40307
+ sandbox_state: detectSandboxState()
40308
+ });
40309
+ return {
40310
+ hookSpecificOutput: {
40311
+ hookEventName: "PreToolUse",
40312
+ permissionDecision: "deny",
40313
+ permissionDecisionReason: `[ScopeBlind] Denied: the policy engine errored while evaluating "${toolName}", so the gate fails closed rather than allow an unverified call. Check the policy set and server logs.`
40314
+ }
40315
+ };
40006
40316
  }
40007
40317
  }
40008
40318
  if (state.jsonPolicy?.policy) {
@@ -40018,6 +40328,7 @@ async function handlePreToolUse(input, state) {
40018
40328
  swarm: swarm.team_name ? swarm : void 0,
40019
40329
  timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
40020
40330
  payload_digest: payloadDigest,
40331
+ action_readback: actionReadback,
40021
40332
  sandbox_state: detectSandboxState()
40022
40333
  });
40023
40334
  return {
@@ -40038,13 +40349,15 @@ async function handlePreToolUse(input, state) {
40038
40349
  hook_event: "PreToolUse",
40039
40350
  swarm: swarm.team_name ? swarm : void 0,
40040
40351
  timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
40352
+ payload_digest: payloadDigest,
40353
+ action_readback: actionReadback,
40041
40354
  sandbox_state: detectSandboxState()
40042
40355
  });
40043
40356
  return {
40044
40357
  hookSpecificOutput: {
40045
40358
  hookEventName: "PreToolUse",
40046
40359
  permissionDecision: "ask",
40047
- permissionDecisionReason: `[ScopeBlind] "${toolName}" requires human approval. Policy: ${state.policyDigest}`
40360
+ permissionDecisionReason: `[ScopeBlind] Approval required for exactly this action: ${actionReadback.summary}. Payload hash: ${actionReadback.payload_hash.slice(0, 16)}\u2026 Policy: ${state.policyDigest}`
40048
40361
  }
40049
40362
  };
40050
40363
  }
@@ -40089,6 +40402,7 @@ async function handlePreToolUse(input, state) {
40089
40402
  swarm: swarm.team_name ? swarm : void 0,
40090
40403
  timing: { hook_latency_ms: hookLatency, started_at: hookStart },
40091
40404
  payload_digest: payloadDigest,
40405
+ action_readback: actionReadback,
40092
40406
  sandbox_state: detectSandboxState(),
40093
40407
  plan_receipt_id: state.activePlanReceiptId || void 0
40094
40408
  });
@@ -40105,7 +40419,7 @@ async function handlePreToolUse(input, state) {
40105
40419
  }
40106
40420
  async function handlePostToolUse(input, state) {
40107
40421
  const toolName = input.toolName || "unknown";
40108
- const requestId = input.toolUseId || (0, import_node_crypto4.randomUUID)().slice(0, 12);
40422
+ const requestId = input.toolUseId || (0, import_node_crypto5.randomUUID)().slice(0, 12);
40109
40423
  const now = Date.now();
40110
40424
  const inflight = state.inflightTools.get(requestId);
40111
40425
  const timing = {
@@ -40117,7 +40431,7 @@ async function handlePostToolUse(input, state) {
40117
40431
  state.inflightTools.delete(requestId);
40118
40432
  }
40119
40433
  const outputDigest = computeOutputDigest(input.toolResult);
40120
- const receiptId = (0, import_node_crypto4.randomUUID)().slice(0, 8);
40434
+ const receiptId = (0, import_node_crypto5.randomUUID)().slice(0, 8);
40121
40435
  const policyName = state.cedarPolicies ? `cedar:${state.policyDigest}` : state.policyDigest;
40122
40436
  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.` : "");
40123
40437
  emitDecisionLog(state, {
@@ -40149,7 +40463,7 @@ function handleSubagentStart(input, state) {
40149
40463
  tool: `subagent:${agentId}`,
40150
40464
  decision: "allow",
40151
40465
  reason_code: "subagent_started",
40152
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40466
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40153
40467
  hook_event: "SubagentStart",
40154
40468
  swarm: {
40155
40469
  ...state.swarmContext,
@@ -40170,7 +40484,7 @@ function handleSubagentStop(input, state) {
40170
40484
  tool: `subagent:${agentId}`,
40171
40485
  decision: "allow",
40172
40486
  reason_code: "subagent_stopped",
40173
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40487
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40174
40488
  hook_event: "SubagentStop",
40175
40489
  swarm: {
40176
40490
  ...state.swarmContext,
@@ -40185,7 +40499,7 @@ function handleTaskCreated(input, state) {
40185
40499
  tool: `task:${input.taskId || "unknown"}`,
40186
40500
  decision: "allow",
40187
40501
  reason_code: "task_created",
40188
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40502
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40189
40503
  hook_event: "TaskCreated",
40190
40504
  swarm: {
40191
40505
  ...state.swarmContext,
@@ -40199,7 +40513,7 @@ function handleTaskCompleted(input, state) {
40199
40513
  tool: `task:${input.taskId || "unknown"}`,
40200
40514
  decision: "allow",
40201
40515
  reason_code: "task_completed",
40202
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40516
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40203
40517
  hook_event: "TaskCompleted",
40204
40518
  swarm: state.swarmContext
40205
40519
  });
@@ -40210,7 +40524,7 @@ function handleSessionStart(input, state) {
40210
40524
  tool: "session",
40211
40525
  decision: "allow",
40212
40526
  reason_code: "session_started",
40213
- request_id: input.sessionId || (0, import_node_crypto4.randomUUID)().slice(0, 12),
40527
+ request_id: input.sessionId || (0, import_node_crypto5.randomUUID)().slice(0, 12),
40214
40528
  hook_event: "SessionStart",
40215
40529
  swarm: state.swarmContext,
40216
40530
  sandbox_state: detectSandboxState()
@@ -40233,7 +40547,7 @@ function handleSessionEnd(input, state) {
40233
40547
  tool: "session",
40234
40548
  decision: "allow",
40235
40549
  reason_code: "session_ended",
40236
- request_id: input.sessionId || (0, import_node_crypto4.randomUUID)().slice(0, 12),
40550
+ request_id: input.sessionId || (0, import_node_crypto5.randomUUID)().slice(0, 12),
40237
40551
  hook_event: "SessionEnd",
40238
40552
  swarm: state.swarmContext
40239
40553
  });
@@ -40244,7 +40558,7 @@ function handleTeammateIdle(input, state) {
40244
40558
  tool: `teammate:${input.agentId || "unknown"}`,
40245
40559
  decision: "allow",
40246
40560
  reason_code: "teammate_idle",
40247
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40561
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40248
40562
  hook_event: "TeammateIdle",
40249
40563
  swarm: {
40250
40564
  ...state.swarmContext,
@@ -40272,7 +40586,7 @@ function handleConfigChange(input, state) {
40272
40586
  tool: "config",
40273
40587
  decision: "deny",
40274
40588
  reason_code: "config_tamper_detected",
40275
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40589
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40276
40590
  hook_event: "ConfigChange",
40277
40591
  swarm: state.swarmContext
40278
40592
  });
@@ -40281,7 +40595,7 @@ function handleConfigChange(input, state) {
40281
40595
  tool: "config",
40282
40596
  decision: "allow",
40283
40597
  reason_code: "config_changed",
40284
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40598
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40285
40599
  hook_event: "ConfigChange"
40286
40600
  });
40287
40601
  }
@@ -40303,7 +40617,7 @@ function handleStop(input, state) {
40303
40617
  tool: "session",
40304
40618
  decision: "allow",
40305
40619
  reason_code: "agent_stopped",
40306
- request_id: (0, import_node_crypto4.randomUUID)().slice(0, 12),
40620
+ request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
40307
40621
  hook_event: "Stop",
40308
40622
  swarm: state.swarmContext
40309
40623
  });
@@ -40311,8 +40625,8 @@ function handleStop(input, state) {
40311
40625
  }
40312
40626
  function emitDecisionLog(state, entry) {
40313
40627
  const mode = state.enforce ? "enforce" : "shadow";
40314
- const otelTraceId = (0, import_node_crypto4.randomBytes)(16).toString("hex");
40315
- const otelSpanId = (0, import_node_crypto4.randomBytes)(8).toString("hex");
40628
+ const otelTraceId = (0, import_node_crypto5.randomBytes)(16).toString("hex");
40629
+ const otelSpanId = (0, import_node_crypto5.randomBytes)(8).toString("hex");
40316
40630
  const log = {
40317
40631
  v: 2,
40318
40632
  tool: entry.tool || "unknown",
@@ -40320,7 +40634,7 @@ function emitDecisionLog(state, entry) {
40320
40634
  reason_code: entry.reason_code || "default_allow",
40321
40635
  policy_digest: state.policyDigest,
40322
40636
  policy_engine: state.cedarPolicies ? "cedar" : "built-in",
40323
- request_id: entry.request_id || (0, import_node_crypto4.randomUUID)().slice(0, 12),
40637
+ request_id: entry.request_id || (0, import_node_crypto5.randomUUID)().slice(0, 12),
40324
40638
  timestamp: Date.now(),
40325
40639
  mode,
40326
40640
  otel_trace_id: otelTraceId,
@@ -41299,8 +41613,515 @@ function generateSchemaStub(namespace = "ScopeBlind") {
41299
41613
  ].join("\n");
41300
41614
  }
41301
41615
 
41616
+ // src/policy-packs.ts
41617
+ var header = (id, description) => `// ScopeBlind protect-mcp policy pack: ${id}
41618
+ // ${description}
41619
+ // Start in shadow mode, review receipts, then run with --enforce.
41620
+
41621
+ `;
41622
+ var defaultPermit = `
41623
+ // Default posture: allow non-matching calls so teams can start in shadow mode.
41624
+ // Tighten this after reviewing your local action dashboard.
41625
+ permit(principal, action == Action::"MCP::Tool::call", resource);
41626
+ `;
41627
+ var filesystemSafe = `${header("filesystem-safe", "Block common destructive filesystem and secret-file access patterns.")}// Destructive file tools are never safe as an unattended default.
41628
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"delete_file");
41629
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"remove_file");
41630
+
41631
+ // Secret-like reads by path.
41632
+ forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41633
+ context has "input" && context.input has "path" && (
41634
+ context.input.path like "*/.env*" ||
41635
+ context.input.path like "*/id_rsa*" ||
41636
+ context.input.path like "*/.ssh/*" ||
41637
+ context.input.path like "*secret*" ||
41638
+ context.input.path like "*credential*"
41639
+ )
41640
+ };
41641
+
41642
+ // Dangerous shell operations that mutate or destroy local state.
41643
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
41644
+ context has "command" && (
41645
+ context.command like "*rm -rf*" ||
41646
+ context.command like "*mkfs*" ||
41647
+ context.command like "*dd if=*" ||
41648
+ context.command like "*chmod -R 777*" ||
41649
+ context.command like "*chown -R*"
41650
+ )
41651
+ };
41652
+ ${defaultPermit}`;
41653
+ 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 {
41654
+ context has "command" && (
41655
+ context.command like "*git push --force*" ||
41656
+ context.command like "*git push -f*" ||
41657
+ context.command like "*git reset --hard*" ||
41658
+ context.command like "*git clean -fd*" ||
41659
+ context.command like "*git checkout --*" ||
41660
+ context.command like "*git branch -D*" ||
41661
+ context.command like "*gh repo delete*"
41662
+ )
41663
+ };
41664
+ ${defaultPermit}`;
41665
+ var emailSafe = `${header("email-safe", "Permit drafting but block unattended external sends.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"mail.send");
41666
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"email.send");
41667
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"send_email");
41668
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"gmail.send");
41669
+
41670
+ // Shell fallbacks that send mail are blocked too.
41671
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
41672
+ context has "command" && (
41673
+ context.command like "*sendmail*" ||
41674
+ context.command like "*mailx*" ||
41675
+ context.command like "*smtp*"
41676
+ )
41677
+ };
41678
+ ${defaultPermit}`;
41679
+ var databaseSafe = `${header("database-safe", "Allow reads, block write/admin SQL unless explicitly approved elsewhere.")}forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41680
+ context has "input" && context.input has "query" && (
41681
+ context.input.query like "*DROP *" ||
41682
+ context.input.query like "*TRUNCATE *" ||
41683
+ context.input.query like "*DELETE *" ||
41684
+ context.input.query like "*UPDATE *" ||
41685
+ context.input.query like "*INSERT *" ||
41686
+ context.input.query like "*ALTER *" ||
41687
+ context.input.query like "*GRANT *" ||
41688
+ context.input.query like "*REVOKE *"
41689
+ )
41690
+ };
41691
+ ${defaultPermit}`;
41692
+ 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 {
41693
+ context has "command" && (
41694
+ context.command like "*terraform destroy*" ||
41695
+ context.command like "*terraform apply*" ||
41696
+ context.command like "*pulumi up*" ||
41697
+ context.command like "*pulumi destroy*" ||
41698
+ context.command like "*aws ec2 run-instances*" ||
41699
+ context.command like "*aws rds create*" ||
41700
+ context.command like "*gcloud compute instances create*" ||
41701
+ context.command like "*az vm create*" ||
41702
+ context.command like "*kubectl delete*"
41703
+ )
41704
+ };
41705
+ ${defaultPermit}`;
41706
+ 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 {
41707
+ context has "input" && context.input has "path" && (
41708
+ context.input.path like "*/.env*" ||
41709
+ context.input.path like "*/.aws/credentials*" ||
41710
+ context.input.path like "*/.npmrc*" ||
41711
+ context.input.path like "*/.netrc*" ||
41712
+ context.input.path like "*/id_rsa*" ||
41713
+ context.input.path like "*secret*" ||
41714
+ context.input.path like "*token*"
41715
+ )
41716
+ };
41717
+
41718
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
41719
+ context has "command" && (
41720
+ context.command like "*printenv*" ||
41721
+ context.command like "*env |*" ||
41722
+ context.command like "*security find-generic-password*" ||
41723
+ context.command like "*aws secretsmanager get-secret-value*" ||
41724
+ context.command like "*gcloud secrets versions access*" ||
41725
+ context.command like "*op read*"
41726
+ )
41727
+ };
41728
+ ${defaultPermit}`;
41729
+ 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 {
41730
+ context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
41731
+ };
41732
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"booking.execute") when {
41733
+ context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
41734
+ };
41735
+ forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"booking.ticket") when {
41736
+ context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
41737
+ };
41738
+
41739
+ // Default example caps: single-name > 10%, gross > 200%, net > 100%.
41740
+ forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41741
+ context has "input" && context.input has "post_trade_weight_bps" && context.input.post_trade_weight_bps > 1000
41742
+ };
41743
+ forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41744
+ context has "input" && context.input has "post_trade_gross_exposure_bps" && context.input.post_trade_gross_exposure_bps > 20000
41745
+ };
41746
+ forbid(principal, action == Action::"MCP::Tool::call", resource) when {
41747
+ context has "input" && context.input has "post_trade_net_exposure_bps" && context.input.post_trade_net_exposure_bps > 10000
41748
+ };
41749
+ ${defaultPermit}`;
41750
+ var POLICY_PACKS = [
41751
+ {
41752
+ id: "filesystem-safe",
41753
+ name: "Filesystem Safe",
41754
+ description: "Blocks destructive filesystem calls and secret-like path reads.",
41755
+ recommendedMode: "shadow-first",
41756
+ files: [{ path: "filesystem-safe.cedar", contents: filesystemSafe }]
41757
+ },
41758
+ {
41759
+ id: "git-safe",
41760
+ name: "Git Safe",
41761
+ description: "Blocks force pushes, hard resets, destructive cleanup, and repo deletion.",
41762
+ recommendedMode: "shadow-first",
41763
+ files: [{ path: "git-safe.cedar", contents: gitSafe }]
41764
+ },
41765
+ {
41766
+ id: "email-safe",
41767
+ name: "Email Safe",
41768
+ description: "Allows drafting workflows while blocking unattended sends.",
41769
+ recommendedMode: "shadow-first",
41770
+ files: [{ path: "email-safe.cedar", contents: emailSafe }]
41771
+ },
41772
+ {
41773
+ id: "database-safe",
41774
+ name: "Database Safe",
41775
+ description: "Allows read-oriented DB tools while blocking mutating/admin SQL.",
41776
+ recommendedMode: "shadow-first",
41777
+ files: [{ path: "database-safe.cedar", contents: databaseSafe }]
41778
+ },
41779
+ {
41780
+ id: "cloud-spend-safe",
41781
+ name: "Cloud Spend Safe",
41782
+ description: "Blocks obvious cloud spend creation and infrastructure destruction.",
41783
+ recommendedMode: "shadow-first",
41784
+ files: [{ path: "cloud-spend-safe.cedar", contents: cloudSpendSafe }]
41785
+ },
41786
+ {
41787
+ id: "secrets-safe",
41788
+ name: "Secrets Safe",
41789
+ description: "Blocks common file, env, shell, and cloud secret exfiltration paths.",
41790
+ recommendedMode: "enforce-ready",
41791
+ files: [{ path: "secrets-safe.cedar", contents: secretsSafe }]
41792
+ },
41793
+ {
41794
+ id: "finance-mandate-safe",
41795
+ name: "Finance Mandate Safe",
41796
+ description: "Blocks restricted-list and concentration breaches in booking flows.",
41797
+ recommendedMode: "shadow-first",
41798
+ files: [{ path: "finance-mandate-safe.cedar", contents: financeMandateSafe }]
41799
+ }
41800
+ ];
41801
+ function getPolicyPack(id) {
41802
+ return POLICY_PACKS.find((pack) => pack.id === id);
41803
+ }
41804
+ function policyPackIds() {
41805
+ return POLICY_PACKS.map((pack) => pack.id);
41806
+ }
41807
+
41808
+ // src/connector-pilots.ts
41809
+ var import_node_fs10 = require("fs");
41810
+ var import_node_path6 = require("path");
41811
+ var defaultPermit2 = `
41812
+ // Default posture: observe all non-matching tools so the connector can be piloted in shadow mode.
41813
+ permit(principal, action == Action::"MCP::Tool::call", resource);
41814
+ `;
41815
+ var CONNECTOR_PILOTS = [
41816
+ {
41817
+ id: "github",
41818
+ category: "code",
41819
+ name: "GitHub pull-request control",
41820
+ status: "usable-pilot",
41821
+ description: "Controls GitHub REST/MCP calls for issue, PR, branch, and workflow actions.",
41822
+ value: "Useful when agents already have repo access through GitHub MCP, gh, or a GitHub-backed tool server.",
41823
+ env: [
41824
+ { name: "GITHUB_TOKEN", required: true, description: "Fine-grained token scoped to the pilot repo." },
41825
+ { name: "GITHUB_REPOSITORY", required: true, description: "owner/repo target for the pilot." }
41826
+ ],
41827
+ tools: ["github.rest.request", "github.issue.create", "github.pull_request.merge", "github.workflow.dispatch"],
41828
+ actions: [
41829
+ { name: "Read repo metadata", tool: "github.rest.request", risk: "low", mode: "observe", description: "GET-only repository and PR inspection." },
41830
+ { name: "Create issue or comment", tool: "github.issue.create", risk: "medium", mode: "require_approval", description: "External write to the system of record." },
41831
+ { name: "Merge PR / dispatch workflow", tool: "github.pull_request.merge", risk: "high", mode: "require_approval", description: "Code-changing or CI-triggering action." }
41832
+ ],
41833
+ setup: [
41834
+ "Create a fine-grained GitHub token for one repository.",
41835
+ "Set GITHUB_TOKEN and GITHUB_REPOSITORY.",
41836
+ "Run the agent through protect-mcp and review GitHub tool calls in the dashboard."
41837
+ ],
41838
+ config: {
41839
+ type: "scopeblind.connector_pilot.v1",
41840
+ provider: "github",
41841
+ target_env: ["GITHUB_TOKEN", "GITHUB_REPOSITORY"],
41842
+ safe_read_probe: "GET /repos/{GITHUB_REPOSITORY}",
41843
+ controlled_tools: ["github.rest.request", "github.issue.create", "github.pull_request.merge", "github.workflow.dispatch"],
41844
+ approval_required_for: ["POST", "PATCH", "PUT", "DELETE", "merge", "workflow_dispatch"],
41845
+ receipt_fields: ["method", "path", "repo", "actor", "payload_hash", "approval_reason"]
41846
+ },
41847
+ cedar: `${defaultPermit2}
41848
+ // GitHub pilot: reads are observed; writes and merges need exact-action approval.
41849
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41850
+ when { context.tool == "github.pull_request.merge" };
41851
+
41852
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41853
+ when { context.tool == "github.workflow.dispatch" && !context.approved };
41854
+
41855
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41856
+ when { context.tool == "github.issue.create" && !context.approved };
41857
+ `
41858
+ },
41859
+ {
41860
+ id: "email-gmail",
41861
+ category: "communications",
41862
+ name: "Gmail self-send / draft approval",
41863
+ status: "usable-pilot",
41864
+ description: "Uses the existing Gmail OAuth connector path and restricts send mode to email.self for the first production pilot.",
41865
+ value: "Makes external communications reviewable before an agent can send mail.",
41866
+ env: [
41867
+ { name: "GOOGLE_CLIENT_ID", required: true, description: "OAuth client for Gmail." },
41868
+ { name: "GOOGLE_CLIENT_SECRET", required: true, description: "OAuth client secret." },
41869
+ { name: "CONNECTOR_TOKEN_KEY", required: true, description: "AES-GCM key material for sealed connector tokens." }
41870
+ ],
41871
+ tools: ["gmail.draft.create", "gmail.send.email_self", "email.send"],
41872
+ actions: [
41873
+ { name: "Create draft", tool: "gmail.draft.create", risk: "medium", mode: "require_approval", description: "Draft content can leak sensitive information." },
41874
+ { name: "Self-send test", tool: "gmail.send.email_self", risk: "medium", mode: "require_approval", description: "First release allows only sending to the account owner." },
41875
+ { name: "External send", tool: "email.send", risk: "high", mode: "deny", description: "Direct external send stays blocked until a customer-specific allowlist exists." }
41876
+ ],
41877
+ setup: [
41878
+ "Configure Google OAuth redirect /fn/connectors/gmail/callback.",
41879
+ "Connect Gmail through the hosted console or local connector flow.",
41880
+ "Keep send mode to email.self until the customer approves recipient allowlists."
41881
+ ],
41882
+ config: {
41883
+ type: "scopeblind.connector_pilot.v1",
41884
+ provider: "gmail",
41885
+ hosted_functions: ["/fn/connectors/gmail/start", "/fn/connectors/gmail/callback", "/fn/connectors/gmail/send", "/fn/connectors/gmail/status"],
41886
+ first_release_scope: "email.self",
41887
+ denied_until_configured: ["email.send.external", "email.bulk_send"],
41888
+ receipt_fields: ["to_hash", "subject_hash", "body_hash", "approval_reason", "gmail_message_id"]
41889
+ },
41890
+ cedar: `${defaultPermit2}
41891
+ // Email pilot: no direct external send. Draft/self-send require exact approval.
41892
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41893
+ when { context.tool == "email.send" };
41894
+
41895
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41896
+ when { context.tool == "gmail.draft.create" && !context.approved };
41897
+
41898
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41899
+ when { context.tool == "gmail.send.email_self" && !context.approved };
41900
+ `
41901
+ },
41902
+ {
41903
+ id: "filesystem-git",
41904
+ category: "local-computer",
41905
+ name: "Filesystem and Git control",
41906
+ status: "usable-pilot",
41907
+ description: "Controls reads, writes, shell commands, and Git mutation in the local project.",
41908
+ value: "Immediately useful with Claude Code, Codex, Cursor, and any agent that edits files or runs shell commands.",
41909
+ env: [],
41910
+ tools: ["Read", "Write", "Edit", "MultiEdit", "Bash", "git.commit", "git.push"],
41911
+ actions: [
41912
+ { name: "Read files", tool: "Read", risk: "low", mode: "observe", description: "Observe file reads for audit context." },
41913
+ { name: "Write/edit files", tool: "Write", risk: "medium", mode: "require_approval", description: "Require approval for sensitive paths or broad rewrites." },
41914
+ { name: "Git push/reset", tool: "Bash", risk: "high", mode: "require_approval", description: "Commands that publish, reset, or delete require exact-action approval." }
41915
+ ],
41916
+ setup: [
41917
+ "Run protect-mcp init-hooks in the project.",
41918
+ "Install filesystem-safe and Git-safe policy packs.",
41919
+ "Review the dashboard before turning on enforce mode."
41920
+ ],
41921
+ config: {
41922
+ type: "scopeblind.connector_pilot.v1",
41923
+ provider: "filesystem-git",
41924
+ local_only: true,
41925
+ protected_paths: [".env", ".ssh", "keys/", "secrets/", "node_modules/"],
41926
+ dangerous_command_patterns: ["rm -rf", "git push", "git reset --hard", "curl | sh", "chmod 777"],
41927
+ receipt_fields: ["tool", "path_hash", "command_hash", "diff_hash", "approval_reason"]
41928
+ },
41929
+ cedar: `${defaultPermit2}
41930
+ // Filesystem/Git pilot: dangerous shell and protected-path writes need approval.
41931
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41932
+ when { context.tool == "Bash" && context.command_pattern.contains("git reset --hard") && !context.approved };
41933
+
41934
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41935
+ when { context.tool == "Bash" && context.command_pattern.contains("git push") && !context.approved };
41936
+
41937
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41938
+ when { ["Write", "Edit", "MultiEdit"].contains(context.tool) && context.path.contains(".env") && !context.approved };
41939
+ `
41940
+ },
41941
+ {
41942
+ id: "slack-teams",
41943
+ category: "communications",
41944
+ name: "Slack or Teams outbound approval",
41945
+ status: "usable-pilot",
41946
+ description: "Controls messages to Slack channels or Microsoft Teams webhooks.",
41947
+ value: "Makes high-impact internal broadcasts and client channels approval-gated.",
41948
+ env: [
41949
+ { name: "SLACK_BOT_TOKEN", required: false, description: "Slack bot token for chat.postMessage pilots." },
41950
+ { name: "SLACK_CHANNEL_ID", required: false, description: "Default Slack channel for the pilot." },
41951
+ { name: "TEAMS_WEBHOOK_URL", required: false, description: "Teams incoming webhook URL if Teams is preferred." }
41952
+ ],
41953
+ tools: ["slack.chat.postMessage", "slack.files.upload", "teams.webhook.post"],
41954
+ actions: [
41955
+ { name: "Post internal message", tool: "slack.chat.postMessage", risk: "medium", mode: "require_approval", description: "Message text and channel are read back before send." },
41956
+ { name: "Upload file", tool: "slack.files.upload", risk: "high", mode: "require_approval", description: "Files can leak customer data and need explicit approval." },
41957
+ { name: "Teams webhook post", tool: "teams.webhook.post", risk: "medium", mode: "require_approval", description: "Webhook destination and payload hash are receipted." }
41958
+ ],
41959
+ setup: [
41960
+ "Choose Slack or Teams for the first pilot, not both.",
41961
+ "Set the relevant token/webhook environment variables.",
41962
+ "Start with a private test channel and exact-action approval for every send."
41963
+ ],
41964
+ config: {
41965
+ type: "scopeblind.connector_pilot.v1",
41966
+ provider: "slack-or-teams",
41967
+ supported_modes: ["slack.chat.postMessage", "teams.webhook.post"],
41968
+ require_channel_allowlist: true,
41969
+ receipt_fields: ["channel_hash", "message_hash", "file_hash", "approval_reason", "provider_message_id"]
41970
+ },
41971
+ cedar: `${defaultPermit2}
41972
+ // Slack/Teams pilot: all outbound posts and uploads require approval by default.
41973
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
41974
+ when { ["slack.chat.postMessage", "slack.files.upload", "teams.webhook.post"].contains(context.tool) && !context.approved };
41975
+ `
41976
+ },
41977
+ {
41978
+ id: "finance-pms",
41979
+ category: "finance",
41980
+ name: "Finance PMS mock-to-real adapter",
41981
+ status: "usable-pilot",
41982
+ description: "Stages orders into a PMS adapter contract, with mock mode locally and real mode through PMS_ADAPTER_URL.",
41983
+ value: "Gives hedge funds the controlled booking path: parse, mandate-check, approve, book, corroborate, receipt.",
41984
+ env: [
41985
+ { name: "PMS_ADAPTER_URL", required: false, description: "Customer-owned adapter endpoint. Omit for local mock mode." },
41986
+ { name: "PMS_ADAPTER_TOKEN", required: false, description: "Bearer token for the customer-owned PMS adapter." }
41987
+ ],
41988
+ tools: ["pms.order.stage", "pms.order.book", "pms.order.cancel", "pms.reconcile"],
41989
+ actions: [
41990
+ { name: "Stage order", tool: "pms.order.stage", risk: "medium", mode: "require_approval", description: "Creates a booking ticket but does not execute." },
41991
+ { name: "Book order", tool: "pms.order.book", risk: "high", mode: "require_approval", description: "Must pass mandate checks and human readback." },
41992
+ { name: "Cancel/order correction", tool: "pms.order.cancel", risk: "high", mode: "require_approval", description: "Mutates book state and requires approval." }
41993
+ ],
41994
+ setup: [
41995
+ "Run local mock mode first with the Legate finance pilot pack.",
41996
+ "Point PMS_ADAPTER_URL at a customer-owned bridge when ready.",
41997
+ "Require mandate checks and exact-action approval before pms.order.book."
41998
+ ],
41999
+ config: {
42000
+ type: "scopeblind.connector_pilot.v1",
42001
+ provider: "finance-pms",
42002
+ mode: "mock-first",
42003
+ adapter_contract: {
42004
+ stage: "POST /orders/stage",
42005
+ book: "POST /orders/book",
42006
+ cancel: "POST /orders/{client_order_id}/cancel",
42007
+ reconcile: "GET /orders/{client_order_id}"
42008
+ },
42009
+ receipt_fields: ["client_order_id", "side", "symbol_hash", "qty", "price", "mandate_digest", "approval_reason", "external_confirmation_hash"]
42010
+ },
42011
+ cedar: `${defaultPermit2}
42012
+ // Finance/PMS pilot: booking actions require mandate pass and exact approval.
42013
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42014
+ when { ["pms.order.stage", "pms.order.book", "pms.order.cancel"].contains(context.tool) && !context.approved };
42015
+
42016
+ forbid(principal, action == Action::"MCP::Tool::call", resource)
42017
+ when { context.tool == "pms.order.book" && context.mandate_passed != true };
42018
+ `
42019
+ }
42020
+ ];
42021
+ function connectorPilotIds() {
42022
+ return CONNECTOR_PILOTS.map((pilot) => pilot.id);
42023
+ }
42024
+ function getConnectorPilot(id) {
42025
+ return CONNECTOR_PILOTS.find((pilot) => pilot.id === id);
42026
+ }
42027
+ function connectorDirectory(dir) {
42028
+ return (0, import_node_path6.join)(dir, ".protect-mcp", "connectors");
42029
+ }
42030
+ function writeConnectorPilots(opts) {
42031
+ const directory = connectorDirectory(opts.dir);
42032
+ (0, import_node_fs10.mkdirSync)(directory, { recursive: true });
42033
+ const selected = opts.ids && opts.ids.length > 0 && !opts.ids.includes("all") ? opts.ids.map((id) => {
42034
+ const pilot = getConnectorPilot(id);
42035
+ if (!pilot) throw new Error(`Unknown connector pilot: ${id}`);
42036
+ return pilot;
42037
+ }) : CONNECTOR_PILOTS;
42038
+ const written = [];
42039
+ for (const pilot of selected) {
42040
+ const configPath = (0, import_node_path6.join)(directory, `${pilot.id}.json`);
42041
+ const policyPath = (0, import_node_path6.join)(directory, `${pilot.id}.cedar`);
42042
+ if (!opts.force && ((0, import_node_fs10.existsSync)(configPath) || (0, import_node_fs10.existsSync)(policyPath))) {
42043
+ throw new Error(`Refusing to overwrite ${pilot.id}. Re-run with --force if intentional.`);
42044
+ }
42045
+ (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");
42046
+ (0, import_node_fs10.writeFileSync)(policyPath, pilot.cedar.endsWith("\n") ? pilot.cedar : `${pilot.cedar}
42047
+ `);
42048
+ written.push(configPath, policyPath);
42049
+ }
42050
+ (0, import_node_fs10.writeFileSync)((0, import_node_path6.join)(directory, "README.md"), renderConnectorReadme(selected));
42051
+ written.push((0, import_node_path6.join)(directory, "README.md"));
42052
+ return { written, pilots: selected, directory };
42053
+ }
42054
+ function readInstalledConnectorPilots(dir) {
42055
+ const directory = connectorDirectory(dir);
42056
+ if (!(0, import_node_fs10.existsSync)(directory)) return [];
42057
+ return (0, import_node_fs10.readdirSync)(directory).filter((name) => name.endsWith(".json")).map((name) => {
42058
+ const configPath = (0, import_node_path6.join)(directory, name);
42059
+ try {
42060
+ const parsed = JSON.parse((0, import_node_fs10.readFileSync)(configPath, "utf-8"));
42061
+ const id = String(parsed.id || name.replace(/\.json$/, ""));
42062
+ const pilot = getConnectorPilot(id);
42063
+ return {
42064
+ id,
42065
+ name: String(parsed.name || pilot?.name || id),
42066
+ category: String(parsed.category || pilot?.category || "unknown"),
42067
+ status: String(parsed.status || parsed.type || "installed"),
42068
+ config_path: configPath,
42069
+ policy_path: (0, import_node_path6.join)(directory, `${id}.cedar`)
42070
+ };
42071
+ } catch {
42072
+ return null;
42073
+ }
42074
+ }).filter(Boolean);
42075
+ }
42076
+ function connectorDoctor(dir, env = process.env) {
42077
+ const installed = new Set(readInstalledConnectorPilots(dir).map((pilot) => pilot.id));
42078
+ return CONNECTOR_PILOTS.map((pilot) => {
42079
+ const envRows = pilot.env.map((item) => ({
42080
+ name: item.name,
42081
+ required: item.required,
42082
+ present: Boolean(env[item.name]),
42083
+ description: item.description
42084
+ }));
42085
+ const missingRequired = envRows.filter((item) => item.required && !item.present).map((item) => item.name);
42086
+ const optionalPresent = envRows.filter((item) => !item.required && item.present).map((item) => item.name);
42087
+ const optionalProviderReady = pilot.id === "slack-teams" ? Boolean(env.SLACK_BOT_TOKEN || env.TEAMS_WEBHOOK_URL) : pilot.id === "finance-pms" ? Boolean(env.PMS_ADAPTER_URL) : false;
42088
+ const mockModeReady = pilot.id === "finance-pms";
42089
+ return {
42090
+ id: pilot.id,
42091
+ name: pilot.name,
42092
+ category: pilot.category,
42093
+ installed: installed.has(pilot.id),
42094
+ usable: missingRequired.length === 0 && (pilot.env.some((item) => item.required) || pilot.env.length === 0 || optionalProviderReady || mockModeReady),
42095
+ mode: pilot.id === "finance-pms" && !env.PMS_ADAPTER_URL ? "mock" : pilot.id === "slack-teams" && !env.SLACK_BOT_TOKEN && !env.TEAMS_WEBHOOK_URL ? "needs_provider_choice" : "configured_or_local",
42096
+ missing_required: missingRequired,
42097
+ optional_present: optionalPresent,
42098
+ tools: pilot.tools,
42099
+ 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}`
42100
+ };
42101
+ });
42102
+ }
42103
+ function renderConnectorReadme(pilots) {
42104
+ return `# protect-mcp connector pilots
42105
+
42106
+ These files make real tool classes visible and controllable without uploading raw prompts or payloads.
42107
+
42108
+ ${pilots.map((pilot) => `## ${pilot.name}
42109
+
42110
+ ${pilot.description}
42111
+
42112
+ Value: ${pilot.value}
42113
+
42114
+ Tools: ${pilot.tools.map((tool) => `\`${tool}\``).join(", ")}
42115
+
42116
+ Setup:
42117
+ ${pilot.setup.map((step) => `- ${step}`).join("\n")}
42118
+ `).join("\n")}
42119
+ Next: run \`npx protect-mcp dashboard --open\` and review tool inventory, policy coverage, approvals, and receipts.
42120
+ `;
42121
+ }
42122
+
41302
42123
  // src/rekor-anchor.ts
41303
- var import_node_crypto5 = require("crypto");
42124
+ var import_node_crypto6 = require("crypto");
41304
42125
  var REKOR_API = "https://rekor.sigstore.dev/api/v1";
41305
42126
  async function anchorToRekor(receiptHash, signature, publicKeyPem) {
41306
42127
  const entry = {
@@ -41369,7 +42190,7 @@ async function verifyRekorAnchor(logIndex, expectedHash) {
41369
42190
  }
41370
42191
  function hashReceipt(receipt) {
41371
42192
  const canonical = JSON.stringify(receipt, Object.keys(receipt).sort());
41372
- return (0, import_node_crypto5.createHash)("sha256").update(canonical).digest("hex");
42193
+ return (0, import_node_crypto6.createHash)("sha256").update(canonical).digest("hex");
41373
42194
  }
41374
42195
  function createLogAnchorField(anchor) {
41375
42196
  return {
@@ -41382,7 +42203,7 @@ function createLogAnchorField(anchor) {
41382
42203
  }
41383
42204
 
41384
42205
  // src/selective-disclosure.ts
41385
- var import_node_crypto6 = require("crypto");
42206
+ var import_node_crypto7 = require("crypto");
41386
42207
  function redactFields(receipt, fieldsToRedact) {
41387
42208
  const redacted = JSON.parse(JSON.stringify(receipt));
41388
42209
  const salts = [];
@@ -41398,7 +42219,7 @@ function redactFields(receipt, fieldsToRedact) {
41398
42219
  if (i === parts.length - 1) {
41399
42220
  if (key in current) {
41400
42221
  const originalValue = current[key];
41401
- const salt = (0, import_node_crypto6.randomBytes)(16).toString("hex");
42222
+ const salt = (0, import_node_crypto7.randomBytes)(16).toString("hex");
41402
42223
  const commitment = computeCommitment(salt, originalValue);
41403
42224
  salts.push({ field: fieldPath, salt, originalValue });
41404
42225
  current[key] = `sha256(salt + ${typeof originalValue === "string" ? "..." : JSON.stringify(originalValue).slice(0, 20) + "..."})`;
@@ -41470,11 +42291,11 @@ function createDisclosurePackage(allSalts, fieldsToDisclose) {
41470
42291
  }
41471
42292
  function computeCommitment(salt, value) {
41472
42293
  const serialized = typeof value === "string" ? value : JSON.stringify(value);
41473
- return (0, import_node_crypto6.createHash)("sha256").update(salt + serialized).digest("hex");
42294
+ return (0, import_node_crypto7.createHash)("sha256").update(salt + serialized).digest("hex");
41474
42295
  }
41475
42296
  function hashObject(obj) {
41476
42297
  const canonical = JSON.stringify(obj, Object.keys(obj).sort());
41477
- return (0, import_node_crypto6.createHash)("sha256").update(canonical).digest("hex");
42298
+ return (0, import_node_crypto7.createHash)("sha256").update(canonical).digest("hex");
41478
42299
  }
41479
42300
 
41480
42301
  // src/huggingface-export.ts
@@ -41615,10 +42436,10 @@ MIT
41615
42436
  }
41616
42437
 
41617
42438
  // src/webauthn-approval.ts
41618
- var import_node_crypto7 = require("crypto");
42439
+ var import_node_crypto8 = require("crypto");
41619
42440
  function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblind.com", timeoutSeconds = 300) {
41620
- const challengeBytes = (0, import_node_crypto7.randomBytes)(32);
41621
- const contextHash = (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify({ requestId, toolName, agentId, timestamp: Date.now() })).digest("hex");
42441
+ const challengeBytes = (0, import_node_crypto8.randomBytes)(32);
42442
+ const contextHash = (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify({ requestId, toolName, agentId, timestamp: Date.now() })).digest("hex");
41622
42443
  return {
41623
42444
  challenge: base64urlEncode(challengeBytes),
41624
42445
  requestId,
@@ -41633,14 +42454,14 @@ function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblin
41633
42454
  function toCredentialRequestOptions(challenge, allowCredentials) {
41634
42455
  return {
41635
42456
  publicKey: {
41636
- challenge: base64urlDecode(challenge.challenge).buffer,
42457
+ challenge: base64urlDecode2(challenge.challenge).buffer,
41637
42458
  rpId: challenge.rpId,
41638
42459
  timeout: challenge.timeoutSeconds * 1e3,
41639
42460
  userVerification: "required",
41640
42461
  // Always require biometric
41641
42462
  ...allowCredentials ? {
41642
42463
  allowCredentials: allowCredentials.map((c) => ({
41643
- id: base64urlDecode(c.id).buffer,
42464
+ id: base64urlDecode2(c.id).buffer,
41644
42465
  type: "public-key"
41645
42466
  }))
41646
42467
  } : {}
@@ -41661,7 +42482,7 @@ function verifyApprovalAssertion(challenge, assertion) {
41661
42482
  approvedAt: (/* @__PURE__ */ new Date()).toISOString()
41662
42483
  };
41663
42484
  }
41664
- const authData = base64urlDecode(assertion.authenticatorData);
42485
+ const authData = base64urlDecode2(assertion.authenticatorData);
41665
42486
  const flags = authData[32];
41666
42487
  const userPresent = !!(flags & 1);
41667
42488
  const userVerified = !!(flags & 4);
@@ -41669,7 +42490,7 @@ function verifyApprovalAssertion(challenge, assertion) {
41669
42490
  const signCount = authData.length >= 37 ? authData[33] << 24 | authData[34] << 16 | authData[35] << 8 | authData[36] : 0;
41670
42491
  let authenticatorType = "unknown";
41671
42492
  try {
41672
- const clientData = JSON.parse(Buffer.from(base64urlDecode(assertion.clientDataJSON)).toString());
42493
+ const clientData = JSON.parse(Buffer.from(base64urlDecode2(assertion.clientDataJSON)).toString());
41673
42494
  if (clientData.type === "webauthn.get") {
41674
42495
  authenticatorType = "platform";
41675
42496
  }
@@ -41698,13 +42519,13 @@ function createApprovalReceiptPayload(challenge, result) {
41698
42519
  context_hash: result.contextHash,
41699
42520
  approved_at: result.approvedAt,
41700
42521
  // Hash the credential ID for privacy — don't store the raw ID
41701
- credential_id_hash: (0, import_node_crypto7.createHash)("sha256").update(result.credentialId).digest("hex").slice(0, 16)
42522
+ credential_id_hash: (0, import_node_crypto8.createHash)("sha256").update(result.credentialId).digest("hex").slice(0, 16)
41702
42523
  };
41703
42524
  }
41704
42525
  function base64urlEncode(buffer) {
41705
42526
  return Buffer.from(buffer).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
41706
42527
  }
41707
- function base64urlDecode(str) {
42528
+ function base64urlDecode2(str) {
41708
42529
  const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
41709
42530
  const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
41710
42531
  return new Uint8Array(Buffer.from(padded, "base64"));
@@ -41952,7 +42773,7 @@ function evaluatePolicy(tool, policy) {
41952
42773
  }
41953
42774
 
41954
42775
  // src/evidence-authenticity.ts
41955
- var import_node_crypto8 = require("crypto");
42776
+ var import_node_crypto9 = require("crypto");
41956
42777
  async function createEvidenceAttestation(input) {
41957
42778
  const tlsNotaryAvailable = await isTLSNotaryAvailable();
41958
42779
  if (tlsNotaryAvailable) {
@@ -42012,14 +42833,14 @@ async function verifyEvidenceAttestation(attestation) {
42012
42833
  }
42013
42834
  }
42014
42835
  function hashResponseBody(body) {
42015
- return (0, import_node_crypto8.createHash)("sha256").update(typeof body === "string" ? body : body).digest("hex");
42836
+ return (0, import_node_crypto9.createHash)("sha256").update(typeof body === "string" ? body : body).digest("hex");
42016
42837
  }
42017
42838
  function createAttestationField(attestation) {
42018
42839
  return {
42019
42840
  evidence_authenticity: {
42020
42841
  version: attestation.version,
42021
42842
  method: attestation.method,
42022
- url_hash: (0, import_node_crypto8.createHash)("sha256").update(attestation.url).digest("hex").slice(0, 16),
42843
+ url_hash: (0, import_node_crypto9.createHash)("sha256").update(attestation.url).digest("hex").slice(0, 16),
42023
42844
  response_hash: attestation.responseHash,
42024
42845
  fetched_at: attestation.fetchedAt,
42025
42846
  verified: attestation.verified,
@@ -42050,7 +42871,7 @@ async function createTLSNotaryAttestation(input) {
42050
42871
  }
42051
42872
 
42052
42873
  // src/c2pa-credentials.ts
42053
- var import_node_crypto9 = require("crypto");
42874
+ var import_node_crypto10 = require("crypto");
42054
42875
  function createC2PAManifest(receipts, options) {
42055
42876
  const generator = options.generator || "protect-mcp";
42056
42877
  const version = options.version || "0.3.3";
@@ -42064,7 +42885,7 @@ function createC2PAManifest(receipts, options) {
42064
42885
  (r) => r.payload?.decision === "deny"
42065
42886
  );
42066
42887
  const receiptHashes = receipts.map(
42067
- (r) => (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify(r)).digest("hex")
42888
+ (r) => (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify(r)).digest("hex")
42068
42889
  );
42069
42890
  const merkleRoot2 = computeMerkleRoot(receiptHashes);
42070
42891
  const assertions = [
@@ -42206,7 +43027,7 @@ function computeMerkleRoot(hashes) {
42206
43027
  const left = hashes[i];
42207
43028
  const right = i + 1 < hashes.length ? hashes[i + 1] : left;
42208
43029
  nextLevel.push(
42209
- (0, import_node_crypto9.createHash)("sha256").update(left + right).digest("hex")
43030
+ (0, import_node_crypto10.createHash)("sha256").update(left + right).digest("hex")
42210
43031
  );
42211
43032
  }
42212
43033
  return computeMerkleRoot(nextLevel);
@@ -42259,7 +43080,7 @@ function toManifoldFormat(prediction) {
42259
43080
  }
42260
43081
 
42261
43082
  // src/agent-exchange.ts
42262
- var import_node_crypto10 = require("crypto");
43083
+ var import_node_crypto11 = require("crypto");
42263
43084
  var ReceiptPropagator = class {
42264
43085
  issuer;
42265
43086
  signer;
@@ -42280,7 +43101,7 @@ var ReceiptPropagator = class {
42280
43101
  delegate(delegateId, options) {
42281
43102
  const now = /* @__PURE__ */ new Date();
42282
43103
  const receipt = {
42283
- receipt_id: `del_${(0, import_node_crypto10.randomUUID)().slice(0, 12)}`,
43104
+ receipt_id: `del_${(0, import_node_crypto11.randomUUID)().slice(0, 12)}`,
42284
43105
  receipt_type: "delegation",
42285
43106
  issuer_id: this.issuer,
42286
43107
  event_time: now.toISOString(),
@@ -42337,7 +43158,7 @@ var ReceiptPropagator = class {
42337
43158
  const currentCount = this.delegationCallCounts.get(options.delegation_receipt) || 0;
42338
43159
  this.delegationCallCounts.set(options.delegation_receipt, currentCount + 1);
42339
43160
  const receipt = {
42340
- receipt_id: `act_${(0, import_node_crypto10.randomUUID)().slice(0, 12)}`,
43161
+ receipt_id: `act_${(0, import_node_crypto11.randomUUID)().slice(0, 12)}`,
42341
43162
  receipt_type: "execution",
42342
43163
  issuer_id: this.issuer,
42343
43164
  event_time: (/* @__PURE__ */ new Date()).toISOString(),
@@ -42429,7 +43250,7 @@ function createReceiptChannel(orchestratorId) {
42429
43250
  async withDelegation(delegateId, tools, fn, options) {
42430
43251
  const delegation = propagator.delegate(delegateId, {
42431
43252
  tools,
42432
- scope: options?.scope || `task-${(0, import_node_crypto10.randomUUID)().slice(0, 8)}`,
43253
+ scope: options?.scope || `task-${(0, import_node_crypto11.randomUUID)().slice(0, 8)}`,
42433
43254
  ttl: options?.ttl || 3600,
42434
43255
  maxCalls: options?.maxCalls
42435
43256
  });
@@ -42579,6 +43400,48 @@ var TOOLS = [
42579
43400
  },
42580
43401
  required: ["environment"]
42581
43402
  }
43403
+ },
43404
+ {
43405
+ name: "github_create_pr",
43406
+ description: "Create a GitHub pull request",
43407
+ inputSchema: {
43408
+ type: "object",
43409
+ properties: {
43410
+ repo: { type: "string", description: "Repository name" },
43411
+ branch: { type: "string", description: "Source branch" },
43412
+ title: { type: "string", description: "Pull request title" }
43413
+ },
43414
+ required: ["repo", "branch", "title"]
43415
+ }
43416
+ },
43417
+ {
43418
+ name: "send_email",
43419
+ description: "Send an email",
43420
+ inputSchema: {
43421
+ type: "object",
43422
+ properties: {
43423
+ to: { type: "string", description: "Recipient email address" },
43424
+ subject: { type: "string", description: "Subject line" },
43425
+ body: { type: "string", description: "Message body" }
43426
+ },
43427
+ required: ["to", "subject"]
43428
+ }
43429
+ },
43430
+ {
43431
+ name: "pms_book_fill",
43432
+ description: "Book a fill into the mock portfolio management system",
43433
+ inputSchema: {
43434
+ type: "object",
43435
+ properties: {
43436
+ account: { type: "string", description: "Portfolio or fund account" },
43437
+ symbol: { type: "string", description: "Instrument symbol" },
43438
+ side: { type: "string", enum: ["BUY", "SELL"] },
43439
+ quantity: { type: "number" },
43440
+ price: { type: "number" },
43441
+ strategy: { type: "string" }
43442
+ },
43443
+ required: ["account", "symbol", "side", "quantity", "price"]
43444
+ }
42582
43445
  }
42583
43446
  ];
42584
43447
  function handleRequest(request) {
@@ -42626,6 +43489,15 @@ Contents: Hello from protect-mcp demo server!`;
42626
43489
  case "deploy":
42627
43490
  resultText = `[demo] Deployed to ${args.environment || "staging"}${args.reason ? ` (reason: ${args.reason})` : ""}`;
42628
43491
  break;
43492
+ case "github_create_pr":
43493
+ resultText = `[demo] Created PR in ${args.repo || "scopeblind/demo"} from ${args.branch || "agent/demo"}: ${args.title || "Agent change"}`;
43494
+ break;
43495
+ case "send_email":
43496
+ resultText = `[demo] Drafted email to ${args.to || "pm@example.com"}: ${args.subject || "Agent update"}`;
43497
+ break;
43498
+ case "pms_book_fill":
43499
+ resultText = `[demo] Booked ${args.side || "BUY"} ${args.quantity || 0} ${args.symbol || "AAPL"} @ ${args.price || 0} into ${args.account || "Demo Fund"} (${args.strategy || "Unassigned"})`;
43500
+ break;
42629
43501
  default:
42630
43502
  resultText = `[demo] Unknown tool: ${toolName}`;
42631
43503
  }
@@ -42698,12 +43570,38 @@ function createSandboxServer() {
42698
43570
  { environment: z.enum(["staging", "production"]).describe("Target environment"), reason: z.string().optional().describe("Deployment reason") },
42699
43571
  async (args) => ({ content: [{ type: "text", text: `[demo] Deployed to ${args.environment}` }] })
42700
43572
  );
43573
+ server.tool("github_create_pr", "Create a GitHub pull request", {
43574
+ repo: z.string(),
43575
+ branch: z.string(),
43576
+ title: z.string()
43577
+ }, async (args) => ({
43578
+ content: [{ type: "text", text: `[demo] PR created in ${args.repo} from ${args.branch}: ${args.title}` }]
43579
+ }));
43580
+ server.tool("send_email", "Send an email", {
43581
+ to: z.string(),
43582
+ subject: z.string(),
43583
+ body: z.string().optional()
43584
+ }, async (args) => ({
43585
+ content: [{ type: "text", text: `[demo] Email prepared for ${args.to}: ${args.subject}` }]
43586
+ }));
43587
+ server.tool("pms_book_fill", "Book a fill into a mock PMS", {
43588
+ account: z.string(),
43589
+ symbol: z.string(),
43590
+ side: z.enum(["BUY", "SELL"]),
43591
+ quantity: z.number(),
43592
+ price: z.number(),
43593
+ strategy: z.string().optional()
43594
+ }, async (args) => ({
43595
+ content: [{ type: "text", text: `[demo] Booked ${args.side} ${args.quantity} ${args.symbol} @ ${args.price} into ${args.account}` }]
43596
+ }));
42701
43597
  return server;
42702
43598
  }
42703
43599
  // Annotate the CommonJS export names for ESM import in node:
42704
43600
  0 && (module.exports = {
42705
43601
  BUILTIN_PATTERNS,
43602
+ CONNECTOR_PILOTS,
42706
43603
  ConfidentialGate,
43604
+ POLICY_PACKS,
42707
43605
  ProtectGateway,
42708
43606
  ReceiptPropagator,
42709
43607
  ScopeBlindBridge,
@@ -42713,6 +43611,9 @@ function createSandboxServer() {
42713
43611
  collectSignedReceipts,
42714
43612
  computeCalibration,
42715
43613
  confidentialInference,
43614
+ connectorDirectory,
43615
+ connectorDoctor,
43616
+ connectorPilotIds,
42716
43617
  createApprovalChallenge,
42717
43618
  createApprovalReceiptPayload,
42718
43619
  createAttestationField,
@@ -42724,6 +43625,7 @@ function createSandboxServer() {
42724
43625
  createReceiptChannel,
42725
43626
  createSandbox,
42726
43627
  createSandboxServer,
43628
+ createSelectiveDisclosurePackage,
42727
43629
  destroySandbox,
42728
43630
  discloseField,
42729
43631
  ed25519ToDIDKey,
@@ -42744,6 +43646,8 @@ function createSandboxServer() {
42744
43646
  generateSampleCedarPolicy,
42745
43647
  generateSchemaStub,
42746
43648
  generateVerifyReceiptSkill,
43649
+ getConnectorPilot,
43650
+ getPolicyPack,
42747
43651
  getScopeBlindBridge,
42748
43652
  getSignerInfo,
42749
43653
  getToolPolicy,
@@ -42764,8 +43668,10 @@ function createSandboxServer() {
42764
43668
  parseLogFile,
42765
43669
  parseNotificationConfigFromEnv,
42766
43670
  parseRateLimit,
43671
+ policyPackIds,
42767
43672
  policySetFromSource,
42768
43673
  queryExternalPDP,
43674
+ readInstalledConnectorPilots,
42769
43675
  receiptToVP,
42770
43676
  receiptsToHFRows,
42771
43677
  redactFields,
@@ -42789,7 +43695,9 @@ function createSandboxServer() {
42789
43695
  verifyApprovalAssertion,
42790
43696
  verifyCommitment,
42791
43697
  verifyEvidenceAttestation,
42792
- verifyRekorAnchor
43698
+ verifyRekorAnchor,
43699
+ verifySelectiveDisclosurePackage,
43700
+ writeConnectorPilots
42793
43701
  });
42794
43702
  /**
42795
43703
  * scopeblind-bridge.ts