protect-mcp 0.5.5 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -205,11 +205,40 @@ async function isCedarAvailable() {
205
205
  var import_node_fs2 = require("fs");
206
206
  var signerState = null;
207
207
  var artifactsModule = null;
208
+ var signingConfigured = false;
209
+ var signingInitError = null;
208
210
  async function initSigning(config) {
209
211
  const warnings = [];
212
+ signerState = null;
213
+ artifactsModule = null;
214
+ signingConfigured = Boolean(config && config.enabled !== false);
215
+ signingInitError = null;
210
216
  if (!config || config.enabled === false) {
211
217
  return warnings;
212
218
  }
219
+ if (!config.key_path) {
220
+ signingInitError = "signing enabled but key_path is not configured";
221
+ warnings.push(`signing: ${signingInitError}`);
222
+ return warnings;
223
+ }
224
+ if (!(0, import_node_fs2.existsSync)(config.key_path)) {
225
+ signingInitError = `key file not found at ${config.key_path}`;
226
+ warnings.push(`signing: ${signingInitError} \u2014 run "protect-mcp init" to generate`);
227
+ return warnings;
228
+ }
229
+ let keyData;
230
+ try {
231
+ keyData = JSON.parse((0, import_node_fs2.readFileSync)(config.key_path, "utf-8"));
232
+ if (!keyData.privateKey || !keyData.publicKey) {
233
+ signingInitError = "key file missing privateKey or publicKey fields";
234
+ warnings.push(`signing: ${signingInitError}`);
235
+ return warnings;
236
+ }
237
+ } catch (err) {
238
+ signingInitError = `failed to load key file: ${err instanceof Error ? err.message : err}`;
239
+ warnings.push(`signing: ${signingInitError}`);
240
+ return warnings;
241
+ }
213
242
  try {
214
243
  const moduleName = "@veritasacta/artifacts";
215
244
  artifactsModule = await import(
@@ -217,37 +246,48 @@ async function initSigning(config) {
217
246
  moduleName
218
247
  );
219
248
  } catch {
220
- warnings.push("signing: @veritasacta/artifacts not available \u2014 receipts will be unsigned");
249
+ signingInitError = "@veritasacta/artifacts not available";
250
+ warnings.push(`signing: ${signingInitError} \u2014 enforce mode will fail closed`);
221
251
  return warnings;
222
252
  }
223
- if (config.key_path) {
224
- if (!(0, import_node_fs2.existsSync)(config.key_path)) {
225
- warnings.push(`signing: key file not found at ${config.key_path} \u2014 run "protect-mcp init" to generate`);
226
- return warnings;
227
- }
228
- try {
229
- const keyData = JSON.parse((0, import_node_fs2.readFileSync)(config.key_path, "utf-8"));
230
- if (!keyData.privateKey || !keyData.publicKey) {
231
- warnings.push("signing: key file missing privateKey or publicKey fields");
232
- return warnings;
233
- }
234
- signerState = {
235
- privateKey: keyData.privateKey,
236
- publicKey: keyData.publicKey,
237
- kid: keyData.kid || artifactsModule.computeKid(keyData.publicKey),
238
- issuer: config.issuer || keyData.issuer || "protect-mcp"
239
- };
240
- } catch (err) {
241
- warnings.push(`signing: failed to load key file: ${err instanceof Error ? err.message : err}`);
242
- }
253
+ try {
254
+ signerState = {
255
+ privateKey: keyData.privateKey,
256
+ publicKey: keyData.publicKey,
257
+ kid: keyData.kid || artifactsModule.computeKid(keyData.publicKey),
258
+ issuer: config.issuer || keyData.issuer || "protect-mcp"
259
+ };
260
+ } catch (err) {
261
+ signingInitError = `failed to initialize signer: ${err instanceof Error ? err.message : err}`;
262
+ artifactsModule = null;
263
+ warnings.push(`signing: ${signingInitError} \u2014 enforce mode will fail closed`);
243
264
  }
244
265
  return warnings;
245
266
  }
246
267
  function signDecision(entry) {
268
+ const artifactType = entry.decision === "deny" ? "gateway_restraint" : "decision_receipt";
269
+ if (signingConfigured && signingInitError) {
270
+ return {
271
+ ok: false,
272
+ signed: null,
273
+ artifact_type: artifactType,
274
+ warning: `signing initialization failed: ${signingInitError}`,
275
+ error: signingInitError
276
+ };
277
+ }
278
+ if (signingConfigured && (!signerState || !artifactsModule)) {
279
+ const error = "signing was configured but no signer is ready";
280
+ return {
281
+ ok: false,
282
+ signed: null,
283
+ artifact_type: artifactType,
284
+ warning: error,
285
+ error
286
+ };
287
+ }
247
288
  if (!signerState || !artifactsModule) {
248
- return { signed: null, artifact_type: "none" };
289
+ return { ok: false, signed: null, artifact_type: "none" };
249
290
  }
250
- const artifactType = entry.decision === "deny" ? "gateway_restraint" : "decision_receipt";
251
291
  try {
252
292
  const payload = {
253
293
  tool: entry.tool,
@@ -288,14 +328,18 @@ function signDecision(entry) {
288
328
  }
289
329
  );
290
330
  return {
331
+ ok: true,
291
332
  signed: JSON.stringify(result.artifact),
292
333
  artifact_type: artifactType
293
334
  };
294
335
  } catch (err) {
336
+ const message = err instanceof Error ? err.message : "unknown error";
295
337
  return {
338
+ ok: false,
296
339
  signed: null,
297
340
  artifact_type: artifactType,
298
- warning: `signing failed: ${err instanceof Error ? err.message : "unknown error"}`
341
+ warning: `signing failed: ${message}`,
342
+ error: message
299
343
  };
300
344
  }
301
345
  }
@@ -308,7 +352,7 @@ function getSignerInfo() {
308
352
  };
309
353
  }
310
354
  function isSigningEnabled() {
311
- return signerState !== null && artifactsModule !== null;
355
+ return signingConfigured && signingInitError === null && signerState !== null && artifactsModule !== null;
312
356
  }
313
357
 
314
358
  // src/policy.ts
@@ -418,6 +462,154 @@ var ReceiptBuffer = class {
418
462
  }
419
463
  };
420
464
 
465
+ // src/scopeblind-bridge.ts
466
+ var DEFAULT_BASE = "https://scopeblind.com";
467
+ var FLUSH_INTERVAL_MS = 5e3;
468
+ var BATCH_MAX = 128;
469
+ var BRASS_REFRESH_MARGIN_MS = 5 * 60 * 1e3;
470
+ var ScopeBlindBridge = class {
471
+ token;
472
+ base;
473
+ tenantOverride;
474
+ cachedProof = null;
475
+ queue = [];
476
+ flushTimer = null;
477
+ stats;
478
+ shuttingDown = false;
479
+ constructor(env = process.env) {
480
+ this.token = env.SCOPEBLIND_TOKEN || null;
481
+ this.base = (env.SCOPEBLIND_BASE || DEFAULT_BASE).replace(/\/$/, "");
482
+ this.tenantOverride = env.SCOPEBLIND_TENANT || null;
483
+ this.stats = {
484
+ enabled: Boolean(this.token),
485
+ tenant_slug: this.tenantOverride,
486
+ forwarded_total: 0,
487
+ rejected_total: 0,
488
+ last_flush_at: null,
489
+ last_error: null
490
+ };
491
+ if (this.enabled()) {
492
+ this.flushTimer = setInterval(() => {
493
+ void this.flush();
494
+ }, FLUSH_INTERVAL_MS);
495
+ if (typeof this.flushTimer === "object" && this.flushTimer && "unref" in this.flushTimer) {
496
+ this.flushTimer.unref?.();
497
+ }
498
+ process.on("beforeExit", () => {
499
+ void this.shutdown();
500
+ });
501
+ }
502
+ }
503
+ enabled() {
504
+ return Boolean(this.token);
505
+ }
506
+ /** Push a signed receipt into the queue. Non-blocking. */
507
+ forward(signedReceipt) {
508
+ if (!this.enabled() || this.shuttingDown) return;
509
+ this.queue.push(signedReceipt);
510
+ if (this.queue.length >= BATCH_MAX) void this.flush();
511
+ }
512
+ /** Flush the queue. Safe to call concurrently. */
513
+ async flush() {
514
+ if (!this.enabled() || this.queue.length === 0) return;
515
+ const batch = this.queue.splice(0, BATCH_MAX);
516
+ try {
517
+ const proof = await this.ensureBrassProof();
518
+ const slug = this.tenantOverride || proof?.tenant_id;
519
+ if (!slug) {
520
+ this.queue.unshift(...batch);
521
+ return;
522
+ }
523
+ this.stats.tenant_slug = slug;
524
+ const res = await fetch(`${this.base}/fn/console/${slug}/receipts`, {
525
+ method: "POST",
526
+ headers: {
527
+ "content-type": "application/json",
528
+ authorization: `Bearer ${this.token}`,
529
+ "user-agent": "protect-mcp/scopeblind-bridge"
530
+ },
531
+ body: JSON.stringify({ receipts: batch })
532
+ });
533
+ if (!res.ok) {
534
+ const errBody = await res.text().catch(() => "");
535
+ this.stats.last_error = `HTTP ${res.status} ${errBody.slice(0, 160)}`;
536
+ this.stats.rejected_total += batch.length;
537
+ if (res.status >= 500 && res.status !== 503) {
538
+ this.queue.unshift(...batch);
539
+ }
540
+ return;
541
+ }
542
+ const body = await res.json().catch(() => ({}));
543
+ this.stats.forwarded_total += body?.accepted ?? batch.length;
544
+ this.stats.rejected_total += body?.rejected ?? 0;
545
+ this.stats.last_flush_at = (/* @__PURE__ */ new Date()).toISOString();
546
+ this.stats.last_error = null;
547
+ } catch (err) {
548
+ this.stats.last_error = String(err?.message || err);
549
+ this.queue.unshift(...batch);
550
+ }
551
+ }
552
+ /** Exchange SCOPEBLIND_TOKEN for a BRASS-v2 proof; refresh near expiry. */
553
+ async ensureBrassProof() {
554
+ if (!this.token) return null;
555
+ const now = Date.now();
556
+ if (this.cachedProof && Date.parse(this.cachedProof.expires_at) - now > BRASS_REFRESH_MARGIN_MS) {
557
+ return this.cachedProof;
558
+ }
559
+ try {
560
+ const res = await fetch(`${this.base}/fn/brass/issue`, {
561
+ method: "POST",
562
+ headers: {
563
+ "content-type": "application/json",
564
+ "user-agent": "protect-mcp/scopeblind-bridge"
565
+ },
566
+ body: JSON.stringify({
567
+ token: this.token,
568
+ scope: "protect-mcp-receipt-emit",
569
+ ttl_seconds: 3600
570
+ })
571
+ });
572
+ if (!res.ok) {
573
+ const text = await res.text().catch(() => "");
574
+ this.stats.last_error = `brass-issue: HTTP ${res.status} ${text.slice(0, 160)}`;
575
+ return null;
576
+ }
577
+ const body = await res.json();
578
+ if (!body?.auth_proof) {
579
+ this.stats.last_error = "brass-issue: missing auth_proof in response";
580
+ return null;
581
+ }
582
+ this.cachedProof = body.auth_proof;
583
+ return this.cachedProof;
584
+ } catch (err) {
585
+ this.stats.last_error = `brass-issue: ${err?.message || err}`;
586
+ return null;
587
+ }
588
+ }
589
+ /**
590
+ * Return a snapshot of bridge stats. Useful for `protect-mcp scopeblind status`.
591
+ */
592
+ getStats() {
593
+ return {
594
+ ...this.stats,
595
+ queued: this.queue.length,
596
+ brass_proof_expires_at: this.cachedProof?.expires_at || null
597
+ };
598
+ }
599
+ /** Flush remaining receipts and stop the interval. Called on process exit. */
600
+ async shutdown() {
601
+ if (this.shuttingDown) return;
602
+ this.shuttingDown = true;
603
+ if (this.flushTimer) clearInterval(this.flushTimer);
604
+ if (this.queue.length > 0) await this.flush();
605
+ }
606
+ };
607
+ var singleton = null;
608
+ function getScopeBlindBridge() {
609
+ if (!singleton) singleton = new ScopeBlindBridge();
610
+ return singleton;
611
+ }
612
+
421
613
  // src/hook-server.ts
422
614
  var DEFAULT_PORT = 9377;
423
615
  var LOG_FILE = ".protect-mcp-log.jsonl";
@@ -626,7 +818,7 @@ async function handlePreToolUse(input, state) {
626
818
  const hookLatency = Date.now() - hookStart;
627
819
  const denyKey = `${toolName}:${input.sessionId || "default"}`;
628
820
  state.denyCounter.delete(denyKey);
629
- emitDecisionLog(state, {
821
+ const emit = emitDecisionLog(state, {
630
822
  tool: toolName,
631
823
  decision: "allow",
632
824
  reason_code: state.cedarPolicies ? "cedar_allow" : state.jsonPolicy ? "policy_allow" : "observe_mode",
@@ -638,6 +830,15 @@ async function handlePreToolUse(input, state) {
638
830
  sandbox_state: detectSandboxState(),
639
831
  plan_receipt_id: state.activePlanReceiptId || void 0
640
832
  });
833
+ if (state.enforce && emit.signingFailed) {
834
+ return {
835
+ hookSpecificOutput: {
836
+ hookEventName: "PreToolUse",
837
+ permissionDecision: "deny",
838
+ permissionDecisionReason: `[ScopeBlind] "${toolName}" was blocked because its receipt could not be signed. Failing closed: a governed action that cannot be proven is not allowed.`
839
+ }
840
+ };
841
+ }
641
842
  return {};
642
843
  }
643
844
  async function handlePostToolUse(input, state) {
@@ -885,11 +1086,35 @@ function emitDecisionLog(state, entry) {
885
1086
  } catch {
886
1087
  }
887
1088
  state.receiptBuffer.add(log.request_id, signed.signed);
888
- } else if (signed.warning) {
889
- process.stderr.write(`[PROTECT_MCP] Warning: ${signed.warning}
1089
+ try {
1090
+ const bridge = getScopeBlindBridge();
1091
+ if (bridge.enabled()) {
1092
+ const parsed = typeof signed.signed === "string" ? JSON.parse(signed.signed) : signed.signed;
1093
+ bridge.forward(parsed);
1094
+ }
1095
+ } catch (err) {
1096
+ process.stderr.write(`[PROTECT_MCP] ScopeBlind forward error: ${err instanceof Error ? err.message : err}
1097
+ `);
1098
+ }
1099
+ } else if (signed.error) {
1100
+ const tombstone = JSON.stringify({
1101
+ type: "scopeblind.signing_failure.v1",
1102
+ request_id: log.request_id,
1103
+ tool: log.tool,
1104
+ decision: log.decision,
1105
+ error: signed.error,
1106
+ at: new Date(log.timestamp).toISOString()
1107
+ });
1108
+ try {
1109
+ (0, import_node_fs5.appendFileSync)(state.receiptFilePath, tombstone + "\n");
1110
+ } catch {
1111
+ }
1112
+ process.stderr.write(`[PROTECT_MCP_SIGNING_FAILURE] ${tombstone}
890
1113
  `);
1114
+ return { signingFailed: true };
891
1115
  }
892
1116
  }
1117
+ return { signingFailed: false };
893
1118
  }
894
1119
  async function routeHookEvent(input, state) {
895
1120
  switch (input.hookEventName) {
@@ -1231,3 +1456,33 @@ function normalizeHookInput(raw) {
1231
1456
  0 && (module.exports = {
1232
1457
  startHookServer
1233
1458
  });
1459
+ /**
1460
+ * scopeblind-bridge.ts
1461
+ *
1462
+ * Optional bridge between protect-mcp (local, MIT) and a paid ScopeBlind
1463
+ * tenant. When SCOPEBLIND_TOKEN is set in the environment, every signed
1464
+ * receipt that protect-mcp emits also gets forwarded to the tenant's
1465
+ * dashboard at https://scopeblind.com/console/<slug>.
1466
+ *
1467
+ * Lifecycle:
1468
+ * 1. On first use, exchange SCOPEBLIND_TOKEN for a short-lived BRASS-v2
1469
+ * auth proof from /fn/brass/issue. Cache the proof in memory until
1470
+ * ~5 minutes before expiry, then refresh.
1471
+ * 2. As receipts are emitted by hook-server.ts, push them into an
1472
+ * in-memory batch queue.
1473
+ * 3. Flush the queue every 5s (or when it reaches 128 receipts) by POSTing
1474
+ * to /fn/console/<slug>/receipts with Bearer SCOPEBLIND_TOKEN.
1475
+ *
1476
+ * Failure mode: forward errors NEVER throw upstream. protect-mcp continues
1477
+ * to mint and persist receipts locally regardless of dashboard availability.
1478
+ * The bridge logs failures to stderr (best-effort) and retries on the next
1479
+ * flush.
1480
+ *
1481
+ * Configuration:
1482
+ * SCOPEBLIND_TOKEN Tenant bearer token (from welcome email).
1483
+ * SCOPEBLIND_TENANT Optional slug override. By default we discover
1484
+ * the slug from the BRASS proof's tenant_id.
1485
+ * SCOPEBLIND_BASE Defaults to https://scopeblind.com.
1486
+ *
1487
+ * @license MIT
1488
+ */
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  startHookServer
3
- } from "./chunk-SPHLVRJ2.mjs";
4
- import "./chunk-YTBC72JJ.mjs";
3
+ } from "./chunk-3YCKR72H.mjs";
4
+ import "./chunk-UV53U6D4.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
  export {
7
7
  startHookServer
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ProtectGateway
3
- } from "./chunk-BYYWYSHM.mjs";
4
- import "./chunk-YTBC72JJ.mjs";
3
+ } from "./chunk-PLKRTBDR.mjs";
4
+ import "./chunk-UV53U6D4.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
 
7
7
  // src/http-transport.ts
package/dist/index.d.mts CHANGED
@@ -95,6 +95,29 @@ interface SigningConfig {
95
95
  issuer?: string;
96
96
  /** Whether signing is enabled (default: true when key_path is set) */
97
97
  enabled?: boolean;
98
+ /**
99
+ * Commitment-mode signing.
100
+ *
101
+ * When enabled, listed fields are committed via SHA-256(salt || JCS({name, salt, value}))
102
+ * and the receipt payload carries a single committed_fields_root (Merkle root) instead
103
+ * of the cleartext field values. Per draft-farley-acta-signed-receipts-01 §commitment-mode.
104
+ *
105
+ * The receipt issuer keeps the openings (value + salt per field) for later selective
106
+ * disclosure. A receipt holder can prove a field's value to an auditor without
107
+ * revealing other committed fields.
108
+ *
109
+ * @since 0.6.0
110
+ */
111
+ commitment_mode?: {
112
+ /** Whether commitment-mode signing is active. Default: false. */
113
+ enabled?: boolean;
114
+ /**
115
+ * Names of payload fields to commit.
116
+ * Recommended defaults: tool, scope, payload_digest, swarm.
117
+ * Other fields remain cleartext.
118
+ */
119
+ committed_field_names?: string[];
120
+ };
98
121
  }
99
122
  interface RateLimit {
100
123
  count: number;
@@ -780,9 +803,9 @@ declare function validateCredentials(credentials: Record<string, CredentialConfi
780
803
  * Produces signed v2 artifact receipts for tool call decisions.
781
804
  * Uses @veritasacta/artifacts as a required dependency (Sprint 2+).
782
805
  *
783
- * If signing is configured, every decision produces a signed artifact.
784
- * If signing fails, the receipt is emitted unsigned with signature: null
785
- * and a warning never crashes, never silently drops.
806
+ * If signing is configured, every decision must produce a signed artifact.
807
+ * Initialization and signing failures are returned as explicit errors so the
808
+ * enforce path can deny rather than silently proceeding without evidence.
786
809
  */
787
810
 
788
811
  /**
@@ -804,9 +827,11 @@ declare function initSigning(config: SigningConfig | undefined): Promise<string[
804
827
  * @standard RFC 8032 (Ed25519), RFC 8785 (JCS)
805
828
  */
806
829
  declare function signDecision(entry: DecisionLog): {
830
+ ok: boolean;
807
831
  signed: string | null;
808
832
  artifact_type: string;
809
833
  warning?: string;
834
+ error?: string;
810
835
  };
811
836
  /**
812
837
  * Get the signer's public key info for discovery/verification.
@@ -825,6 +850,131 @@ declare function getSignerInfo(): {
825
850
  */
826
851
  declare function isSigningEnabled(): boolean;
827
852
 
853
+ /**
854
+ * A Merkle inclusion proof for a single leaf.
855
+ *
856
+ * The siblings array lists the sibling hashes encountered while walking
857
+ * from the leaf up to the root. Each sibling is hex-encoded SHA-256.
858
+ * The (index, treeSize) pair determines whether the current node is
859
+ * left or right at each level during verification.
860
+ */
861
+ interface MerkleProof {
862
+ /** Zero-based index of the leaf in the canonically-sorted leaf list. */
863
+ index: number;
864
+ /** Total number of leaves in the tree. */
865
+ treeSize: number;
866
+ /** Sibling hashes from leaf upward, hex-encoded SHA-256 (lowercase). */
867
+ siblings: string[];
868
+ }
869
+
870
+ /**
871
+ * @scopeblind/protect-mcp: Commitment-Mode Signing
872
+ *
873
+ * Produces commitment-mode signed receipts per draft-farley-acta-signed-receipts-01
874
+ * §commitment-mode. Each listed field is independently committed via
875
+ * SHA-256(0x00 || JCS({name, salt, value})), arranged into an RFC 6962-style
876
+ * Merkle tree with explicit one-byte domain separation, and the receipt payload
877
+ * carries a single committed_fields_root field instead of the cleartext values.
878
+ *
879
+ * The receipt holder retains openings (value + salt per field) and can selectively
880
+ * disclose any subset to auditors via Merkle inclusion proofs verifiable by
881
+ * @veritasacta/verify@>=0.6.0.
882
+ *
883
+ * This module sits alongside signing.ts (the legacy @veritasacta/artifacts-based
884
+ * cleartext path) and is invoked when SigningConfig.commitment_mode.enabled is
885
+ * true. The two paths are mutually exclusive on a per-receipt basis.
886
+ *
887
+ * @since 0.6.0
888
+ * @standard draft-farley-acta-signed-receipts-01 §commitment-mode
889
+ * @standard RFC 6962 (Certificate Transparency Merkle tree construction)
890
+ * @standard RFC 8032 (Ed25519)
891
+ * @standard RFC 8785 (JCS)
892
+ */
893
+
894
+ /**
895
+ * The opening information for a single committed field. Held by the
896
+ * receipt issuer; never embedded in the published receipt. Required to
897
+ * later produce a selective-disclosure proof.
898
+ */
899
+ interface CommittedFieldOpening {
900
+ /** Field name (matches one of committed_field_names). */
901
+ name: string;
902
+ /** Cleartext value of the field. */
903
+ value: unknown;
904
+ /** Salt bytes (32 random bytes per field per receipt). */
905
+ salt: Uint8Array;
906
+ /** Zero-based index of the field in the canonically-sorted leaf list. */
907
+ index: number;
908
+ }
909
+ /**
910
+ * The result of signing a decision in commitment mode.
911
+ */
912
+ interface CommittedSignResult {
913
+ /** The signed receipt as a JSON string (canonical wire form). */
914
+ signed: string;
915
+ /** Receipt artifact type, e.g. "decision_receipt_committed_v1". */
916
+ artifact_type: string;
917
+ /**
918
+ * Per-field openings, indexed by field name. The issuer MUST persist
919
+ * these securely if it intends to support selective disclosure later.
920
+ * Storing them is the issuer's responsibility; this library does not
921
+ * write them to disk.
922
+ */
923
+ openings: Record<string, CommittedFieldOpening>;
924
+ /** Lowercase hex SHA-256 of the canonical signed receipt. */
925
+ receipt_hash: string;
926
+ }
927
+ /**
928
+ * A minimal selective-disclosure envelope. Reveal a single committed field
929
+ * to an auditor by supplying its (name, value, salt, proof). The auditor
930
+ * recomputes the leaf hash and walks the proof to confirm it reconstructs
931
+ * the receipt's committed_fields_root.
932
+ *
933
+ * Compatible with @veritasacta/verify@>=0.6.0.
934
+ */
935
+ interface MinimalDisclosure {
936
+ /** The receipt this disclosure targets, by canonical hash. */
937
+ parent_receipt_hash: string;
938
+ /** Disclosed field name. */
939
+ name: string;
940
+ /** Cleartext value of the disclosed field. */
941
+ value: unknown;
942
+ /** Salt as base64url (no padding). */
943
+ salt: string;
944
+ /** Merkle inclusion proof. */
945
+ proof: MerkleProof;
946
+ }
947
+ /**
948
+ * Sign a DecisionLog in commitment mode.
949
+ *
950
+ * @param entry - The decision log entry to sign.
951
+ * @param committedFieldNames - Names of fields to commit. Recommended:
952
+ * ["tool", "scope", "payload_digest", "swarm"]. Fields not listed
953
+ * remain cleartext in the signed payload.
954
+ * @param signingKey - Ed25519 private key (32 bytes hex or raw).
955
+ * @param publicKey - Ed25519 public key (32 bytes hex).
956
+ * @param kid - Key identifier (RFC 7638 JWK thumbprint or operator-chosen).
957
+ * @param issuer - Issuer identifier (e.g. "my-gateway.example.com").
958
+ *
959
+ * @returns Signed receipt JSON, openings (per field), and receipt hash.
960
+ *
961
+ * @standard draft-farley-acta-signed-receipts-01 §signature-scope
962
+ * The signature covers SHA-256(JCS(payload_minus_signature)).
963
+ */
964
+ declare function signCommittedDecision(entry: DecisionLog, committedFieldNames: string[], signingKey: string, publicKey: string, kid: string, issuer: string): CommittedSignResult;
965
+ /**
966
+ * Build a minimal selective-disclosure envelope for a single committed
967
+ * field. The envelope can be verified offline by anyone who has the
968
+ * receipt's committed_fields_root (which the receipt itself carries).
969
+ *
970
+ * @param receiptHash - Canonical hash of the receipt the disclosure targets.
971
+ * @param fieldName - Which field to disclose.
972
+ * @param openings - The full openings map produced by signCommittedDecision.
973
+ *
974
+ * @standard draft-farley-acta-signed-receipts-01 §commitment-disclosure
975
+ */
976
+ declare function discloseField(receiptHash: string, fieldName: string, openings: Record<string, CommittedFieldOpening>): MinimalDisclosure;
977
+
828
978
  /**
829
979
  * @scopeblind/protect-mcp — External PDP Adapter
830
980
  *
@@ -2763,4 +2913,73 @@ declare function confidentialInference(_prompt: string, _config: ConfidentialInf
2763
2913
  receipt: Record<string, unknown>;
2764
2914
  }>;
2765
2915
 
2766
- export { type ActionReceipt, type AdmissionResult, type AgentId, type AgentManifest, type ApprovalAssertion, type ApprovalChallenge, type ApprovalNotification, type ApprovalResult, type ArenaPayload, type ArenaReceipt, type AttestationDocument, type AttestationPayload, type AttestationProvider, type AttestationReceipt, type AttestationResult, type AuditBundle, type AuditBundleOptions, type BenchmarkPayload, type BenchmarkReceipt, type BuilderId, type C2PAAssertion, type C2PAIngredient, type C2PAManifest, type C2PAOptions, type CCRConnectorConfig, type CCRSessionContext, type CalibrationScore, type CedarEvalRequest, type CedarPolicySet, type CedarSchema, type CedarSchemaResult, type ComplianceReport, ConfidentialGate, type ConfidentialGateConfig, type ConfidentialInferenceConfig, type CredentialConfig, type DecisionContext, type DecisionLog, type DelegationReceipt, type DisclosureMode, type Ed25519PublicKey, type EvidenceAttestation, type EvidenceAttestationInput, type EvidenceIssuer, type EvidenceReceipt, type EvidenceReceiptBase, type EvidenceSummary, type EvidenceSummaryEntry, type EvidenceType, type ExternalDecision, type ExternalPDPConfig, type HFDatasetMetadata, type HFReceiptRow, type HookEventName, type HookInput, type HookResponse, type IssuerType, type JsonRpcRequest, type JsonRpcResponse, type LeaseCompatibility, type ManifestBuilder, type ManifestCapabilities, type ManifestConfig, type ManifestIdentity, type ManifestPresentation, type ManifestSignature, type ManifestStatus, type McpToolDescription, type NotificationConfig, type PassportTokenClaims, type PayloadDigest, type PlanReceipt, type PolicyEngineMode, type PredictionReceipt, type PredictionResolution, type PropagatorConfig, type ProtectConfig, ProtectGateway, type ProtectPolicy, type RateLimit, ReceiptPropagator, type RedactedResult, type RedactionSalt, type RekorAnchor, type RekorVerification, type RestraintPayload, type RestraintReceipt, type SHA256Hash, type SafetyTranscript, type Sandbox, type SandboxConfig, type SandboxReceipt, type SandboxResult, type SandboxToolCall, type SchemaGeneratorConfig, type SigningConfig, type SimulationResult, type SimulationSummary, type SwarmContext, type TierOverrides, type TimingMetrics, type ToolPolicy, type TrustTier, type WorkPayload, type WorkReceipt, anchorToRekor, buildDecisionContext, checkRateLimit, collectSignedReceipts, computeCalibration, confidentialInference, createApprovalChallenge, createApprovalReceiptPayload, createAttestationField, createAuditBundle, createC2PAManifest, createDisclosurePackage, createEvidenceAttestation, createLogAnchorField, createReceiptChannel, createSandbox, destroySandbox, ed25519ToDIDKey, evaluateCedar, evaluateTier, exportC2PAManifestJSON, exportJSONL, formatReportMarkdown, formatSimulation, generateC2PACommand, generateCedarSchema, generateDatasetCard, generateHFMetadata, generateReport, generateSafetyTranscript, generateSchemaStub, getSignerInfo, getToolPolicy, hashReceipt, hashResponseBody, initSigning, isAgentId, isCedarAvailable, isDisclosureMode, isEvidenceType, isManifestStatus, isSigningEnabled, listCredentialLabels, loadCedarPolicies, loadPolicy, manifestToVC, meetsMinTier, parseLogFile, parseNotificationConfigFromEnv, parseRateLimit, queryExternalPDP, receiptToVP, receiptsToHFRows, redactFields, resolveCredential, revealField, runInSandbox, sendApprovalNotification, signDecision, simulate, toCredentialRequestOptions, toManifoldFormat, toMetaculusFormat, validateCredentials, validateEvidenceReceipt, validateManifest, verifyActaC2PAAssertions, verifyAllCommitments, verifyApprovalAssertion, verifyCommitment, verifyEvidenceAttestation, verifyRekorAnchor };
2916
+ /**
2917
+ * scopeblind-bridge.ts
2918
+ *
2919
+ * Optional bridge between protect-mcp (local, MIT) and a paid ScopeBlind
2920
+ * tenant. When SCOPEBLIND_TOKEN is set in the environment, every signed
2921
+ * receipt that protect-mcp emits also gets forwarded to the tenant's
2922
+ * dashboard at https://scopeblind.com/console/<slug>.
2923
+ *
2924
+ * Lifecycle:
2925
+ * 1. On first use, exchange SCOPEBLIND_TOKEN for a short-lived BRASS-v2
2926
+ * auth proof from /fn/brass/issue. Cache the proof in memory until
2927
+ * ~5 minutes before expiry, then refresh.
2928
+ * 2. As receipts are emitted by hook-server.ts, push them into an
2929
+ * in-memory batch queue.
2930
+ * 3. Flush the queue every 5s (or when it reaches 128 receipts) by POSTing
2931
+ * to /fn/console/<slug>/receipts with Bearer SCOPEBLIND_TOKEN.
2932
+ *
2933
+ * Failure mode: forward errors NEVER throw upstream. protect-mcp continues
2934
+ * to mint and persist receipts locally regardless of dashboard availability.
2935
+ * The bridge logs failures to stderr (best-effort) and retries on the next
2936
+ * flush.
2937
+ *
2938
+ * Configuration:
2939
+ * SCOPEBLIND_TOKEN Tenant bearer token (from welcome email).
2940
+ * SCOPEBLIND_TENANT Optional slug override. By default we discover
2941
+ * the slug from the BRASS proof's tenant_id.
2942
+ * SCOPEBLIND_BASE Defaults to https://scopeblind.com.
2943
+ *
2944
+ * @license MIT
2945
+ */
2946
+ interface BridgeStats {
2947
+ enabled: boolean;
2948
+ tenant_slug: string | null;
2949
+ forwarded_total: number;
2950
+ rejected_total: number;
2951
+ last_flush_at: string | null;
2952
+ last_error: string | null;
2953
+ }
2954
+ declare class ScopeBlindBridge {
2955
+ private readonly token;
2956
+ private readonly base;
2957
+ private readonly tenantOverride;
2958
+ private cachedProof;
2959
+ private queue;
2960
+ private flushTimer;
2961
+ private stats;
2962
+ private shuttingDown;
2963
+ constructor(env?: Record<string, string | undefined>);
2964
+ enabled(): boolean;
2965
+ /** Push a signed receipt into the queue. Non-blocking. */
2966
+ forward(signedReceipt: any): void;
2967
+ /** Flush the queue. Safe to call concurrently. */
2968
+ flush(): Promise<void>;
2969
+ /** Exchange SCOPEBLIND_TOKEN for a BRASS-v2 proof; refresh near expiry. */
2970
+ private ensureBrassProof;
2971
+ /**
2972
+ * Return a snapshot of bridge stats. Useful for `protect-mcp scopeblind status`.
2973
+ */
2974
+ getStats(): BridgeStats & {
2975
+ queued: number;
2976
+ brass_proof_expires_at: string | null;
2977
+ };
2978
+ /** Flush remaining receipts and stop the interval. Called on process exit. */
2979
+ shutdown(): Promise<void>;
2980
+ }
2981
+ declare function getScopeBlindBridge(): ScopeBlindBridge;
2982
+ /** Convenience: forward a signed receipt without instantiating yourself. */
2983
+ declare function forwardReceipt(signedReceipt: any): void;
2984
+
2985
+ export { type ActionReceipt, type AdmissionResult, type AgentId, type AgentManifest, type ApprovalAssertion, type ApprovalChallenge, type ApprovalNotification, type ApprovalResult, type ArenaPayload, type ArenaReceipt, type AttestationDocument, type AttestationPayload, type AttestationProvider, type AttestationReceipt, type AttestationResult, type AuditBundle, type AuditBundleOptions, type BenchmarkPayload, type BenchmarkReceipt, type BuilderId, type C2PAAssertion, type C2PAIngredient, type C2PAManifest, type C2PAOptions, type CCRConnectorConfig, type CCRSessionContext, type CalibrationScore, type CedarEvalRequest, type CedarPolicySet, type CedarSchema, type CedarSchemaResult, type CommittedFieldOpening, type CommittedSignResult, type ComplianceReport, ConfidentialGate, type ConfidentialGateConfig, type ConfidentialInferenceConfig, type CredentialConfig, type DecisionContext, type DecisionLog, type DelegationReceipt, type DisclosureMode, type Ed25519PublicKey, type EvidenceAttestation, type EvidenceAttestationInput, type EvidenceIssuer, type EvidenceReceipt, type EvidenceReceiptBase, type EvidenceSummary, type EvidenceSummaryEntry, type EvidenceType, type ExternalDecision, type ExternalPDPConfig, type HFDatasetMetadata, type HFReceiptRow, type HookEventName, type HookInput, type HookResponse, type IssuerType, type JsonRpcRequest, type JsonRpcResponse, type LeaseCompatibility, type ManifestBuilder, type ManifestCapabilities, type ManifestConfig, type ManifestIdentity, type ManifestPresentation, type ManifestSignature, type ManifestStatus, type McpToolDescription, type MinimalDisclosure, type NotificationConfig, type PassportTokenClaims, type PayloadDigest, type PlanReceipt, type PolicyEngineMode, type PredictionReceipt, type PredictionResolution, type PropagatorConfig, type ProtectConfig, ProtectGateway, type ProtectPolicy, type RateLimit, ReceiptPropagator, type RedactedResult, type RedactionSalt, type RekorAnchor, type RekorVerification, type RestraintPayload, type RestraintReceipt, type SHA256Hash, type SafetyTranscript, type Sandbox, type SandboxConfig, type SandboxReceipt, type SandboxResult, type SandboxToolCall, type SchemaGeneratorConfig, ScopeBlindBridge, type SigningConfig, type SimulationResult, type SimulationSummary, type SwarmContext, type TierOverrides, type TimingMetrics, type ToolPolicy, type TrustTier, type WorkPayload, type WorkReceipt, anchorToRekor, buildDecisionContext, checkRateLimit, collectSignedReceipts, computeCalibration, confidentialInference, createApprovalChallenge, createApprovalReceiptPayload, createAttestationField, createAuditBundle, createC2PAManifest, createDisclosurePackage, createEvidenceAttestation, createLogAnchorField, createReceiptChannel, createSandbox, destroySandbox, discloseField, ed25519ToDIDKey, evaluateCedar, evaluateTier, exportC2PAManifestJSON, exportJSONL, formatReportMarkdown, formatSimulation, forwardReceipt, generateC2PACommand, generateCedarSchema, generateDatasetCard, generateHFMetadata, generateReport, generateSafetyTranscript, generateSchemaStub, getScopeBlindBridge, getSignerInfo, getToolPolicy, hashReceipt, hashResponseBody, initSigning, isAgentId, isCedarAvailable, isDisclosureMode, isEvidenceType, isManifestStatus, isSigningEnabled, listCredentialLabels, loadCedarPolicies, loadPolicy, manifestToVC, meetsMinTier, parseLogFile, parseNotificationConfigFromEnv, parseRateLimit, queryExternalPDP, receiptToVP, receiptsToHFRows, redactFields, resolveCredential, revealField, runInSandbox, sendApprovalNotification, signCommittedDecision, signDecision, simulate, toCredentialRequestOptions, toManifoldFormat, toMetaculusFormat, validateCredentials, validateEvidenceReceipt, validateManifest, verifyActaC2PAAssertions, verifyAllCommitments, verifyApprovalAssertion, verifyCommitment, verifyEvidenceAttestation, verifyRekorAnchor };