@sanctuary-framework/mcp-server 1.0.0-rc.1 → 1.1.0

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
@@ -3,8 +3,8 @@ import { gcm } from '@noble/ciphers/aes.js';
3
3
  import { sha256 } from '@noble/hashes/sha256';
4
4
  import { hmac } from '@noble/hashes/hmac';
5
5
  import { RistrettoPoint, ed25519 } from '@noble/curves/ed25519';
6
- import { readFile, mkdir, writeFile, stat, unlink, readdir, chmod, access } from 'fs/promises';
7
- import { join, dirname } from 'path';
6
+ import { readFile, mkdir, writeFile, stat, unlink, readdir, chmod, lstat, realpath, rm, access } from 'fs/promises';
7
+ import { join, resolve, dirname, sep, basename } from 'path';
8
8
  import { platform, homedir } from 'os';
9
9
  import { createRequire } from 'module';
10
10
  import { argon2id } from 'hash-wasm';
@@ -13,7 +13,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
13
13
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
14
14
  import { createServer as createServer$2 } from 'http';
15
15
  import { createServer as createServer$1 } from 'https';
16
- import { exec, execSync } from 'child_process';
16
+ import { exec, execSync, spawn } from 'child_process';
17
17
  import { statSync, existsSync, readFileSync } from 'fs';
18
18
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
19
19
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
@@ -475,6 +475,15 @@ function defaultConfig() {
475
475
  auto_publish_to_verascore: true,
476
476
  // DELTA-04: default OFF for privacy. Enable explicitly per deployment.
477
477
  auto_publish_handshakes: false
478
+ },
479
+ privacy_filter: {
480
+ mode: "local",
481
+ // Fail-closed by default per Sanctuary Invariant #5: never silently
482
+ // degrade to a less-secure behavior on error. Operators who need a
483
+ // legacy fallback path opt in explicitly via config or env var.
484
+ fail_mode: "closed",
485
+ command: "opf",
486
+ timeout_ms: 5e3
478
487
  }
479
488
  };
480
489
  }
@@ -486,10 +495,14 @@ async function loadConfig(configPath) {
486
495
  const raw = await readFile(path, "utf-8");
487
496
  const fileConfig = JSON.parse(raw);
488
497
  config = deepMerge(config, fileConfig);
498
+ assertSanctuaryConfigShape(config);
489
499
  } catch (err) {
490
500
  if (err instanceof Error && err.message.includes("unimplemented features")) {
491
501
  throw err;
492
502
  }
503
+ if (err instanceof Error && err.message.startsWith("Sanctuary config field")) {
504
+ throw err;
505
+ }
493
506
  }
494
507
  if (process.env.SANCTUARY_STORAGE_PATH) {
495
508
  config.storage_path = process.env.SANCTUARY_STORAGE_PATH;
@@ -560,6 +573,21 @@ async function loadConfig(configPath) {
560
573
  if (process.env.SANCTUARY_AUTO_PUBLISH_HANDSHAKES === "false") {
561
574
  config.verascore.auto_publish_handshakes = false;
562
575
  }
576
+ if (process.env.SANCTUARY_PRIVACY_FILTER) {
577
+ config.privacy_filter.mode = process.env.SANCTUARY_PRIVACY_FILTER;
578
+ }
579
+ if (process.env.SANCTUARY_PRIVACY_FILTER_FAIL_MODE) {
580
+ config.privacy_filter.fail_mode = process.env.SANCTUARY_PRIVACY_FILTER_FAIL_MODE;
581
+ }
582
+ if (process.env.SANCTUARY_PRIVACY_FILTER_COMMAND) {
583
+ config.privacy_filter.command = process.env.SANCTUARY_PRIVACY_FILTER_COMMAND;
584
+ }
585
+ if (process.env.SANCTUARY_PRIVACY_FILTER_TIMEOUT_MS) {
586
+ config.privacy_filter.timeout_ms = parseInt(
587
+ process.env.SANCTUARY_PRIVACY_FILTER_TIMEOUT_MS,
588
+ 10
589
+ );
590
+ }
563
591
  config.version = PKG_VERSION;
564
592
  validateConfig(config);
565
593
  return config;
@@ -600,6 +628,23 @@ function validateConfig(config) {
600
628
  `Unimplemented config value: reputation.mode = "${config.reputation.mode}". Only ${[...implementedReputationMode].map((v) => `"${v}"`).join(", ")} is currently implemented. Using an unimplemented reputation mode would silently skip reputation verification.`
601
629
  );
602
630
  }
631
+ const implementedPrivacyModes = /* @__PURE__ */ new Set(["local", "opf", "off"]);
632
+ if (!implementedPrivacyModes.has(config.privacy_filter.mode)) {
633
+ errors.push(
634
+ `Invalid config value: privacy_filter.mode = "${config.privacy_filter.mode}". Use ${[...implementedPrivacyModes].map((v) => `"${v}"`).join(", ")}.`
635
+ );
636
+ }
637
+ const implementedPrivacyFailModes = /* @__PURE__ */ new Set(["closed", "fallback"]);
638
+ if (!implementedPrivacyFailModes.has(config.privacy_filter.fail_mode)) {
639
+ errors.push(
640
+ `Invalid config value: privacy_filter.fail_mode = "${config.privacy_filter.fail_mode}". Use ${[...implementedPrivacyFailModes].map((v) => `"${v}"`).join(", ")}.`
641
+ );
642
+ }
643
+ if (!Number.isFinite(config.privacy_filter.timeout_ms) || config.privacy_filter.timeout_ms < 100) {
644
+ errors.push(
645
+ `Invalid config value: privacy_filter.timeout_ms = "${config.privacy_filter.timeout_ms}". Use an integer timeout of at least 100 ms.`
646
+ );
647
+ }
603
648
  if (errors.length > 0) {
604
649
  throw new Error(
605
650
  `Sanctuary configuration references unimplemented features:
@@ -621,6 +666,39 @@ function deepMerge(base, override) {
621
666
  }
622
667
  return result;
623
668
  }
669
+ function assertSanctuaryConfigShape(c) {
670
+ const requiredObjectKeys = [
671
+ "state",
672
+ "execution",
673
+ "disclosure",
674
+ "reputation",
675
+ "dashboard",
676
+ "webhook",
677
+ "verascore",
678
+ "privacy_filter"
679
+ ];
680
+ for (const k of requiredObjectKeys) {
681
+ if (typeof c[k] !== "object" || c[k] === null || Array.isArray(c[k])) {
682
+ throw new Error(
683
+ `Sanctuary config field "${k}" must be an object; got ${c[k] === null ? "null" : Array.isArray(c[k]) ? "array" : typeof c[k]}`
684
+ );
685
+ }
686
+ }
687
+ if (typeof c.version !== "string") {
688
+ throw new Error(`Sanctuary config field "version" must be a string`);
689
+ }
690
+ if (typeof c.storage_path !== "string") {
691
+ throw new Error(`Sanctuary config field "storage_path" must be a string`);
692
+ }
693
+ if (c.transport !== "stdio" && c.transport !== "http") {
694
+ throw new Error(
695
+ `Sanctuary config field "transport" must be "stdio" | "http"; got ${JSON.stringify(c.transport)}`
696
+ );
697
+ }
698
+ if (typeof c.http_port !== "number" || !Number.isFinite(c.http_port)) {
699
+ throw new Error(`Sanctuary config field "http_port" must be a finite number`);
700
+ }
701
+ }
624
702
 
625
703
  // src/storage/filesystem.ts
626
704
  init_random();
@@ -817,6 +895,11 @@ var RESERVED_NAMESPACE_PREFIXES = [
817
895
  "_context_gate_policies",
818
896
  "_fortress_mode"
819
897
  ];
898
+ function isReservedNamespace(namespace) {
899
+ return RESERVED_NAMESPACE_PREFIXES.some(
900
+ (prefix) => namespace === prefix || namespace.startsWith(prefix + "/")
901
+ );
902
+ }
820
903
  var StateStore = class _StateStore {
821
904
  storage;
822
905
  masterKey;
@@ -4145,6 +4228,25 @@ var DEFAULT_POLICY = {
4145
4228
  // Creates new Ed25519 identity + publishes — always requires approval
4146
4229
  "sanctuary_export_identity_bundle",
4147
4230
  // Exports portable identity — always requires approval
4231
+ "exit_bundle_export",
4232
+ // Complete portability bundle export. Always requires approval.
4233
+ "exit_bundle_import",
4234
+ // External durable-record import. Always requires approval.
4235
+ "exit_bundle_import_activate",
4236
+ // Activates imported material. Always requires approval.
4237
+ "exit_bundle_rekey",
4238
+ // Re-encrypts imported state under destination keys.
4239
+ // v1.1 hub-control surfaces. Every operation_category in
4240
+ // server/src/contracts/v1.1/hub-events.ts that is not already canonical
4241
+ // here MUST be enrolled under Tier 1 in the same PR that lands the hub
4242
+ // endpoint that surfaces it. Drift between the contract enum and this
4243
+ // list is a release blocker per the contract comment.
4244
+ "policy_change",
4245
+ // Operator-driven policy bind on a wrapped agent.
4246
+ "lockdown",
4247
+ // Operator-driven hard-stop of a wrapped agent.
4248
+ "unwrap",
4249
+ // Operator-driven removal of the Sanctuary wrap from an agent.
4148
4250
  // WP-MVP-2 Operator Console: federation-node-join requires explicit
4149
4251
  // operator confirmation per Key 8. No auto-approve path. The console's
4150
4252
  // JoinApprover drives this gate via `MeshConsoleClient.makeJoinApprover`.
@@ -4334,6 +4436,13 @@ tier1_always_approve:
4334
4436
  - governor_reset
4335
4437
  - sanctuary_bootstrap
4336
4438
  - sanctuary_export_identity_bundle
4439
+ - exit_bundle_export
4440
+ - exit_bundle_import
4441
+ - exit_bundle_import_activate
4442
+ - exit_bundle_rekey
4443
+ - policy_change
4444
+ - lockdown
4445
+ - unwrap
4337
4446
 
4338
4447
  # \u2500\u2500\u2500 Tier 2: Behavioral Anomaly Detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
4339
4448
  # Triggers approval when agent behavior deviates from its baseline.
@@ -8931,7 +9040,7 @@ var DashboardApprovalChannel = class {
8931
9040
  server = createServer$2(handler);
8932
9041
  }
8933
9042
  this.httpServer = server;
8934
- return new Promise((resolve, reject) => {
9043
+ return new Promise((resolve4, reject) => {
8935
9044
  const protocol = this.useTLS ? "https" : "http";
8936
9045
  const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
8937
9046
  server.listen(this.config.port, this.config.host, () => {
@@ -8956,7 +9065,7 @@ var DashboardApprovalChannel = class {
8956
9065
  if (shouldAutoOpen) {
8957
9066
  this.openInBrowser(sessionUrl);
8958
9067
  }
8959
- resolve();
9068
+ resolve4();
8960
9069
  });
8961
9070
  server.on("error", (err) => {
8962
9071
  if (err.code === "EADDRINUSE") {
@@ -9002,8 +9111,8 @@ var DashboardApprovalChannel = class {
9002
9111
  }
9003
9112
  this.rateLimits.clear();
9004
9113
  if (this.httpServer) {
9005
- return new Promise((resolve) => {
9006
- this.httpServer.close(() => resolve());
9114
+ return new Promise((resolve4) => {
9115
+ this.httpServer.close(() => resolve4());
9007
9116
  });
9008
9117
  }
9009
9118
  }
@@ -9017,7 +9126,7 @@ var DashboardApprovalChannel = class {
9017
9126
  `[Sanctuary] Approval required: ${request.operation} (Tier ${request.tier}) \u2014 open dashboard to respond
9018
9127
  `
9019
9128
  );
9020
- return new Promise((resolve) => {
9129
+ return new Promise((resolve4) => {
9021
9130
  const timer = setTimeout(() => {
9022
9131
  this.pending.delete(id);
9023
9132
  const response = {
@@ -9031,12 +9140,12 @@ var DashboardApprovalChannel = class {
9031
9140
  decision: response.decision,
9032
9141
  decided_by: "timeout"
9033
9142
  });
9034
- resolve(response);
9143
+ resolve4(response);
9035
9144
  }, this.config.timeout_seconds * 1e3);
9036
9145
  const pending = {
9037
9146
  id,
9038
9147
  request,
9039
- resolve,
9148
+ resolve: resolve4,
9040
9149
  timer,
9041
9150
  created_at: (/* @__PURE__ */ new Date()).toISOString()
9042
9151
  };
@@ -9882,7 +9991,7 @@ var WebhookApprovalChannel = class {
9882
9991
  * Start the callback listener server.
9883
9992
  */
9884
9993
  async start() {
9885
- return new Promise((resolve, reject) => {
9994
+ return new Promise((resolve4, reject) => {
9886
9995
  this.callbackServer = createServer$2(
9887
9996
  (req, res) => this.handleCallback(req, res)
9888
9997
  );
@@ -9897,7 +10006,7 @@ var WebhookApprovalChannel = class {
9897
10006
 
9898
10007
  `
9899
10008
  );
9900
- resolve();
10009
+ resolve4();
9901
10010
  }
9902
10011
  );
9903
10012
  this.callbackServer.on("error", reject);
@@ -9917,8 +10026,8 @@ var WebhookApprovalChannel = class {
9917
10026
  }
9918
10027
  this.pending.clear();
9919
10028
  if (this.callbackServer) {
9920
- return new Promise((resolve) => {
9921
- this.callbackServer.close(() => resolve());
10029
+ return new Promise((resolve4) => {
10030
+ this.callbackServer.close(() => resolve4());
9922
10031
  });
9923
10032
  }
9924
10033
  }
@@ -9931,7 +10040,7 @@ var WebhookApprovalChannel = class {
9931
10040
  `[Sanctuary] Webhook approval sent: ${request.operation} (Tier ${request.tier}) \u2014 awaiting callback
9932
10041
  `
9933
10042
  );
9934
- return new Promise((resolve) => {
10043
+ return new Promise((resolve4) => {
9935
10044
  const timer = setTimeout(() => {
9936
10045
  this.pending.delete(id);
9937
10046
  const response = {
@@ -9940,12 +10049,12 @@ var WebhookApprovalChannel = class {
9940
10049
  decided_at: (/* @__PURE__ */ new Date()).toISOString(),
9941
10050
  decided_by: "timeout"
9942
10051
  };
9943
- resolve(response);
10052
+ resolve4(response);
9944
10053
  }, this.config.timeout_seconds * 1e3);
9945
10054
  const pending = {
9946
10055
  id,
9947
10056
  request,
9948
- resolve,
10057
+ resolve: resolve4,
9949
10058
  timer,
9950
10059
  created_at: (/* @__PURE__ */ new Date()).toISOString()
9951
10060
  };
@@ -11492,7 +11601,7 @@ var ApprovalGate = class {
11492
11601
  return {
11493
11602
  allowed: response.decision === "approve",
11494
11603
  tier,
11495
- reason: response.decision === "approve" ? `Approved by ${response.decided_by}` : reason,
11604
+ reason: response.decision === "approve" ? `Approved by ${response.decided_by}` : `Tier ${tier} operation requires approval`,
11496
11605
  approval_required: true,
11497
11606
  approval_response: response
11498
11607
  };
@@ -13139,7 +13248,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13139
13248
  const now = (/* @__PURE__ */ new Date()).toISOString();
13140
13249
  const canonicalBytes = canonicalize(outcome);
13141
13250
  const canonicalString = new TextDecoder().decode(canonicalBytes);
13142
- const sha2567 = createCommitment(canonicalString);
13251
+ const sha2568 = createCommitment(canonicalString);
13143
13252
  let pedersenData;
13144
13253
  if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
13145
13254
  const pedersen = createPedersenCommitment(outcome.rounds);
@@ -13151,7 +13260,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13151
13260
  const commitmentPayload = {
13152
13261
  bridge_commitment_id: commitmentId,
13153
13262
  session_id: outcome.session_id,
13154
- sha256_commitment: sha2567.commitment,
13263
+ sha256_commitment: sha2568.commitment,
13155
13264
  terms_hash: outcome.terms_hash,
13156
13265
  committer_did: identity.did,
13157
13266
  committed_at: now,
@@ -13162,8 +13271,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13162
13271
  return {
13163
13272
  bridge_commitment_id: commitmentId,
13164
13273
  session_id: outcome.session_id,
13165
- sha256_commitment: sha2567.commitment,
13166
- blinding_factor: sha2567.blinding_factor,
13274
+ sha256_commitment: sha2568.commitment,
13275
+ blinding_factor: sha2568.blinding_factor,
13167
13276
  committer_did: identity.did,
13168
13277
  signature: toBase64url(signature),
13169
13278
  pedersen_commitment: pedersenData,
@@ -14254,6 +14363,140 @@ function createAuditTools(config) {
14254
14363
  return { tools };
14255
14364
  }
14256
14365
 
14366
+ // src/audit/reset-history.ts
14367
+ init_hashing();
14368
+ init_encoding();
14369
+ var RESET_HISTORY_FILENAME = ".reset-history.log";
14370
+ var RECOVERED_FROM_RESET_OPERATION = "fortress_recovered_from_reset";
14371
+ var ResetHistoryMalformedError = class extends Error {
14372
+ constructor(markerPath, lineNumber, cause) {
14373
+ const reason = cause instanceof Error ? cause.message : String(cause);
14374
+ super(
14375
+ `Reset-history marker at ${markerPath} is malformed at line ${lineNumber}: ${reason}.
14376
+ Inspect the file and either correct the JSON or delete it manually before re-running.`
14377
+ );
14378
+ this.markerPath = markerPath;
14379
+ this.lineNumber = lineNumber;
14380
+ this.cause = cause;
14381
+ this.name = "ResetHistoryMalformedError";
14382
+ }
14383
+ };
14384
+ function parseResetHistory(content, markerPath) {
14385
+ const markerHash = hashToString(stringToBytes(content));
14386
+ const markers = [];
14387
+ const lines = content.split("\n");
14388
+ for (let i = 0; i < lines.length; i++) {
14389
+ const raw = lines[i] ?? "";
14390
+ if (raw.trim().length === 0) continue;
14391
+ let parsed;
14392
+ try {
14393
+ parsed = JSON.parse(raw);
14394
+ } catch (err) {
14395
+ throw new ResetHistoryMalformedError(markerPath, i + 1, err);
14396
+ }
14397
+ markers.push(coerceMarker(parsed, markerPath, i + 1));
14398
+ }
14399
+ return { markers, markerHash };
14400
+ }
14401
+ function coerceMarker(raw, markerPath, lineNumber) {
14402
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
14403
+ throw new ResetHistoryMalformedError(
14404
+ markerPath,
14405
+ lineNumber,
14406
+ new Error("expected a JSON object")
14407
+ );
14408
+ }
14409
+ const obj = raw;
14410
+ const required = [
14411
+ "started_at",
14412
+ "completed_at",
14413
+ "recovery_mode",
14414
+ "fortress_name",
14415
+ "storage_path",
14416
+ "keychain_cleared"
14417
+ ];
14418
+ for (const field of required) {
14419
+ if (!(field in obj)) {
14420
+ throw new ResetHistoryMalformedError(
14421
+ markerPath,
14422
+ lineNumber,
14423
+ new Error(`missing required field "${field}"`)
14424
+ );
14425
+ }
14426
+ }
14427
+ if (typeof obj.started_at !== "string")
14428
+ throw typed(markerPath, lineNumber, "started_at", "string");
14429
+ if (typeof obj.completed_at !== "string")
14430
+ throw typed(markerPath, lineNumber, "completed_at", "string");
14431
+ if (typeof obj.fortress_name !== "string")
14432
+ throw typed(markerPath, lineNumber, "fortress_name", "string");
14433
+ if (typeof obj.storage_path !== "string")
14434
+ throw typed(markerPath, lineNumber, "storage_path", "string");
14435
+ if (typeof obj.keychain_cleared !== "boolean")
14436
+ throw typed(markerPath, lineNumber, "keychain_cleared", "boolean");
14437
+ if (obj.recovery_mode !== "shares" && obj.recovery_mode !== "guardian" && obj.recovery_mode !== "nuke") {
14438
+ throw new ResetHistoryMalformedError(
14439
+ markerPath,
14440
+ lineNumber,
14441
+ new Error(
14442
+ `recovery_mode must be one of shares|guardian|nuke (got ${JSON.stringify(obj.recovery_mode)})`
14443
+ )
14444
+ );
14445
+ }
14446
+ return {
14447
+ started_at: obj.started_at,
14448
+ completed_at: obj.completed_at,
14449
+ recovery_mode: obj.recovery_mode,
14450
+ fortress_name: obj.fortress_name,
14451
+ storage_path: obj.storage_path,
14452
+ keychain_cleared: obj.keychain_cleared
14453
+ };
14454
+ }
14455
+ function typed(markerPath, lineNumber, field, expected) {
14456
+ return new ResetHistoryMalformedError(
14457
+ markerPath,
14458
+ lineNumber,
14459
+ new Error(`field "${field}" must be a ${expected}`)
14460
+ );
14461
+ }
14462
+ async function consumeResetHistoryMarker(options) {
14463
+ const markerPath = join(options.storagePath, RESET_HISTORY_FILENAME);
14464
+ if (!await fileExists2(markerPath)) {
14465
+ return { emitted: 0, markerPath };
14466
+ }
14467
+ const content = await readFile(markerPath, "utf-8");
14468
+ const { markers, markerHash } = parseResetHistory(content, markerPath);
14469
+ if (markers.length === 0) {
14470
+ await rm(markerPath, { force: true });
14471
+ return { emitted: 0, markerHash, markerPath };
14472
+ }
14473
+ for (let i = 0; i < markers.length; i++) {
14474
+ const marker = markers[i];
14475
+ options.auditLog.append("l2", RECOVERED_FROM_RESET_OPERATION, "system", {
14476
+ reset_at_started: marker.started_at,
14477
+ reset_at_completed: marker.completed_at,
14478
+ recovery_mode: marker.recovery_mode,
14479
+ fortress_name: marker.fortress_name,
14480
+ reset_storage_path: marker.storage_path,
14481
+ keychain_cleared: marker.keychain_cleared,
14482
+ reset_marker_hash: markerHash,
14483
+ reset_marker_index: i,
14484
+ reset_marker_total: markers.length
14485
+ });
14486
+ }
14487
+ await options.auditLog.flush();
14488
+ await rm(markerPath, { force: true });
14489
+ return { emitted: markers.length, markerHash, markerPath };
14490
+ }
14491
+ async function fileExists2(path) {
14492
+ try {
14493
+ await access(path);
14494
+ return true;
14495
+ } catch {
14496
+ return false;
14497
+ }
14498
+ }
14499
+
14257
14500
  // src/audit/siem-formatter.ts
14258
14501
  function parseGateDecision(details) {
14259
14502
  if (!details || typeof details.gate_decision !== "string") {
@@ -15321,6 +15564,607 @@ function matchesFieldPattern(normalizedField, pattern) {
15321
15564
  // src/l2-operational/context-gate-enforcer.ts
15322
15565
  init_encoding();
15323
15566
  init_hashing();
15567
+
15568
+ // src/l2-operational/privacy-filter.ts
15569
+ init_encryption();
15570
+ init_encoding();
15571
+ init_hashing();
15572
+ var SPAN_PATTERNS = [
15573
+ {
15574
+ class: "email",
15575
+ pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
15576
+ replacement: "[EMAIL_REDACTED]",
15577
+ placeholderPrefix: "EMAIL",
15578
+ detectorClass: "person"
15579
+ },
15580
+ {
15581
+ class: "ssn",
15582
+ pattern: /\b\d{3}-\d{2}-\d{4}\b/g,
15583
+ replacement: "[SSN_REDACTED]",
15584
+ placeholderPrefix: "SSN",
15585
+ detectorClass: "person"
15586
+ },
15587
+ {
15588
+ class: "credit_card",
15589
+ pattern: /\b(?:\d[ -]*?){13,19}\b/g,
15590
+ replacement: "[CARD_REDACTED]",
15591
+ placeholderPrefix: "CARD",
15592
+ detectorClass: "account"
15593
+ },
15594
+ {
15595
+ class: "phone",
15596
+ pattern: /\b(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)\d{3}[-.\s]?\d{4}\b/g,
15597
+ replacement: "[PHONE_REDACTED]",
15598
+ placeholderPrefix: "PHONE",
15599
+ detectorClass: "person"
15600
+ },
15601
+ {
15602
+ class: "secret_assignment",
15603
+ pattern: /\b(api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\s*[:=]\s*["']?[^"',\s}]+/gi,
15604
+ replacement: "$1=[SECRET_REDACTED]",
15605
+ placeholderPrefix: "SECRET",
15606
+ detectorClass: "secret"
15607
+ },
15608
+ {
15609
+ class: "secret",
15610
+ pattern: /\b(?:Bearer\s+[A-Za-z0-9._~+/-]{16,}|sk-[A-Za-z0-9_-]{12,}|sk_(?:live|test)_[A-Za-z0-9]{12,}|ghp_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
15611
+ replacement: "[SECRET_REDACTED]",
15612
+ placeholderPrefix: "SECRET",
15613
+ detectorClass: "secret"
15614
+ },
15615
+ {
15616
+ class: "credential",
15617
+ pattern: /\b(?:credential|credentials|client[_-]?secret|private[_-]?key)\s*[:=]\s*["']?[^"',\s}]+/gi,
15618
+ replacement: "[CREDENTIAL_REDACTED]",
15619
+ placeholderPrefix: "CREDENTIAL",
15620
+ detectorClass: "credential"
15621
+ },
15622
+ {
15623
+ class: "account_number",
15624
+ pattern: /\b(?:(?:acct|account|customer|tenant|org)[_-][A-Za-z0-9]{4,}|(?:acct|account)[0-9]{4,})\b/gi,
15625
+ replacement: "[ACCOUNT_REDACTED]",
15626
+ placeholderPrefix: "ACCOUNT",
15627
+ detectorClass: "account"
15628
+ },
15629
+ {
15630
+ class: "file_path",
15631
+ pattern: /(?:~\/|\/(?:Users|home|var|tmp|etc|opt)\/|[A-Za-z]:\\|\.{1,2}\/)(?:[A-Za-z0-9._-]+[\\/])+[A-Za-z0-9._-]+/g,
15632
+ replacement: "[FILE_PATH_REDACTED]",
15633
+ placeholderPrefix: "FILE_PATH",
15634
+ detectorClass: "file_path"
15635
+ }
15636
+ ];
15637
+ var MAX_DEPTH = 20;
15638
+ var VAULT_NAMESPACE = "_privacy_placeholder_vault";
15639
+ var PRIVACY_VAULT_CACHE_MAX = 5e3;
15640
+ var LruCache = class {
15641
+ max;
15642
+ map = /* @__PURE__ */ new Map();
15643
+ constructor(max) {
15644
+ this.max = max;
15645
+ }
15646
+ get(key) {
15647
+ const value = this.map.get(key);
15648
+ if (value === void 0) return void 0;
15649
+ this.map.delete(key);
15650
+ this.map.set(key, value);
15651
+ return value;
15652
+ }
15653
+ set(key, value) {
15654
+ if (this.map.has(key)) {
15655
+ this.map.delete(key);
15656
+ } else if (this.map.size >= this.max) {
15657
+ const oldestKey = this.map.keys().next().value;
15658
+ if (oldestKey !== void 0) {
15659
+ this.map.delete(oldestKey);
15660
+ }
15661
+ }
15662
+ this.map.set(key, value);
15663
+ }
15664
+ has(key) {
15665
+ return this.map.has(key);
15666
+ }
15667
+ get size() {
15668
+ return this.map.size;
15669
+ }
15670
+ };
15671
+ var PrivacyPlaceholderVault = class {
15672
+ storage;
15673
+ encryptionKey;
15674
+ lookupKey;
15675
+ cache = new LruCache(PRIVACY_VAULT_CACHE_MAX);
15676
+ pathCache = new LruCache(PRIVACY_VAULT_CACHE_MAX);
15677
+ constructor(storage, masterKey) {
15678
+ this.storage = storage;
15679
+ this.encryptionKey = derivePurposeKey(masterKey, "l2-privacy-placeholders");
15680
+ this.lookupKey = derivePurposeKey(masterKey, "l2-privacy-placeholder-lookup");
15681
+ }
15682
+ async placeholderFor(spanClass, rawValue, scope = "default") {
15683
+ const key = this.recordKey(spanClass, rawValue, scope);
15684
+ const cached = this.cache.get(key);
15685
+ if (cached) return cached.placeholder;
15686
+ const existing = await this.readRecord(key);
15687
+ if (existing) {
15688
+ this.cache.set(key, existing);
15689
+ return existing.placeholder;
15690
+ }
15691
+ const index = await this.readIndex(scope);
15692
+ const next = (index.counters[spanClass] ?? 0) + 1;
15693
+ index.counters[spanClass] = next;
15694
+ const placeholder = `${placeholderPrefixFor(spanClass)}_${next}`;
15695
+ const record = {
15696
+ version: 1,
15697
+ kind: "placeholder",
15698
+ scope,
15699
+ class: spanClass,
15700
+ placeholder,
15701
+ raw_value: rawValue,
15702
+ raw_hash: this.hmacString(`raw:${rawValue}`),
15703
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
15704
+ };
15705
+ await this.writeRecord(key, record);
15706
+ await this.writeIndex(scope, index);
15707
+ this.cache.set(key, record);
15708
+ return placeholder;
15709
+ }
15710
+ async resolvePlaceholder(placeholder, scope = "default") {
15711
+ const entries = await this.storage.list(VAULT_NAMESPACE, `${scope}__record__`);
15712
+ for (const meta of entries) {
15713
+ const record = await this.readRecord(meta.key);
15714
+ if (record?.placeholder === placeholder) {
15715
+ return record.raw_value;
15716
+ }
15717
+ }
15718
+ return null;
15719
+ }
15720
+ async aliasForFieldPath(path, scope = "default") {
15721
+ const key = this.pathRecordKey(path, scope);
15722
+ const cached = this.pathCache.get(key);
15723
+ if (cached) return cached.alias;
15724
+ const existing = await this.readPathRecord(key);
15725
+ if (existing) {
15726
+ this.pathCache.set(key, existing);
15727
+ return existing.alias;
15728
+ }
15729
+ const index = await this.readPathIndex(scope);
15730
+ const alias = `$${index.next}`;
15731
+ const nextIndex = { version: 1, next: index.next + 1 };
15732
+ const record = {
15733
+ version: 1,
15734
+ kind: "field_path",
15735
+ scope,
15736
+ alias,
15737
+ raw_path: path,
15738
+ raw_hash: this.hmacString(`path:${path}`),
15739
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
15740
+ };
15741
+ await this.writePathRecord(key, record);
15742
+ await this.writePathIndex(scope, nextIndex);
15743
+ this.pathCache.set(key, record);
15744
+ return alias;
15745
+ }
15746
+ async resolveFieldPathAlias(alias, scope = "default") {
15747
+ const entries = await this.storage.list(VAULT_NAMESPACE, `${scope}__path__`);
15748
+ for (const meta of entries) {
15749
+ const record = await this.readPathRecord(meta.key);
15750
+ if (record?.alias === alias) {
15751
+ return record.raw_path;
15752
+ }
15753
+ }
15754
+ return null;
15755
+ }
15756
+ recordKey(spanClass, rawValue, scope) {
15757
+ const rawHash = this.hmacString(`${scope}:${spanClass}:${rawValue}`);
15758
+ return `${scope}__record__${spanClass}__${rawHash}`;
15759
+ }
15760
+ indexKey(scope) {
15761
+ return `${scope}__index`;
15762
+ }
15763
+ pathRecordKey(path, scope) {
15764
+ return `${scope}__path__${this.hmacString(`${scope}:field_path:${path}`)}`;
15765
+ }
15766
+ pathIndexKey(scope) {
15767
+ return `${scope}__path_index`;
15768
+ }
15769
+ async readIndex(scope) {
15770
+ const raw = await this.storage.read(VAULT_NAMESPACE, this.indexKey(scope));
15771
+ if (!raw) return { version: 1, counters: {} };
15772
+ try {
15773
+ const encrypted = JSON.parse(bytesToString(raw));
15774
+ const decrypted = decrypt(encrypted, this.encryptionKey);
15775
+ return JSON.parse(bytesToString(decrypted));
15776
+ } catch (err) {
15777
+ throw new PrivacyVaultError("privacy_vault_index_unreadable", err);
15778
+ }
15779
+ }
15780
+ async writeIndex(scope, index) {
15781
+ const encrypted = encrypt(stringToBytes(JSON.stringify(index)), this.encryptionKey);
15782
+ await this.storage.write(
15783
+ VAULT_NAMESPACE,
15784
+ this.indexKey(scope),
15785
+ stringToBytes(JSON.stringify(encrypted))
15786
+ );
15787
+ }
15788
+ async readRecord(key) {
15789
+ const raw = await this.storage.read(VAULT_NAMESPACE, key);
15790
+ if (!raw) return null;
15791
+ try {
15792
+ const encrypted = JSON.parse(bytesToString(raw));
15793
+ const decrypted = decrypt(encrypted, this.encryptionKey);
15794
+ const parsed = JSON.parse(bytesToString(decrypted));
15795
+ return parsed.kind === "placeholder" || parsed.kind === void 0 ? parsed : null;
15796
+ } catch (err) {
15797
+ throw new PrivacyVaultError("privacy_vault_record_unreadable", err);
15798
+ }
15799
+ }
15800
+ async writeRecord(key, record) {
15801
+ const encrypted = encrypt(stringToBytes(JSON.stringify(record)), this.encryptionKey);
15802
+ await this.storage.write(
15803
+ VAULT_NAMESPACE,
15804
+ key,
15805
+ stringToBytes(JSON.stringify(encrypted))
15806
+ );
15807
+ }
15808
+ async readPathIndex(scope) {
15809
+ const raw = await this.storage.read(VAULT_NAMESPACE, this.pathIndexKey(scope));
15810
+ if (!raw) return { version: 1, next: 0 };
15811
+ try {
15812
+ const encrypted = JSON.parse(bytesToString(raw));
15813
+ const decrypted = decrypt(encrypted, this.encryptionKey);
15814
+ return JSON.parse(bytesToString(decrypted));
15815
+ } catch (err) {
15816
+ throw new PrivacyVaultError("privacy_vault_path_index_unreadable", err);
15817
+ }
15818
+ }
15819
+ async writePathIndex(scope, index) {
15820
+ const encrypted = encrypt(stringToBytes(JSON.stringify(index)), this.encryptionKey);
15821
+ await this.storage.write(
15822
+ VAULT_NAMESPACE,
15823
+ this.pathIndexKey(scope),
15824
+ stringToBytes(JSON.stringify(encrypted))
15825
+ );
15826
+ }
15827
+ async readPathRecord(key) {
15828
+ const raw = await this.storage.read(VAULT_NAMESPACE, key);
15829
+ if (!raw) return null;
15830
+ try {
15831
+ const encrypted = JSON.parse(bytesToString(raw));
15832
+ const decrypted = decrypt(encrypted, this.encryptionKey);
15833
+ const parsed = JSON.parse(bytesToString(decrypted));
15834
+ return parsed.kind === "field_path" ? parsed : null;
15835
+ } catch (err) {
15836
+ throw new PrivacyVaultError("privacy_vault_path_record_unreadable", err);
15837
+ }
15838
+ }
15839
+ async writePathRecord(key, record) {
15840
+ const encrypted = encrypt(stringToBytes(JSON.stringify(record)), this.encryptionKey);
15841
+ await this.storage.write(
15842
+ VAULT_NAMESPACE,
15843
+ key,
15844
+ stringToBytes(JSON.stringify(encrypted))
15845
+ );
15846
+ }
15847
+ hmacString(value) {
15848
+ return bytesToHex(hmacSha256(this.lookupKey, stringToBytes(value)));
15849
+ }
15850
+ };
15851
+ var PrivacyVaultError = class extends Error {
15852
+ code;
15853
+ cause;
15854
+ constructor(code, cause) {
15855
+ super(code);
15856
+ this.name = "PrivacyVaultError";
15857
+ this.code = code;
15858
+ this.cause = cause;
15859
+ }
15860
+ };
15861
+ function applyLocalPrivacyFilter(value, path = "$") {
15862
+ const findings = [];
15863
+ const filtered = filterValue(value, path, findings, 0);
15864
+ return { value: filtered, findings };
15865
+ }
15866
+ async function applyPrivacyPlaceholders(value, vault, scope = "default", path = "$") {
15867
+ const findings = [];
15868
+ const filtered = await placeholderValue(value, path, findings, vault, scope, 0);
15869
+ return { value: filtered, findings };
15870
+ }
15871
+ async function applyOpenAIPrivacyFilterResult(result, vault, scope = "default", path = "$") {
15872
+ const text = result.text;
15873
+ const findings = [];
15874
+ const spans = result.detected_spans.map((span) => ({
15875
+ ...span,
15876
+ class: mapOpenAIPrivacyLabel(span.label)
15877
+ })).filter(
15878
+ (span) => span.class !== null && Number.isInteger(span.start) && Number.isInteger(span.end) && span.start >= 0 && span.end > span.start && span.end <= text.length
15879
+ ).sort((a, b) => a.start - b.start);
15880
+ let cursor = 0;
15881
+ const pieces = [];
15882
+ for (const span of spans) {
15883
+ if (span.start < cursor) continue;
15884
+ const raw = text.slice(span.start, span.end);
15885
+ const placeholder = await vault.placeholderFor(span.class, raw, scope);
15886
+ pieces.push(text.slice(cursor, span.start));
15887
+ pieces.push(placeholder);
15888
+ cursor = span.end;
15889
+ findings.push({
15890
+ path,
15891
+ class: span.class,
15892
+ action: "placeholder",
15893
+ placeholder
15894
+ });
15895
+ }
15896
+ pieces.push(text.slice(cursor));
15897
+ return {
15898
+ value: pieces.join(""),
15899
+ findings
15900
+ };
15901
+ }
15902
+ function detectSensitiveSpans(input, options = {}) {
15903
+ const spans = [];
15904
+ addConfiguredTermSpans(
15905
+ spans,
15906
+ input,
15907
+ options.clientNames ?? [],
15908
+ "client",
15909
+ "client",
15910
+ "CLIENT"
15911
+ );
15912
+ addConfiguredTermSpans(
15913
+ spans,
15914
+ input,
15915
+ options.projectNames ?? [],
15916
+ "project",
15917
+ "project",
15918
+ "PROJECT"
15919
+ );
15920
+ addConfiguredTermSpans(
15921
+ spans,
15922
+ input,
15923
+ options.domainTerms ?? [],
15924
+ "domain_term",
15925
+ "domain_term",
15926
+ "TERM"
15927
+ );
15928
+ addConfiguredTermSpans(
15929
+ spans,
15930
+ input,
15931
+ options.personNames ?? [],
15932
+ "person",
15933
+ "person",
15934
+ "PERSON"
15935
+ );
15936
+ const pathHint = options.pathHint?.toLowerCase() ?? "";
15937
+ if (input.length <= 120 && /(?:^|[^a-z0-9])(?:name|owner|recipient|contact|person)(?:$|[^a-z0-9])/.test(pathHint) && /^[A-Z][A-Za-z'-]+(?:\s+[A-Z][A-Za-z'-]+){1,3}$/.test(input.trim())) {
15938
+ const start = input.indexOf(input.trim());
15939
+ spans.push({
15940
+ class: "person",
15941
+ detectorClass: "person",
15942
+ start,
15943
+ end: start + input.trim().length,
15944
+ text: input.trim(),
15945
+ placeholderPrefix: "PERSON"
15946
+ });
15947
+ }
15948
+ for (const pattern of SPAN_PATTERNS) {
15949
+ pattern.pattern.lastIndex = 0;
15950
+ for (const match of input.matchAll(pattern.pattern)) {
15951
+ if (match.index === void 0 || match[0].length === 0) continue;
15952
+ spans.push({
15953
+ class: pattern.class,
15954
+ detectorClass: pattern.detectorClass ?? detectorClassForSpan(pattern.class),
15955
+ start: match.index,
15956
+ end: match.index + match[0].length,
15957
+ text: match[0],
15958
+ placeholderPrefix: pattern.placeholderPrefix
15959
+ });
15960
+ }
15961
+ }
15962
+ return removeOverlappingSpans(spans);
15963
+ }
15964
+ function detectorClassForSpan(spanClass) {
15965
+ switch (spanClass) {
15966
+ case "client":
15967
+ return "client";
15968
+ case "project":
15969
+ return "project";
15970
+ case "secret":
15971
+ case "secret_assignment":
15972
+ return "secret";
15973
+ case "credential":
15974
+ return "credential";
15975
+ case "account_number":
15976
+ case "credit_card":
15977
+ return "account";
15978
+ case "file_path":
15979
+ return "file_path";
15980
+ case "domain_term":
15981
+ return "domain_term";
15982
+ case "custom":
15983
+ return "custom";
15984
+ case "email":
15985
+ case "phone":
15986
+ case "ssn":
15987
+ case "address":
15988
+ case "person":
15989
+ case "url":
15990
+ case "date":
15991
+ default:
15992
+ return "person";
15993
+ }
15994
+ }
15995
+ function placeholderPrefixFor(spanClass) {
15996
+ return SPAN_PATTERNS.find((p) => p.class === spanClass)?.placeholderPrefix ?? OPENAI_LABEL_PREFIXES[spanClass] ?? CONTRACT_CLASS_PREFIXES[spanClass] ?? "PRIVATE";
15997
+ }
15998
+ function filterValue(value, path, findings, depth) {
15999
+ if (depth > MAX_DEPTH) return value;
16000
+ if (typeof value === "string") {
16001
+ return filterString(value, path, findings);
16002
+ }
16003
+ if (Array.isArray(value)) {
16004
+ return value.map(
16005
+ (item, index) => filterValue(item, `${path}[${index}]`, findings, depth + 1)
16006
+ );
16007
+ }
16008
+ if (value && typeof value === "object") {
16009
+ const filtered = {};
16010
+ for (const [key, child] of Object.entries(value)) {
16011
+ filtered[key] = filterValue(child, `${path}.${key}`, findings, depth + 1);
16012
+ }
16013
+ return filtered;
16014
+ }
16015
+ return value;
16016
+ }
16017
+ function filterString(input, path, findings) {
16018
+ let output = input;
16019
+ for (const span of SPAN_PATTERNS) {
16020
+ const before = output;
16021
+ output = output.replace(span.pattern, span.replacement);
16022
+ if (output !== before) {
16023
+ findings.push({ path, class: span.class, action: "redact" });
16024
+ }
16025
+ }
16026
+ return output;
16027
+ }
16028
+ async function placeholderValue(value, path, findings, vault, scope, depth) {
16029
+ if (depth > MAX_DEPTH) return value;
16030
+ if (typeof value === "string") {
16031
+ return placeholderString(value, path, findings, vault, scope);
16032
+ }
16033
+ if (Array.isArray(value)) {
16034
+ const out = [];
16035
+ for (let index = 0; index < value.length; index++) {
16036
+ out.push(await placeholderValue(
16037
+ value[index],
16038
+ `${path}[${index}]`,
16039
+ findings,
16040
+ vault,
16041
+ scope,
16042
+ depth + 1
16043
+ ));
16044
+ }
16045
+ return out;
16046
+ }
16047
+ if (value && typeof value === "object") {
16048
+ const filtered = {};
16049
+ for (const [key, child] of Object.entries(value)) {
16050
+ filtered[key] = await placeholderValue(
16051
+ child,
16052
+ `${path}.${key}`,
16053
+ findings,
16054
+ vault,
16055
+ scope,
16056
+ depth + 1
16057
+ );
16058
+ }
16059
+ return filtered;
16060
+ }
16061
+ return value;
16062
+ }
16063
+ async function placeholderString(input, path, findings, vault, scope) {
16064
+ const spans = detectSensitiveSpans(input, { pathHint: path });
16065
+ if (spans.length === 0) return input;
16066
+ let cursor = 0;
16067
+ const pieces = [];
16068
+ for (const span of spans) {
16069
+ const placeholder = await vault.placeholderFor(span.class, span.text, scope);
16070
+ pieces.push(input.slice(cursor, span.start));
16071
+ pieces.push(placeholder);
16072
+ cursor = span.end;
16073
+ findings.push({
16074
+ path,
16075
+ class: span.class,
16076
+ action: "placeholder",
16077
+ placeholder
16078
+ });
16079
+ }
16080
+ pieces.push(input.slice(cursor));
16081
+ return pieces.join("");
16082
+ }
16083
+ var OPENAI_LABEL_PREFIXES = {
16084
+ account_number: "ACCOUNT",
16085
+ address: "ADDRESS",
16086
+ client: "CLIENT",
16087
+ credential: "CREDENTIAL",
16088
+ domain_term: "TERM",
16089
+ person: "PERSON",
16090
+ project: "PROJECT",
16091
+ file_path: "FILE_PATH",
16092
+ secret: "SECRET",
16093
+ url: "URL",
16094
+ date: "DATE"
16095
+ };
16096
+ var CONTRACT_CLASS_PREFIXES = {
16097
+ account_number: "ACCOUNT",
16098
+ client: "CLIENT",
16099
+ credential: "CREDENTIAL",
16100
+ domain_term: "TERM",
16101
+ file_path: "FILE_PATH",
16102
+ project: "PROJECT",
16103
+ secret: "SECRET"
16104
+ };
16105
+ function addConfiguredTermSpans(spans, input, terms, spanClass, detectorClass, placeholderPrefix) {
16106
+ const sortedTerms = [...new Set(terms.map((term) => term.trim()).filter(Boolean))].sort((a, b) => b.length - a.length);
16107
+ for (const term of sortedTerms) {
16108
+ const pattern = new RegExp(escapeRegExp(term), "gi");
16109
+ for (const match of input.matchAll(pattern)) {
16110
+ if (match.index === void 0 || match[0].length === 0) continue;
16111
+ spans.push({
16112
+ class: spanClass,
16113
+ detectorClass,
16114
+ start: match.index,
16115
+ end: match.index + match[0].length,
16116
+ text: match[0],
16117
+ placeholderPrefix
16118
+ });
16119
+ }
16120
+ }
16121
+ }
16122
+ function removeOverlappingSpans(spans) {
16123
+ const sorted = spans.sort((a, b) => {
16124
+ if (a.start !== b.start) return a.start - b.start;
16125
+ return b.end - b.start - (a.end - a.start);
16126
+ });
16127
+ const accepted = [];
16128
+ let cursor = -1;
16129
+ for (const span of sorted) {
16130
+ if (span.start < cursor) continue;
16131
+ accepted.push(span);
16132
+ cursor = span.end;
16133
+ }
16134
+ return accepted;
16135
+ }
16136
+ function escapeRegExp(input) {
16137
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16138
+ }
16139
+ function bytesToHex(bytes) {
16140
+ return Buffer.from(bytes).toString("hex");
16141
+ }
16142
+ function mapOpenAIPrivacyLabel(label) {
16143
+ switch (label) {
16144
+ case "account_number":
16145
+ return "account_number";
16146
+ case "private_address":
16147
+ return "address";
16148
+ case "private_email":
16149
+ return "email";
16150
+ case "private_person":
16151
+ return "person";
16152
+ case "private_phone":
16153
+ return "phone";
16154
+ case "private_url":
16155
+ return "url";
16156
+ case "private_date":
16157
+ return "date";
16158
+ case "secret":
16159
+ return "secret_assignment";
16160
+ case "redacted":
16161
+ return "secret_assignment";
16162
+ default:
16163
+ return null;
16164
+ }
16165
+ }
16166
+
16167
+ // src/l2-operational/context-gate-enforcer.ts
15324
16168
  var BUILTIN_SENSITIVE_PATTERNS = [
15325
16169
  "*_key",
15326
16170
  "*_token",
@@ -15346,6 +16190,7 @@ var BUILTIN_SENSITIVE_PATTERNS = [
15346
16190
  var ContextGateEnforcer = class {
15347
16191
  policyStore;
15348
16192
  auditLog;
16193
+ privacyVault;
15349
16194
  config;
15350
16195
  stats = {
15351
16196
  calls_inspected: 0,
@@ -15355,9 +16200,10 @@ var ContextGateEnforcer = class {
15355
16200
  fields_blocked: 0,
15356
16201
  calls_blocked: 0
15357
16202
  };
15358
- constructor(policyStore, auditLog, config) {
16203
+ constructor(policyStore, auditLog, config, privacyVault) {
15359
16204
  this.policyStore = policyStore;
15360
16205
  this.auditLog = auditLog;
16206
+ this.privacyVault = privacyVault;
15361
16207
  this.config = config;
15362
16208
  }
15363
16209
  /**
@@ -15434,6 +16280,7 @@ var ContextGateEnforcer = class {
15434
16280
  }
15435
16281
  }
15436
16282
  const filteredArgs = this.buildFilteredArgs(args, result.decisions);
16283
+ const privacyFiltered = this.privacyVault ? await applyPrivacyPlaceholders(filteredArgs, this.privacyVault, policy.policy_id) : applyLocalPrivacyFilter(filteredArgs);
15437
16284
  if (this.config.log_only) {
15438
16285
  this.auditLog.append(
15439
16286
  "l2",
@@ -15447,6 +16294,8 @@ var ContextGateEnforcer = class {
15447
16294
  fields_redacted: result.fields_redacted,
15448
16295
  fields_hashed: result.fields_hashed,
15449
16296
  fields_blocked: deniedFields.length,
16297
+ privacy_findings: privacyFiltered.findings.length,
16298
+ privacy_classes: [...new Set(privacyFiltered.findings.map((f) => f.class))],
15450
16299
  original_context_hash: result.original_context_hash
15451
16300
  }
15452
16301
  );
@@ -15467,13 +16316,15 @@ var ContextGateEnforcer = class {
15467
16316
  fields_redacted: result.fields_redacted,
15468
16317
  fields_hashed: result.fields_hashed,
15469
16318
  fields_blocked: deniedFields.length,
16319
+ privacy_findings: privacyFiltered.findings.length,
16320
+ privacy_classes: [...new Set(privacyFiltered.findings.map((f) => f.class))],
15470
16321
  original_context_hash: result.original_context_hash
15471
16322
  }
15472
16323
  );
15473
16324
  this.stats.fields_redacted += result.fields_redacted;
15474
16325
  this.stats.fields_hashed += result.fields_hashed;
15475
16326
  this.stats.fields_blocked += deniedFields.length;
15476
- return originalHandler(filteredArgs);
16327
+ return originalHandler(privacyFiltered.value);
15477
16328
  }
15478
16329
  /**
15479
16330
  * Filter tool arguments using built-in sensitive patterns.
@@ -15490,6 +16341,39 @@ var ContextGateEnforcer = class {
15490
16341
  }
15491
16342
  }
15492
16343
  if (fieldsToRedact.length === 0) {
16344
+ const privacyFiltered2 = this.privacyVault ? await applyPrivacyPlaceholders(args, this.privacyVault, `builtin:${toolName}`) : applyLocalPrivacyFilter(args);
16345
+ if (privacyFiltered2.findings.length > 0) {
16346
+ const filteredHash2 = hashToString(
16347
+ stringToBytes(JSON.stringify(privacyFiltered2.value))
16348
+ );
16349
+ if (this.config.log_only) {
16350
+ this.auditLog.append(
16351
+ "l2",
16352
+ "context_gate_enforcer_builtin_privacy_log_only",
16353
+ "system",
16354
+ {
16355
+ tool_name: toolName,
16356
+ privacy_findings: privacyFiltered2.findings.length,
16357
+ privacy_classes: [...new Set(privacyFiltered2.findings.map((f) => f.class))],
16358
+ original_context_hash: originalHash
16359
+ }
16360
+ );
16361
+ return originalHandler(args);
16362
+ }
16363
+ this.auditLog.append(
16364
+ "l2",
16365
+ "context_gate_enforcer_builtin_privacy_filter",
16366
+ "system",
16367
+ {
16368
+ tool_name: toolName,
16369
+ privacy_findings: privacyFiltered2.findings.length,
16370
+ privacy_classes: [...new Set(privacyFiltered2.findings.map((f) => f.class))],
16371
+ original_context_hash: originalHash,
16372
+ filtered_context_hash: filteredHash2
16373
+ }
16374
+ );
16375
+ return originalHandler(privacyFiltered2.value);
16376
+ }
15493
16377
  this.auditLog.append(
15494
16378
  "l2",
15495
16379
  "context_gate_enforcer_builtin_pass",
@@ -15509,8 +16393,9 @@ var ContextGateEnforcer = class {
15509
16393
  filteredArgs[key] = value;
15510
16394
  }
15511
16395
  }
16396
+ const privacyFiltered = this.privacyVault ? await applyPrivacyPlaceholders(filteredArgs, this.privacyVault, `builtin:${toolName}`) : applyLocalPrivacyFilter(filteredArgs);
15512
16397
  const filteredHash = hashToString(
15513
- stringToBytes(JSON.stringify(filteredArgs))
16398
+ stringToBytes(JSON.stringify(privacyFiltered.value))
15514
16399
  );
15515
16400
  if (this.config.log_only) {
15516
16401
  this.auditLog.append(
@@ -15521,6 +16406,8 @@ var ContextGateEnforcer = class {
15521
16406
  tool_name: toolName,
15522
16407
  fields_redacted: fieldsToRedact.length,
15523
16408
  redacted_fields: fieldsToRedact,
16409
+ privacy_findings: privacyFiltered.findings.length,
16410
+ privacy_classes: [...new Set(privacyFiltered.findings.map((f) => f.class))],
15524
16411
  original_context_hash: originalHash
15525
16412
  }
15526
16413
  );
@@ -15535,12 +16422,14 @@ var ContextGateEnforcer = class {
15535
16422
  tool_name: toolName,
15536
16423
  fields_redacted: fieldsToRedact.length,
15537
16424
  redacted_fields: fieldsToRedact,
16425
+ privacy_findings: privacyFiltered.findings.length,
16426
+ privacy_classes: [...new Set(privacyFiltered.findings.map((f) => f.class))],
15538
16427
  original_context_hash: originalHash,
15539
16428
  filtered_context_hash: filteredHash
15540
16429
  }
15541
16430
  );
15542
16431
  this.stats.fields_redacted += fieldsToRedact.length;
15543
- return originalHandler(filteredArgs);
16432
+ return originalHandler(privacyFiltered.value);
15544
16433
  }
15545
16434
  /**
15546
16435
  * Check if a tool should be filtered based on bypass prefixes.
@@ -15646,21 +16535,166 @@ var ContextGateEnforcer = class {
15646
16535
  };
15647
16536
  }
15648
16537
  };
15649
-
15650
- // src/l2-operational/context-gate-tools.ts
15651
- function createContextGateTools(storage, masterKey, auditLog) {
15652
- const policyStore = new ContextGatePolicyStore(storage, masterKey);
15653
- const enforcerConfig = {
15654
- enabled: false,
15655
- // Off by default; agents must explicitly enable it
15656
- bypass_prefixes: ["*"],
15657
- // Skip all Sanctuary-internal tools; only proxy/ tools get filtered
15658
- log_only: false,
15659
- // Filter immediately
16538
+ var PrivacyFilterRuntimeError = class extends Error {
16539
+ constructor(message) {
16540
+ super(message);
16541
+ this.name = "PrivacyFilterRuntimeError";
16542
+ }
16543
+ };
16544
+ async function applyConfiguredPrivacyFilter(value, vault, scope, config) {
16545
+ if (config.mode === "off") {
16546
+ return { value, findings: [], mode: "off" };
16547
+ }
16548
+ if (config.mode === "local") {
16549
+ const result = await applyPrivacyPlaceholders(value, vault, scope);
16550
+ return { ...result, mode: "local" };
16551
+ }
16552
+ try {
16553
+ const result = await applyOpenAIPrivacyFilter(value, vault, scope, config);
16554
+ return { ...result, mode: "opf" };
16555
+ } catch (err) {
16556
+ if (config.fail_mode === "fallback") {
16557
+ const result = await applyPrivacyPlaceholders(value, vault, scope);
16558
+ return { ...result, mode: "local", fallback_from: "opf" };
16559
+ }
16560
+ throw err;
16561
+ }
16562
+ }
16563
+ async function applyOpenAIPrivacyFilter(value, vault, scope, config) {
16564
+ if (typeof value === "string") {
16565
+ const opf = await runOpenAIPrivacyFilter(value, config);
16566
+ const filtered = await applyOpenAIPrivacyFilterResult(opf, vault, scope);
16567
+ return filtered;
16568
+ }
16569
+ if (Array.isArray(value)) {
16570
+ const findings = [];
16571
+ const out = [];
16572
+ for (let index = 0; index < value.length; index++) {
16573
+ const filtered = await applyOpenAIPrivacyFilter(
16574
+ value[index],
16575
+ vault,
16576
+ scope,
16577
+ config
16578
+ );
16579
+ out.push(filtered.value);
16580
+ findings.push(...filtered.findings.map((f) => ({
16581
+ ...f,
16582
+ path: `$[${index}]${f.path === "$" ? "" : f.path.slice(1)}`
16583
+ })));
16584
+ }
16585
+ return { value: out, findings };
16586
+ }
16587
+ if (value && typeof value === "object") {
16588
+ const findings = [];
16589
+ const out = {};
16590
+ for (const [key, child] of Object.entries(value)) {
16591
+ const filtered = await applyOpenAIPrivacyFilter(child, vault, scope, config);
16592
+ out[key] = filtered.value;
16593
+ findings.push(...filtered.findings.map((f) => ({
16594
+ ...f,
16595
+ path: `$.${key}${f.path === "$" ? "" : f.path.slice(1)}`
16596
+ })));
16597
+ }
16598
+ return { value: out, findings };
16599
+ }
16600
+ return { value, findings: [] };
16601
+ }
16602
+ var OPF_STDOUT_MAX_BYTES = 1e7;
16603
+ async function runOpenAIPrivacyFilter(text, config) {
16604
+ const stdout = await runCommand(config.command, text, config.timeout_ms);
16605
+ if (Buffer.byteLength(stdout, "utf8") > OPF_STDOUT_MAX_BYTES) {
16606
+ throw new PrivacyFilterRuntimeError(
16607
+ `OpenAI privacy-filter stdout exceeded ${OPF_STDOUT_MAX_BYTES}-byte cap`
16608
+ );
16609
+ }
16610
+ let parsed;
16611
+ try {
16612
+ parsed = JSON.parse(stdout);
16613
+ } catch {
16614
+ throw new PrivacyFilterRuntimeError("OpenAI privacy-filter returned invalid JSON");
16615
+ }
16616
+ if (!isOpenAIPrivacyFilterResult(parsed)) {
16617
+ throw new PrivacyFilterRuntimeError(
16618
+ "OpenAI privacy-filter JSON did not match the expected span schema"
16619
+ );
16620
+ }
16621
+ return parsed;
16622
+ }
16623
+ function runCommand(command, input, timeoutMs) {
16624
+ return new Promise((resolve4, reject) => {
16625
+ const child = spawn(command, [], {
16626
+ stdio: ["pipe", "pipe", "pipe"],
16627
+ shell: false
16628
+ });
16629
+ let stdout = "";
16630
+ let stderr = "";
16631
+ const timer = setTimeout(() => {
16632
+ child.kill("SIGTERM");
16633
+ reject(new PrivacyFilterRuntimeError("OpenAI privacy-filter timed out"));
16634
+ }, timeoutMs);
16635
+ child.stdout.setEncoding("utf8");
16636
+ child.stderr.setEncoding("utf8");
16637
+ child.stdout.on("data", (chunk) => {
16638
+ stdout += chunk;
16639
+ });
16640
+ child.stderr.on("data", (chunk) => {
16641
+ stderr += chunk;
16642
+ });
16643
+ child.on("error", (err) => {
16644
+ clearTimeout(timer);
16645
+ reject(new PrivacyFilterRuntimeError(`OpenAI privacy-filter failed to start: ${err.message}`));
16646
+ });
16647
+ child.on("close", (code) => {
16648
+ clearTimeout(timer);
16649
+ if (code !== 0) {
16650
+ reject(new PrivacyFilterRuntimeError(
16651
+ `OpenAI privacy-filter exited with code ${code}: ${stderr.trim()}`
16652
+ ));
16653
+ return;
16654
+ }
16655
+ resolve4(stdout);
16656
+ });
16657
+ child.stdin.end(input);
16658
+ });
16659
+ }
16660
+ function isOpenAIPrivacyFilterResult(value) {
16661
+ if (!value || typeof value !== "object") return false;
16662
+ const v = value;
16663
+ if (typeof v.text !== "string") return false;
16664
+ if (!Array.isArray(v.detected_spans)) return false;
16665
+ return v.detected_spans.every((span) => {
16666
+ if (!span || typeof span !== "object") return false;
16667
+ const s = span;
16668
+ return typeof s.label === "string" && typeof s.start === "number" && typeof s.end === "number" && typeof s.text === "string";
16669
+ });
16670
+ }
16671
+
16672
+ // src/l2-operational/context-gate-tools.ts
16673
+ function createContextGateTools(storage, masterKey, auditLog, options = {}) {
16674
+ const policyStore = new ContextGatePolicyStore(storage, masterKey);
16675
+ const privacyVault = new PrivacyPlaceholderVault(storage, masterKey);
16676
+ const privacyFilterConfig = options.privacyFilter ?? {
16677
+ mode: "local",
16678
+ fail_mode: "closed",
16679
+ command: "opf",
16680
+ timeout_ms: 5e3
16681
+ };
16682
+ const enforcerConfig = {
16683
+ enabled: false,
16684
+ // Off by default; agents must explicitly enable it
16685
+ bypass_prefixes: ["*"],
16686
+ // Skip all Sanctuary-internal tools; only proxy/ tools get filtered
16687
+ log_only: false,
16688
+ // Filter immediately
15660
16689
  on_deny: "block"
15661
16690
  // Block requests with denied fields
15662
16691
  };
15663
- const enforcer = new ContextGateEnforcer(policyStore, auditLog, enforcerConfig);
16692
+ const enforcer = new ContextGateEnforcer(
16693
+ policyStore,
16694
+ auditLog,
16695
+ enforcerConfig,
16696
+ privacyVault
16697
+ );
15664
16698
  const tools = [
15665
16699
  // ── Set Policy ──────────────────────────────────────────────────
15666
16700
  {
@@ -15954,6 +16988,34 @@ function createContextGateTools(storage, masterKey, auditLog) {
15954
16988
  break;
15955
16989
  }
15956
16990
  }
16991
+ let privacyFiltered;
16992
+ try {
16993
+ privacyFiltered = await applyConfiguredPrivacyFilter(
16994
+ safeContext,
16995
+ privacyVault,
16996
+ policyId,
16997
+ privacyFilterConfig
16998
+ );
16999
+ } catch (err) {
17000
+ if (err instanceof PrivacyFilterRuntimeError) {
17001
+ auditLog.append("l2", "context_gate_privacy_filter_failure", policy.identity_id ?? "system", {
17002
+ policy_id: policyId,
17003
+ provider,
17004
+ mode: privacyFilterConfig.mode,
17005
+ fail_mode: privacyFilterConfig.fail_mode,
17006
+ original_context_hash: result.original_context_hash,
17007
+ error: err.message
17008
+ }, "failure");
17009
+ return toolResult({
17010
+ blocked: true,
17011
+ error: "privacy_filter_failed",
17012
+ message: err.message,
17013
+ mode: privacyFilterConfig.mode,
17014
+ recommendation: privacyFilterConfig.fail_mode === "closed" ? "Install/configure the local privacy filter or switch SANCTUARY_PRIVACY_FILTER_FAIL_MODE to fallback." : "Check the local privacy filter configuration."
17015
+ });
17016
+ }
17017
+ throw err;
17018
+ }
15957
17019
  auditLog.append("l2", "context_gate_filter", policy.identity_id ?? "system", {
15958
17020
  policy_id: policyId,
15959
17021
  provider,
@@ -15962,20 +17024,32 @@ function createContextGateTools(storage, masterKey, auditLog) {
15962
17024
  fields_redacted: result.fields_redacted,
15963
17025
  fields_hashed: result.fields_hashed,
15964
17026
  fields_summarized: result.fields_summarized,
17027
+ privacy_findings: privacyFiltered.findings.length,
17028
+ privacy_classes: [...new Set(privacyFiltered.findings.map((f) => f.class))],
17029
+ privacy_filter_mode: privacyFiltered.mode,
17030
+ privacy_filter_configured_mode: privacyFilterConfig.mode,
17031
+ privacy_filter_fallback_from: privacyFiltered.fallback_from,
15965
17032
  original_context_hash: result.original_context_hash,
15966
17033
  filtered_context_hash: result.filtered_context_hash
15967
17034
  });
15968
17035
  return toolResult({
15969
17036
  blocked: false,
15970
- safe_context: safeContext,
17037
+ safe_context: privacyFiltered.value,
15971
17038
  summary: {
15972
17039
  total_fields: Object.keys(context).length,
15973
17040
  allowed: result.fields_allowed,
15974
17041
  redacted: result.fields_redacted,
15975
17042
  hashed: result.fields_hashed,
15976
- summarized: result.fields_summarized
17043
+ summarized: result.fields_summarized,
17044
+ privacy_filtered_spans: privacyFiltered.findings.length
15977
17045
  },
15978
17046
  decisions: result.decisions,
17047
+ privacy_filter: {
17048
+ mode: privacyFiltered.mode,
17049
+ configured_mode: privacyFilterConfig.mode,
17050
+ fallback_from: privacyFiltered.fallback_from,
17051
+ findings: privacyFiltered.findings
17052
+ },
15979
17053
  audit: {
15980
17054
  original_context_hash: result.original_context_hash,
15981
17055
  filtered_context_hash: result.filtered_context_hash,
@@ -17256,6 +18330,55 @@ var ProxyRouter = class {
17256
18330
  return toolResult(govResult.cached_result ?? {});
17257
18331
  }
17258
18332
  }
18333
+ let privacyPolicy = null;
18334
+ let privacyDestination = "tool-api";
18335
+ let outboundFiltered = false;
18336
+ if (this.options.privacyEnforcement) {
18337
+ const serverConfig = this.clientManager.getServerConfig(serverName);
18338
+ privacyDestination = serverConfig?.destination_category ?? "tool-api";
18339
+ const identityId = serverConfig?.privacy_identity_id;
18340
+ try {
18341
+ privacyPolicy = await this.options.privacyEnforcement.policyResolver(
18342
+ serverName,
18343
+ identityId
18344
+ );
18345
+ } catch {
18346
+ privacyPolicy = null;
18347
+ }
18348
+ const decision = await this.options.privacyEnforcement.engine.filterOutbound({
18349
+ payload: filteredArgs,
18350
+ policy: privacyPolicy,
18351
+ identity_id: identityId,
18352
+ agent_id: `proxy:${serverName}`,
18353
+ destination_category: privacyDestination,
18354
+ audit_log: this.auditLog
18355
+ });
18356
+ if (decision.status === "denied") {
18357
+ this.auditLog.append("l2", `proxy_privacy_denied:${proxyName}`, "system", {
18358
+ server: serverName,
18359
+ tool: toolName,
18360
+ tier,
18361
+ denial_reason_class: decision.audit_payload.denial_reason_class,
18362
+ latency_ms: Date.now() - start
18363
+ }, "failure");
18364
+ this.notifyProxyCall(
18365
+ proxyName,
18366
+ serverName,
18367
+ "blocked",
18368
+ "privacy_denied",
18369
+ tier
18370
+ );
18371
+ return toolResult({
18372
+ error: "Operation not permitted",
18373
+ proxy: true,
18374
+ privacy_denied: true
18375
+ });
18376
+ }
18377
+ if (decision.status === "filtered") {
18378
+ outboundFiltered = true;
18379
+ filteredArgs = decision.payload;
18380
+ }
18381
+ }
17259
18382
  const result = await this.callWithTimeout(
17260
18383
  serverName,
17261
18384
  toolName,
@@ -17274,6 +18397,26 @@ var ProxyRouter = class {
17274
18397
  latency_ms: latencyMs
17275
18398
  });
17276
18399
  this.notifyProxyCall(proxyName, serverName, "allowed", void 0, tier);
18400
+ if (outboundFiltered && this.options.privacyEnforcement && privacyPolicy) {
18401
+ const serverConfig = this.clientManager.getServerConfig(serverName);
18402
+ const identityId = serverConfig?.privacy_identity_id;
18403
+ const rehydrated = await this.options.privacyEnforcement.engine.rehydrateResponse({
18404
+ response: result,
18405
+ policy: privacyPolicy,
18406
+ identity_id: identityId,
18407
+ agent_id: `proxy:${serverName}`,
18408
+ destination_category: privacyDestination,
18409
+ audit_log: this.auditLog
18410
+ });
18411
+ if (rehydrated.status === "rehydrated") {
18412
+ return this.normalizeResponse(
18413
+ rehydrated.response
18414
+ );
18415
+ }
18416
+ return this.normalizeResponse(
18417
+ rehydrated.response
18418
+ );
18419
+ }
17277
18420
  return this.normalizeResponse(result);
17278
18421
  } catch (err) {
17279
18422
  const latencyMs = Date.now() - start;
@@ -17330,13 +18473,13 @@ var ProxyRouter = class {
17330
18473
  * Call an upstream tool with a timeout.
17331
18474
  */
17332
18475
  async callWithTimeout(serverName, toolName, args, timeoutMs) {
17333
- return new Promise((resolve, reject) => {
18476
+ return new Promise((resolve4, reject) => {
17334
18477
  const timer = setTimeout(() => {
17335
18478
  reject(new Error(`Upstream tool call timed out after ${timeoutMs}ms`));
17336
18479
  }, timeoutMs);
17337
18480
  this.clientManager.callTool(serverName, toolName, args).then((result) => {
17338
18481
  clearTimeout(timer);
17339
- resolve(result);
18482
+ resolve4(result);
17340
18483
  }).catch((err) => {
17341
18484
  clearTimeout(timer);
17342
18485
  reject(err);
@@ -17378,7 +18521,7 @@ var ProxyRouter = class {
17378
18521
  function strToBytes(s) {
17379
18522
  return new TextEncoder().encode(s);
17380
18523
  }
17381
- function bytesToHex(bytes) {
18524
+ function bytesToHex2(bytes) {
17382
18525
  let hex = "";
17383
18526
  for (let i = 0; i < bytes.length; i++) {
17384
18527
  hex += bytes[i].toString(16).padStart(2, "0");
@@ -17386,7 +18529,7 @@ function bytesToHex(bytes) {
17386
18529
  return hex;
17387
18530
  }
17388
18531
  function sha256Hex(input) {
17389
- return bytesToHex(sha256(strToBytes(input)));
18532
+ return bytesToHex2(sha256(strToBytes(input)));
17390
18533
  }
17391
18534
  var DEFAULT_CONFIG = {
17392
18535
  volume_limit: 200,
@@ -18100,18 +19243,18 @@ function createSanctuaryTools(opts) {
18100
19243
  const tagBytes = enc.encode(domainTag);
18101
19244
  const purposeBytes = enc.encode(purpose);
18102
19245
  const nonceBytes = enc.encode(nonce);
18103
- const sep = new Uint8Array([0]);
19246
+ const sep2 = new Uint8Array([0]);
18104
19247
  const message = new Uint8Array(
18105
19248
  tagBytes.length + 1 + purposeBytes.length + 1 + nonceBytes.length
18106
19249
  );
18107
19250
  let offset = 0;
18108
19251
  message.set(tagBytes, offset);
18109
19252
  offset += tagBytes.length;
18110
- message.set(sep, offset);
19253
+ message.set(sep2, offset);
18111
19254
  offset += 1;
18112
19255
  message.set(purposeBytes, offset);
18113
19256
  offset += purposeBytes.length;
18114
- message.set(sep, offset);
19257
+ message.set(sep2, offset);
18115
19258
  offset += 1;
18116
19259
  message.set(nonceBytes, offset);
18117
19260
  let sigB64;
@@ -20411,7 +21554,7 @@ function classifyAgentDescription(description) {
20411
21554
  }
20412
21555
 
20413
21556
  // src/compliance/eu_ai_act/generator.ts
20414
- function bytesToHex2(bytes) {
21557
+ function bytesToHex3(bytes) {
20415
21558
  let hex = "";
20416
21559
  for (let i = 0; i < bytes.length; i++) {
20417
21560
  hex += bytes[i].toString(16).padStart(2, "0");
@@ -20575,18 +21718,18 @@ function makeSigner(signer, masterKey) {
20575
21718
  const sig = sign(digest, signer.encrypted_private_key, encryptionKey);
20576
21719
  return toBase64url(sig);
20577
21720
  },
20578
- sha256Hex: (content) => bytesToHex2(hash(stringToBytes(content)))
21721
+ sha256Hex: (content) => bytesToHex3(hash(stringToBytes(content)))
20579
21722
  };
20580
21723
  }
20581
21724
  function finaliseDocument(template, context, filename, ds) {
20582
21725
  const content = render(template, context);
20583
- const sha256Hex2 = ds.sha256Hex(content);
21726
+ const sha256Hex4 = ds.sha256Hex(content);
20584
21727
  const signature = ds.signContent(content);
20585
21728
  return {
20586
21729
  filename,
20587
21730
  content,
20588
21731
  content_type: "text/markdown",
20589
- sha256: sha256Hex2,
21732
+ sha256: sha256Hex4,
20590
21733
  signature
20591
21734
  };
20592
21735
  }
@@ -21384,101 +22527,1565 @@ var MemoryStorage = class {
21384
22527
  }
21385
22528
  };
21386
22529
 
21387
- // src/dashboard/aggregator.ts
21388
- var L4_DEGRADATION_IMPACT = {
21389
- critical: 40,
21390
- warning: 25,
21391
- info: 10
22530
+ // src/contracts/v1.1/constants.ts
22531
+ var SIGNATURE_SCHEME_V1 = "ed25519-v1";
22532
+ var EXIT_BUNDLE_MANIFEST_VERSION = "SANCTUARY_EXIT_BUNDLE_V1";
22533
+ var EXIT_BUNDLE_ARTIFACT_KINDS = [
22534
+ "public_identity",
22535
+ "encrypted_state",
22536
+ "policy_set",
22537
+ "audit_receipts",
22538
+ "reputation_bundle",
22539
+ "commitments",
22540
+ "placeholder_vault_metadata"
22541
+ ];
22542
+
22543
+ // src/mesh/errors.ts
22544
+ var MeshError = class extends Error {
22545
+ constructor(message) {
22546
+ super(message);
22547
+ this.name = "MeshError";
22548
+ }
21392
22549
  };
21393
- function computeL4LayerScore(degradations, status) {
21394
- if (status === "compromised") return 0;
21395
- let score = 100;
21396
- for (const deg of degradations) {
21397
- score -= L4_DEGRADATION_IMPACT[deg.severity] ?? 10;
22550
+ var MeshEnvelopeError = class extends MeshError {
22551
+ constructor(message) {
22552
+ super(message);
22553
+ this.name = "MeshEnvelopeError";
21398
22554
  }
21399
- score = Math.max(0, score);
21400
- if (degradations.length === 0 && score > 50) {
21401
- score = Math.min(100, score + 5);
22555
+ };
22556
+ var MeshReservedExtensionKeyError = class extends MeshEnvelopeError {
22557
+ constructor(key) {
22558
+ super(
22559
+ `v0.1 emitters MUST NOT populate reserved extension_envelope key: ${key}`
22560
+ );
22561
+ this.name = "MeshReservedExtensionKeyError";
21402
22562
  }
21403
- return Math.round(score);
22563
+ };
22564
+ var MeshReservedEventTypeError = class extends MeshEnvelopeError {
22565
+ constructor(eventType) {
22566
+ super(
22567
+ `v0.1 emitters MUST NOT emit reserved-namespace event_type: ${eventType}`
22568
+ );
22569
+ this.name = "MeshReservedEventTypeError";
22570
+ }
22571
+ };
22572
+
22573
+ // src/mesh/canonical-json.ts
22574
+ var MeshCanonicalJsonError = class extends MeshError {
22575
+ constructor(message) {
22576
+ super(message);
22577
+ this.name = "MeshCanonicalJsonError";
22578
+ }
22579
+ };
22580
+ function canonicalize2(value) {
22581
+ if (value === void 0) {
22582
+ throw new MeshCanonicalJsonError(
22583
+ "canonicalize(): top-level undefined is not serializable"
22584
+ );
22585
+ }
22586
+ return encode(value);
21404
22587
  }
21405
- var MAX_ACTIVITY = 50;
21406
- var MAX_AUDIT = 50;
21407
- function fingerprintDID(did) {
21408
- const raw = did.replace(/^did:[a-z0-9]+:/i, "");
21409
- if (raw.length <= 12) return raw;
21410
- return `${raw.slice(0, 6)}\u2026${raw.slice(-6)}`;
22588
+ function encode(value) {
22589
+ if (value === null) return "null";
22590
+ if (typeof value === "boolean") return value ? "true" : "false";
22591
+ if (typeof value === "number") {
22592
+ if (!Number.isFinite(value)) {
22593
+ throw new MeshCanonicalJsonError(
22594
+ `canonicalize(): non-finite number (${String(value)}) is not serializable`
22595
+ );
22596
+ }
22597
+ return JSON.stringify(value);
22598
+ }
22599
+ if (typeof value === "string") return JSON.stringify(value);
22600
+ if (Array.isArray(value)) return encodeArray(value);
22601
+ if (typeof value === "object") return encodeObject(value);
22602
+ throw new MeshCanonicalJsonError(
22603
+ `canonicalize(): unsupported type ${typeof value}`
22604
+ );
21411
22605
  }
21412
- function countInjectionsToday(audit) {
21413
- const startOfDay = /* @__PURE__ */ new Date();
21414
- startOfDay.setHours(0, 0, 0, 0);
21415
- const cutoff = startOfDay.getTime();
21416
- return audit.filter((e) => {
21417
- const ts = new Date(e.timestamp).getTime();
21418
- if (isNaN(ts) || ts < cutoff) return false;
21419
- const op = (e.operation ?? "").toLowerCase();
21420
- return op.includes("injection") || op.includes("blocked");
21421
- }).length;
22606
+ function encodeArray(arr) {
22607
+ const parts = [];
22608
+ for (const item of arr) {
22609
+ parts.push(item === void 0 ? "null" : encode(item));
22610
+ }
22611
+ return "[" + parts.join(",") + "]";
21422
22612
  }
21423
- var PROOF_CREATION_OPS = /* @__PURE__ */ new Set([
21424
- "zk_prove",
21425
- "zk_range_prove",
21426
- "proof_commitment"
22613
+ function encodeObject(obj) {
22614
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
22615
+ const parts = [];
22616
+ for (const k of keys) {
22617
+ parts.push(JSON.stringify(k) + ":" + encode(obj[k]));
22618
+ }
22619
+ return "{" + parts.join(",") + "}";
22620
+ }
22621
+ function canonicalizeToBytes(value) {
22622
+ return new TextEncoder().encode(canonicalize2(value));
22623
+ }
22624
+
22625
+ // src/exit/bundle.ts
22626
+ init_hashing();
22627
+ init_encoding();
22628
+ init_encryption();
22629
+ init_identity();
22630
+
22631
+ // src/contracts/v1.1/exit-bundle-manifest.ts
22632
+ var EXIT_BUNDLE_PATH_PATTERN = /^[a-z0-9._-]+(?:\/[a-z0-9._-]+)*$/;
22633
+ var EXIT_BUNDLE_PATH_MAX_BYTES = 256;
22634
+
22635
+ // src/exit/verifier.ts
22636
+ init_encoding();
22637
+ init_hashing();
22638
+ var PRIVATE_MATERIAL_KEYS = /* @__PURE__ */ new Set([
22639
+ "private_key",
22640
+ "privatekey",
22641
+ "encrypted_private_key",
22642
+ "encryptedprivatekey",
22643
+ "passphrase",
22644
+ "recovery_key",
22645
+ "recoverykey",
22646
+ "seed",
22647
+ "mnemonic"
21427
22648
  ]);
21428
- function countProofsToday(audit) {
21429
- const startOfDay = /* @__PURE__ */ new Date();
21430
- startOfDay.setHours(0, 0, 0, 0);
21431
- const cutoff = startOfDay.getTime();
21432
- return audit.filter((e) => {
21433
- if (e.layer !== "l3") return false;
21434
- if (!PROOF_CREATION_OPS.has(e.operation)) return false;
21435
- const ts = new Date(e.timestamp).getTime();
21436
- return !isNaN(ts) && ts >= cutoff;
21437
- }).length;
22649
+ function sha256Hex2(bytes) {
22650
+ return Array.from(hash(bytes)).map((b) => b.toString(16).padStart(2, "0")).join("");
21438
22651
  }
21439
- function buildAgent(sources) {
21440
- if (!sources.identityManager) {
21441
- return {
21442
- display_name: "Unclaimed agent",
21443
- did: null,
21444
- did_fingerprint: null,
21445
- identity_count: 0,
21446
- primary_identity_id: null
21447
- };
21448
- }
21449
- const primary = sources.identityManager.getDefault();
21450
- const identities = sources.identityManager.list();
21451
- if (!primary) {
21452
- return {
21453
- display_name: "Unclaimed agent",
21454
- did: null,
21455
- did_fingerprint: null,
21456
- identity_count: identities.length,
21457
- primary_identity_id: null
21458
- };
21459
- }
22652
+ function resultBase(bundleDir, manifest, warnings = [], unsupported = []) {
22653
+ const body = manifest?.body;
21460
22654
  return {
21461
- display_name: primary.label || "Sovereign agent",
21462
- did: primary.did,
21463
- did_fingerprint: fingerprintDID(primary.did),
21464
- identity_count: identities.length,
21465
- primary_identity_id: primary.identity_id
22655
+ version: "1.1",
22656
+ passed: false,
22657
+ verified_at: (/* @__PURE__ */ new Date()).toISOString(),
22658
+ manifest_path: join(bundleDir, "manifest.json"),
22659
+ manifest_hash: null,
22660
+ manifest_summary: {
22661
+ manifest_version: body?.manifest_version ?? EXIT_BUNDLE_MANIFEST_VERSION,
22662
+ fortress_id: body?.identity_binding?.fortress_id ?? "",
22663
+ identity_id: body?.identity_binding?.identity_id ?? "",
22664
+ exported_at: body?.exported_at ?? "",
22665
+ artifact_count: body?.artifacts?.length ?? 0
22666
+ },
22667
+ artifact_results: body?.artifacts?.map((artifact) => ({
22668
+ path: artifact.path,
22669
+ kind: artifact.kind,
22670
+ hash_passed: false,
22671
+ size_passed: false
22672
+ })) ?? [],
22673
+ warnings,
22674
+ unsupported_artifacts: unsupported
21466
22675
  };
21467
22676
  }
21468
- function buildL1(sources, audit) {
21469
- const hasIdentity = !!sources.identityManager?.getDefault();
21470
- const state = hasIdentity ? "full" : "degraded";
22677
+ function fail(bundleDir, manifest, failureClass, warnings = [], unsupported = []) {
21471
22678
  return {
21472
- label: "L1 Cognitive",
21473
- state,
21474
- headline: hasIdentity ? "State encrypted at rest" : "No sovereign identity \u2014 run sanctuary_bootstrap",
21475
- encryption: "AES-256-GCM + HKDF per namespace",
21476
- injection_blocked_today: countInjectionsToday(audit),
21477
- memory_attest_ready: hasIdentity
22679
+ ...resultBase(bundleDir, manifest, warnings, unsupported),
22680
+ failure_class: failureClass
21478
22681
  };
21479
22682
  }
21480
- function buildL2(sources) {
21481
- const teeAvailable = sources.teeAvailable ?? false;
22683
+ function isKnownKind(kind) {
22684
+ return EXIT_BUNDLE_ARTIFACT_KINDS.includes(kind);
22685
+ }
22686
+ function validateArtifactPath(path) {
22687
+ if (Buffer.byteLength(path, "utf8") > EXIT_BUNDLE_PATH_MAX_BYTES) {
22688
+ return "unsafe";
22689
+ }
22690
+ if (!EXIT_BUNDLE_PATH_PATTERN.test(path)) return "unsafe";
22691
+ if (path.startsWith("/") || path.includes("\\") || path.includes("\0")) {
22692
+ return "unsafe";
22693
+ }
22694
+ const normalized = decodeURIComponentSafe(path).normalize("NFKC");
22695
+ if (normalized.split("/").some((segment) => segment === "." || segment === "..")) {
22696
+ return "unsafe";
22697
+ }
22698
+ return "ok";
22699
+ }
22700
+ function decodeURIComponentSafe(value) {
22701
+ let current = value;
22702
+ for (let i = 0; i < 2; i++) {
22703
+ try {
22704
+ const decoded = decodeURIComponent(current);
22705
+ if (decoded === current) return decoded;
22706
+ current = decoded;
22707
+ } catch {
22708
+ return current;
22709
+ }
22710
+ }
22711
+ return current;
22712
+ }
22713
+ async function assertDescendant(root, candidate) {
22714
+ const rootReal = await realpath(root);
22715
+ const candidateDir = await realpath(dirname(candidate));
22716
+ const rootWithSep = rootReal.endsWith(sep) ? rootReal : rootReal + sep;
22717
+ return candidateDir === rootReal || candidateDir.startsWith(rootWithSep);
22718
+ }
22719
+ function findPrivateMaterial(value, path = "$") {
22720
+ if (value === null || typeof value !== "object") return [];
22721
+ if (Array.isArray(value)) {
22722
+ return value.flatMap(
22723
+ (item, index) => findPrivateMaterial(item, `${path}[${index}]`)
22724
+ );
22725
+ }
22726
+ const findings = [];
22727
+ for (const [key, child] of Object.entries(value)) {
22728
+ const normalized = key.toLowerCase().replace(/[-\s]/g, "_");
22729
+ if (PRIVATE_MATERIAL_KEYS.has(normalized)) {
22730
+ findings.push(`${path}.${key}`);
22731
+ continue;
22732
+ }
22733
+ findings.push(...findPrivateMaterial(child, `${path}.${key}`));
22734
+ }
22735
+ return findings;
22736
+ }
22737
+ async function readManifest(bundleDir) {
22738
+ const bytes = await readFile(join(bundleDir, "manifest.json"));
22739
+ return JSON.parse(Buffer.from(bytes).toString("utf8"));
22740
+ }
22741
+ async function loadExitArtifact(bundleDir, manifest, kind) {
22742
+ const entry = manifest.body.artifacts.find((artifact) => artifact.kind === kind);
22743
+ if (!entry) return null;
22744
+ const artifactPath = join(bundleDir, entry.path);
22745
+ const bytes = await readFile(artifactPath);
22746
+ return {
22747
+ entry,
22748
+ path: artifactPath,
22749
+ bytes: new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength),
22750
+ json: JSON.parse(Buffer.from(bytes).toString("utf8"))
22751
+ };
22752
+ }
22753
+ function verifyIdentityArtifact(identityArtifact) {
22754
+ const wrapper = identityArtifact;
22755
+ const bundle = wrapper.bundle;
22756
+ if (!bundle || typeof wrapper.signature !== "string") {
22757
+ return { signature_valid: false };
22758
+ }
22759
+ const publicKey = bundle.publicKey;
22760
+ if (typeof publicKey !== "string") {
22761
+ return { signature_valid: false };
22762
+ }
22763
+ const signatureValid = ed25519.verify(
22764
+ fromBase64url(wrapper.signature),
22765
+ canonicalizeToBytes(bundle),
22766
+ fromBase64url(publicKey)
22767
+ );
22768
+ return {
22769
+ signature_valid: signatureValid,
22770
+ identity_id: typeof bundle.identity_id === "string" ? bundle.identity_id : void 0,
22771
+ did: typeof bundle.did === "string" ? bundle.did : void 0,
22772
+ public_key: publicKey
22773
+ };
22774
+ }
22775
+ function verifyReputationArtifact(reputationArtifact, publicKeysByDid) {
22776
+ const bundle = reputationArtifact;
22777
+ const attestations = Array.isArray(bundle.attestations) ? bundle.attestations : [];
22778
+ let bundleSignatureValid = "unverifiable";
22779
+ if (bundle.version === "SANCTUARY_REP_V1" && typeof bundle.exporter_did === "string" && typeof bundle.bundle_signature === "string") {
22780
+ const exporterKey = publicKeysByDid.get(bundle.exporter_did);
22781
+ if (exporterKey) {
22782
+ const signedBody = {
22783
+ version: "SANCTUARY_REP_V1",
22784
+ attestations,
22785
+ exported_at: bundle.exported_at,
22786
+ exporter_did: bundle.exporter_did
22787
+ };
22788
+ bundleSignatureValid = ed25519.verify(
22789
+ fromBase64url(bundle.bundle_signature),
22790
+ stringToBytes(JSON.stringify(signedBody)),
22791
+ exporterKey
22792
+ );
22793
+ }
22794
+ }
22795
+ let verified = 0;
22796
+ let invalid = 0;
22797
+ let unverifiable = 0;
22798
+ for (const attestation of attestations) {
22799
+ const signerKey = publicKeysByDid.get(attestation.signer);
22800
+ if (!signerKey) {
22801
+ unverifiable++;
22802
+ continue;
22803
+ }
22804
+ const ok = ed25519.verify(
22805
+ fromBase64url(attestation.signature),
22806
+ stringToBytes(JSON.stringify(attestation.data)),
22807
+ signerKey
22808
+ );
22809
+ if (ok) verified++;
22810
+ else invalid++;
22811
+ }
22812
+ return {
22813
+ bundle_signature_valid: bundleSignatureValid,
22814
+ attestation_count: attestations.length,
22815
+ verified_attestations: verified,
22816
+ invalid_attestations: invalid,
22817
+ unverifiable_attestations: unverifiable
22818
+ };
22819
+ }
22820
+ async function verifyExitBundle(bundleDir) {
22821
+ const root = resolve(bundleDir);
22822
+ let manifest;
22823
+ let manifestBytes;
22824
+ try {
22825
+ const raw = await readFile(join(root, "manifest.json"));
22826
+ manifestBytes = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
22827
+ manifest = JSON.parse(Buffer.from(raw).toString("utf8"));
22828
+ } catch {
22829
+ return fail(root, null, "other", ["manifest.json is missing or unreadable"]);
22830
+ }
22831
+ const warnings = [];
22832
+ const unsupportedArtifacts = [];
22833
+ const body = manifest.body;
22834
+ if (!body || body.manifest_version !== EXIT_BUNDLE_MANIFEST_VERSION) {
22835
+ return fail(root, manifest, "manifest_unknown_version", warnings, unsupportedArtifacts);
22836
+ }
22837
+ if (body.signature_scheme !== SIGNATURE_SCHEME_V1) {
22838
+ return fail(
22839
+ root,
22840
+ manifest,
22841
+ "manifest_signature_scheme_invalid",
22842
+ warnings,
22843
+ unsupportedArtifacts
22844
+ );
22845
+ }
22846
+ const seenPaths = /* @__PURE__ */ new Set();
22847
+ for (const artifact of body.artifacts) {
22848
+ if (!isKnownKind(artifact.kind)) {
22849
+ return fail(root, manifest, "other", [`unknown artifact kind: ${artifact.kind}`]);
22850
+ }
22851
+ if (seenPaths.has(artifact.path)) {
22852
+ return fail(root, manifest, "artifact_path_duplicate", warnings, unsupportedArtifacts);
22853
+ }
22854
+ seenPaths.add(artifact.path);
22855
+ if (validateArtifactPath(artifact.path) !== "ok") {
22856
+ return fail(root, manifest, "artifact_path_unsafe", warnings, unsupportedArtifacts);
22857
+ }
22858
+ }
22859
+ const signatureOk = ed25519.verify(
22860
+ fromBase64url(manifest.signature),
22861
+ canonicalizeToBytes(body),
22862
+ fromBase64url(body.identity_binding.fortress_master_pubkey)
22863
+ );
22864
+ if (!signatureOk) {
22865
+ return fail(root, manifest, "manifest_signature_invalid", warnings, unsupportedArtifacts);
22866
+ }
22867
+ const expectedAggregate = sha256Hex2(
22868
+ stringToBytes(canonicalize2(body.artifacts))
22869
+ );
22870
+ if (expectedAggregate !== body.artifacts_aggregate_hash) {
22871
+ return fail(root, manifest, "aggregate_hash_mismatch", warnings, unsupportedArtifacts);
22872
+ }
22873
+ const artifactResults = [];
22874
+ let artifactFailure = null;
22875
+ for (const artifact of body.artifacts) {
22876
+ const artifactPath = join(root, artifact.path);
22877
+ let bytes;
22878
+ let fileSize = -1;
22879
+ try {
22880
+ const linkStat = await lstat(artifactPath);
22881
+ if (linkStat.isSymbolicLink()) {
22882
+ artifactFailure = "archive_contains_symlink";
22883
+ artifactResults.push({
22884
+ path: artifact.path,
22885
+ kind: artifact.kind,
22886
+ hash_passed: false,
22887
+ size_passed: false
22888
+ });
22889
+ continue;
22890
+ }
22891
+ const descends = await assertDescendant(root, artifactPath);
22892
+ if (!descends) {
22893
+ artifactFailure = "artifact_path_escapes_root";
22894
+ artifactResults.push({
22895
+ path: artifact.path,
22896
+ kind: artifact.kind,
22897
+ hash_passed: false,
22898
+ size_passed: false
22899
+ });
22900
+ continue;
22901
+ }
22902
+ const fileStat = await stat(artifactPath);
22903
+ fileSize = fileStat.size;
22904
+ const raw = await readFile(artifactPath);
22905
+ bytes = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
22906
+ } catch {
22907
+ artifactFailure = "artifact_missing";
22908
+ artifactResults.push({
22909
+ path: artifact.path,
22910
+ kind: artifact.kind,
22911
+ hash_passed: false,
22912
+ size_passed: false
22913
+ });
22914
+ continue;
22915
+ }
22916
+ const hashPassed = sha256Hex2(bytes) === artifact.hash;
22917
+ const sizePassed = fileSize === artifact.size_bytes;
22918
+ if (!hashPassed && !artifactFailure) artifactFailure = "artifact_hash_mismatch";
22919
+ if (!sizePassed && !artifactFailure) artifactFailure = "artifact_size_mismatch";
22920
+ artifactResults.push({
22921
+ path: artifact.path,
22922
+ kind: artifact.kind,
22923
+ hash_passed: hashPassed,
22924
+ size_passed: sizePassed
22925
+ });
22926
+ try {
22927
+ const parsed = JSON.parse(Buffer.from(bytes).toString("utf8"));
22928
+ const privateFindings = findPrivateMaterial(parsed);
22929
+ if (privateFindings.length > 0) {
22930
+ artifactFailure = "private_material_present";
22931
+ warnings.push(
22932
+ `${artifact.path} contains private-material field(s): ${privateFindings.join(", ")}`
22933
+ );
22934
+ }
22935
+ } catch {
22936
+ warnings.push(`${artifact.path} is not parseable JSON`);
22937
+ }
22938
+ }
22939
+ if (artifactFailure) {
22940
+ return {
22941
+ ...fail(root, manifest, artifactFailure, warnings, unsupportedArtifacts),
22942
+ manifest_hash: sha256Hex2(manifestBytes),
22943
+ artifact_results: artifactResults
22944
+ };
22945
+ }
22946
+ const publicKeysByDid = /* @__PURE__ */ new Map();
22947
+ const identityArtifact = await loadExitArtifact(root, manifest, "public_identity");
22948
+ let identity;
22949
+ if (identityArtifact) {
22950
+ const identityVerification = verifyIdentityArtifact(identityArtifact.json);
22951
+ identity = {
22952
+ signature_valid: identityVerification.signature_valid,
22953
+ identity_id: identityVerification.identity_id,
22954
+ did: identityVerification.did
22955
+ };
22956
+ if (identityVerification.did && identityVerification.public_key) {
22957
+ publicKeysByDid.set(
22958
+ identityVerification.did,
22959
+ fromBase64url(identityVerification.public_key)
22960
+ );
22961
+ }
22962
+ if (!identityVerification.signature_valid) {
22963
+ warnings.push("public identity artifact signature is invalid");
22964
+ }
22965
+ }
22966
+ const auditArtifact = await loadExitArtifact(root, manifest, "audit_receipts");
22967
+ const audit = auditArtifact ? {
22968
+ receipt_count: Array.isArray(auditArtifact.json.entries) ? auditArtifact.json.entries.length : 0,
22969
+ individual_signatures_verified: false
22970
+ } : void 0;
22971
+ if (auditArtifact) {
22972
+ unsupportedArtifacts.push(
22973
+ "audit_receipts: individual audit entries are not signed in the legacy L2 audit log; verifier pins them by signed manifest hash"
22974
+ );
22975
+ }
22976
+ const reputationArtifact = await loadExitArtifact(root, manifest, "reputation_bundle");
22977
+ const reputation = reputationArtifact ? verifyReputationArtifact(reputationArtifact.json, publicKeysByDid) : void 0;
22978
+ if (reputation) {
22979
+ if (reputation.bundle_signature_valid === "unverifiable") {
22980
+ warnings.push("reputation bundle signature is unverifiable from included public identities");
22981
+ } else if (!reputation.bundle_signature_valid) {
22982
+ warnings.push("reputation bundle signature is invalid");
22983
+ }
22984
+ if (reputation.unverifiable_attestations > 0) {
22985
+ warnings.push(
22986
+ `${reputation.unverifiable_attestations} reputation attestation(s) have unknown signer public keys`
22987
+ );
22988
+ }
22989
+ if (reputation.invalid_attestations > 0) {
22990
+ warnings.push(
22991
+ `${reputation.invalid_attestations} reputation attestation(s) failed signature verification`
22992
+ );
22993
+ }
22994
+ }
22995
+ const reputationFailed = reputation?.bundle_signature_valid === false || (reputation?.invalid_attestations ?? 0) > 0;
22996
+ const identityFailed = identity ? !identity.signature_valid : false;
22997
+ return {
22998
+ version: "1.1",
22999
+ passed: !reputationFailed && !identityFailed,
23000
+ verified_at: (/* @__PURE__ */ new Date()).toISOString(),
23001
+ manifest_path: join(root, "manifest.json"),
23002
+ manifest_hash: sha256Hex2(manifestBytes),
23003
+ manifest_summary: {
23004
+ manifest_version: body.manifest_version,
23005
+ fortress_id: body.identity_binding.fortress_id,
23006
+ identity_id: body.identity_binding.identity_id,
23007
+ exported_at: body.exported_at,
23008
+ artifact_count: body.artifacts.length
23009
+ },
23010
+ artifact_results: artifactResults,
23011
+ warnings,
23012
+ unsupported_artifacts: unsupportedArtifacts,
23013
+ identity,
23014
+ audit,
23015
+ reputation,
23016
+ failure_class: reputationFailed || identityFailed ? "other" : void 0
23017
+ };
23018
+ }
23019
+
23020
+ // src/exit/bundle.ts
23021
+ var ARTIFACT_DIR = "artifacts";
23022
+ var EXIT_IMPORT_NAMESPACE = "_exit_imports";
23023
+ var EXIT_PUBLIC_IDENTITIES_NAMESPACE = "_exit_public_identities";
23024
+ var EXIT_AUDIT_RECEIPTS_NAMESPACE = "_exit_audit_receipts";
23025
+ var EXIT_POLICY_SETS_NAMESPACE = "_exit_policy_sets";
23026
+ var EXIT_COMMITMENTS_NAMESPACE = "_exit_commitments";
23027
+ var EXIT_PLACEHOLDER_METADATA_NAMESPACE = "_exit_placeholder_metadata";
23028
+ var PRIVACY_PLACEHOLDER_NAMESPACE = "_privacy_placeholder_vault";
23029
+ function sha256Hex3(bytes) {
23030
+ return Array.from(hash(bytes)).map((b) => b.toString(16).padStart(2, "0")).join("");
23031
+ }
23032
+ function jsonBytes(value) {
23033
+ return stringToBytes(JSON.stringify(value, null, 2) + "\n");
23034
+ }
23035
+ async function writeJsonArtifact(bundleDir, path, value, kind) {
23036
+ const bytes = jsonBytes(value);
23037
+ const fullPath = join(bundleDir, path);
23038
+ await mkdir(join(bundleDir, ARTIFACT_DIR), { recursive: true, mode: 448 });
23039
+ await writeFile(fullPath, bytes, { mode: 384 });
23040
+ return {
23041
+ kind,
23042
+ path,
23043
+ hash_alg: "sha256",
23044
+ hash: sha256Hex3(bytes),
23045
+ size_bytes: bytes.length
23046
+ };
23047
+ }
23048
+ async function readSourceKeyParams(storage) {
23049
+ const raw = await storage.read("_meta", "key-params");
23050
+ if (!raw) return void 0;
23051
+ return JSON.parse(bytesToString(raw));
23052
+ }
23053
+ async function discoverFilesystemStateNamespaces(stateStoragePath) {
23054
+ if (!stateStoragePath) return [];
23055
+ try {
23056
+ const names = await readdir(stateStoragePath);
23057
+ const namespaces = [];
23058
+ for (const name of names) {
23059
+ const full = join(stateStoragePath, name);
23060
+ const entryStat = await stat(full);
23061
+ if (!entryStat.isDirectory()) continue;
23062
+ if (name.startsWith("_")) continue;
23063
+ namespaces.push(name);
23064
+ }
23065
+ return namespaces.sort();
23066
+ } catch {
23067
+ return [];
23068
+ }
23069
+ }
23070
+ async function exportEncryptedState(opts) {
23071
+ const namespaceSet = new Set(
23072
+ opts.stateNamespaces ?? await discoverFilesystemStateNamespaces(opts.stateStoragePath)
23073
+ );
23074
+ const entries = [];
23075
+ for (const namespace of [...namespaceSet].sort()) {
23076
+ if (isReservedNamespace(namespace)) continue;
23077
+ const metas = await opts.storage.list(namespace);
23078
+ for (const meta of metas) {
23079
+ const raw = await opts.storage.read(namespace, meta.key);
23080
+ if (!raw) continue;
23081
+ try {
23082
+ entries.push({
23083
+ namespace,
23084
+ key: meta.key,
23085
+ entry: JSON.parse(bytesToString(raw))
23086
+ });
23087
+ } catch {
23088
+ }
23089
+ }
23090
+ }
23091
+ return {
23092
+ format: "SANCTUARY_EXIT_ENCRYPTED_STATE_V1",
23093
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
23094
+ key_source: opts.keySource ?? "unknown",
23095
+ source_key_derivation: await readSourceKeyParams(opts.storage),
23096
+ namespaces: [...new Set(entries.map((entry) => entry.namespace))].sort(),
23097
+ total_keys: entries.length,
23098
+ contains_reserved_namespaces: false,
23099
+ entries
23100
+ };
23101
+ }
23102
+ function exportPublicIdentity(identity, masterKey) {
23103
+ const body = {
23104
+ format: "SANCTUARY_IDENTITY_BUNDLE_V1",
23105
+ publicKey: identity.public_key,
23106
+ did: identity.did,
23107
+ identity_id: identity.identity_id,
23108
+ label: identity.label,
23109
+ key_type: identity.key_type,
23110
+ key_protection: identity.key_protection,
23111
+ rotation_history: identity.rotation_history ?? [],
23112
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
23113
+ };
23114
+ const signature = sign(
23115
+ canonicalizeToBytes(body),
23116
+ identity.encrypted_private_key,
23117
+ derivePurposeKey(masterKey, "identity-encryption")
23118
+ );
23119
+ return {
23120
+ bundle: body,
23121
+ signature: toBase64url(signature),
23122
+ signed_by: identity.did
23123
+ };
23124
+ }
23125
+ function redactedPolicy(policy) {
23126
+ return {
23127
+ ...policy,
23128
+ approval_channel: {
23129
+ type: policy.approval_channel.type,
23130
+ timeout_seconds: policy.approval_channel.timeout_seconds,
23131
+ webhook_url: policy.approval_channel.webhook_url
23132
+ }
23133
+ };
23134
+ }
23135
+ function exportPolicySet(policy, config) {
23136
+ const cfg = config ?? defaultConfig();
23137
+ return {
23138
+ format: "SANCTUARY_EXIT_POLICY_SET_V1",
23139
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
23140
+ principal_policy: redactedPolicy(policy),
23141
+ config_summary: {
23142
+ version: cfg.version,
23143
+ state: cfg.state,
23144
+ execution: cfg.execution,
23145
+ disclosure: cfg.disclosure,
23146
+ reputation: cfg.reputation,
23147
+ privacy_filter: cfg.privacy_filter
23148
+ }
23149
+ };
23150
+ }
23151
+ async function exportAuditReceipts(auditLog) {
23152
+ await auditLog.flush();
23153
+ const result = await auditLog.query({ limit: 1e5 });
23154
+ return {
23155
+ format: "SANCTUARY_AUDIT_RECEIPTS_V1",
23156
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
23157
+ total: result.total,
23158
+ individual_entry_signatures: false,
23159
+ entries: result.entries
23160
+ };
23161
+ }
23162
+ async function exportCommitments(storage, masterKey) {
23163
+ const encryptionKey = derivePurposeKey(masterKey, "l3-commitments");
23164
+ const publicCommitments = [];
23165
+ let unreadable = 0;
23166
+ for (const meta of await storage.list("_commitments")) {
23167
+ const raw = await storage.read("_commitments", meta.key);
23168
+ if (!raw) continue;
23169
+ try {
23170
+ const encrypted = JSON.parse(bytesToString(raw));
23171
+ const decrypted = decrypt(encrypted, encryptionKey);
23172
+ const parsed = JSON.parse(bytesToString(decrypted));
23173
+ publicCommitments.push({
23174
+ commitment_id: meta.key,
23175
+ commitment: parsed.commitment,
23176
+ committed_at: parsed.committed_at,
23177
+ revealed: parsed.revealed,
23178
+ revealed_at: parsed.revealed_at
23179
+ });
23180
+ } catch {
23181
+ unreadable++;
23182
+ }
23183
+ }
23184
+ return {
23185
+ format: "SANCTUARY_EXIT_COMMITMENTS_V1",
23186
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
23187
+ public_commitments: publicCommitments,
23188
+ unreadable_count: unreadable,
23189
+ redacted_fields: ["value", "blinding_factor"]
23190
+ };
23191
+ }
23192
+ async function exportPlaceholderVaultMetadata(storage, masterKey) {
23193
+ const encryptionKey = derivePurposeKey(masterKey, "l2-privacy-placeholders");
23194
+ const entries = [];
23195
+ let unreadable = 0;
23196
+ for (const meta of await storage.list(PRIVACY_PLACEHOLDER_NAMESPACE)) {
23197
+ const raw = await storage.read(PRIVACY_PLACEHOLDER_NAMESPACE, meta.key);
23198
+ if (!raw) continue;
23199
+ try {
23200
+ const encrypted = JSON.parse(bytesToString(raw));
23201
+ const decrypted = decrypt(encrypted, encryptionKey);
23202
+ const parsed = JSON.parse(bytesToString(decrypted));
23203
+ const safe = {
23204
+ key: meta.key,
23205
+ version: parsed.version,
23206
+ kind: parsed.kind ?? (meta.key.endsWith("__index") ? "index" : "metadata"),
23207
+ scope: parsed.scope,
23208
+ class: parsed.class,
23209
+ placeholder: parsed.placeholder,
23210
+ alias: parsed.alias,
23211
+ raw_hash: parsed.raw_hash,
23212
+ counters: parsed.counters,
23213
+ next: parsed.next,
23214
+ created_at: parsed.created_at
23215
+ };
23216
+ entries.push(
23217
+ Object.fromEntries(
23218
+ Object.entries(safe).filter(([, value]) => value !== void 0)
23219
+ )
23220
+ );
23221
+ } catch {
23222
+ unreadable++;
23223
+ }
23224
+ }
23225
+ return {
23226
+ format: "SANCTUARY_PLACEHOLDER_VAULT_METADATA_V1",
23227
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
23228
+ entries,
23229
+ unreadable_count: unreadable,
23230
+ redacted_fields: ["raw_value", "raw_path"]
23231
+ };
23232
+ }
23233
+ async function exportExitBundle(opts) {
23234
+ const bundleDir = resolve(opts.bundleDir);
23235
+ await mkdir(bundleDir, { recursive: true, mode: 448 });
23236
+ await mkdir(join(bundleDir, ARTIFACT_DIR), { recursive: true, mode: 448 });
23237
+ const identity = opts.identityManager.getDefault();
23238
+ if (!identity) {
23239
+ throw new Error("Cannot export exit bundle: no default identity exists.");
23240
+ }
23241
+ const exportApprovalAuditId = opts.exportApprovalAuditId ?? `exit-export-${Date.now()}`;
23242
+ opts.auditLog.append("l1", "exit_bundle_export", identity.identity_id, {
23243
+ approval_id: exportApprovalAuditId,
23244
+ manifest_version: EXIT_BUNDLE_MANIFEST_VERSION
23245
+ });
23246
+ const reputationStore = opts.reputationStore ?? new ReputationStore(opts.storage, opts.masterKey);
23247
+ const identityEncryptionKey = derivePurposeKey(opts.masterKey, "identity-encryption");
23248
+ const artifacts = [];
23249
+ artifacts.push(
23250
+ await writeJsonArtifact(
23251
+ bundleDir,
23252
+ `${ARTIFACT_DIR}/public_identity.json`,
23253
+ exportPublicIdentity(identity, opts.masterKey),
23254
+ "public_identity"
23255
+ )
23256
+ );
23257
+ artifacts.push(
23258
+ await writeJsonArtifact(
23259
+ bundleDir,
23260
+ `${ARTIFACT_DIR}/encrypted_state.json`,
23261
+ await exportEncryptedState(opts),
23262
+ "encrypted_state"
23263
+ )
23264
+ );
23265
+ artifacts.push(
23266
+ await writeJsonArtifact(
23267
+ bundleDir,
23268
+ `${ARTIFACT_DIR}/policy_set.json`,
23269
+ exportPolicySet(opts.policy, opts.config),
23270
+ "policy_set"
23271
+ )
23272
+ );
23273
+ artifacts.push(
23274
+ await writeJsonArtifact(
23275
+ bundleDir,
23276
+ `${ARTIFACT_DIR}/audit_receipts.json`,
23277
+ await exportAuditReceipts(opts.auditLog),
23278
+ "audit_receipts"
23279
+ )
23280
+ );
23281
+ artifacts.push(
23282
+ await writeJsonArtifact(
23283
+ bundleDir,
23284
+ `${ARTIFACT_DIR}/reputation_bundle.json`,
23285
+ await reputationStore.exportBundle(identity, identityEncryptionKey),
23286
+ "reputation_bundle"
23287
+ )
23288
+ );
23289
+ artifacts.push(
23290
+ await writeJsonArtifact(
23291
+ bundleDir,
23292
+ `${ARTIFACT_DIR}/commitments.json`,
23293
+ await exportCommitments(opts.storage, opts.masterKey),
23294
+ "commitments"
23295
+ )
23296
+ );
23297
+ artifacts.push(
23298
+ await writeJsonArtifact(
23299
+ bundleDir,
23300
+ `${ARTIFACT_DIR}/placeholder_vault_metadata.json`,
23301
+ await exportPlaceholderVaultMetadata(opts.storage, opts.masterKey),
23302
+ "placeholder_vault_metadata"
23303
+ )
23304
+ );
23305
+ const body = {
23306
+ manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
23307
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
23308
+ identity_binding: {
23309
+ identity_id: identity.identity_id,
23310
+ fortress_id: identity.did,
23311
+ fortress_master_pubkey: identity.public_key,
23312
+ did: identity.did
23313
+ },
23314
+ source_sanctuary_version: opts.config?.version ?? SANCTUARY_VERSION,
23315
+ artifacts,
23316
+ artifacts_aggregate_hash: sha256Hex3(
23317
+ stringToBytes(canonicalize2(artifacts))
23318
+ ),
23319
+ artifacts_aggregate_hash_alg: "sha256",
23320
+ export_approval_audit_id: exportApprovalAuditId,
23321
+ signature_scheme: SIGNATURE_SCHEME_V1
23322
+ };
23323
+ const signature = sign(
23324
+ canonicalizeToBytes(body),
23325
+ identity.encrypted_private_key,
23326
+ identityEncryptionKey
23327
+ );
23328
+ const manifest = {
23329
+ body,
23330
+ signature: toBase64url(signature)
23331
+ };
23332
+ const manifestBytes = jsonBytes(manifest);
23333
+ await writeFile(join(bundleDir, "manifest.json"), manifestBytes, { mode: 384 });
23334
+ await opts.auditLog.flush();
23335
+ return {
23336
+ bundle_dir: bundleDir,
23337
+ manifest,
23338
+ manifest_hash: sha256Hex3(manifestBytes),
23339
+ artifact_count: artifacts.length,
23340
+ unsupported_artifacts: [
23341
+ "audit_receipts: legacy L2 audit entries are manifest-pinned but not individually signed"
23342
+ ]
23343
+ };
23344
+ }
23345
+ function publicKeysFromIdentityArtifact(identityArtifact) {
23346
+ const pubkey = fromBase64url(identityArtifact.bundle.publicKey);
23347
+ return {
23348
+ byIdentityId: /* @__PURE__ */ new Map([[identityArtifact.bundle.identity_id, pubkey]]),
23349
+ byDid: /* @__PURE__ */ new Map([[identityArtifact.bundle.did, pubkey]])
23350
+ };
23351
+ }
23352
+ async function conflictReport(storage, identityArtifact, encryptedState, reputationBundle, manifest) {
23353
+ const stateConflicts = [];
23354
+ for (const item of encryptedState?.entries ?? []) {
23355
+ if (await storage.exists(item.namespace, item.key)) {
23356
+ stateConflicts.push({ namespace: item.namespace, key: item.key });
23357
+ }
23358
+ }
23359
+ const reputationConflicts = [];
23360
+ for (const attestation of reputationBundle?.attestations ?? []) {
23361
+ if (await storage.exists("_reputation", attestation.attestation_id)) {
23362
+ reputationConflicts.push(attestation.attestation_id);
23363
+ }
23364
+ }
23365
+ const importId = importIdForManifest(manifest);
23366
+ return {
23367
+ public_identity_exists: identityArtifact ? await storage.exists(
23368
+ EXIT_PUBLIC_IDENTITIES_NAMESPACE,
23369
+ identityArtifact.bundle.identity_id
23370
+ ) : false,
23371
+ state_conflicts: stateConflicts,
23372
+ reputation_conflicts: reputationConflicts,
23373
+ policy_set_exists: await storage.exists(EXIT_POLICY_SETS_NAMESPACE, importId),
23374
+ audit_receipts_exist: await storage.exists(EXIT_AUDIT_RECEIPTS_NAMESPACE, importId)
23375
+ };
23376
+ }
23377
+ function importIdForManifest(manifest) {
23378
+ return `${manifest.body.identity_binding.identity_id}-${manifest.body.exported_at.replace(/[^0-9a-zA-Z_.-]/g, "_")}`;
23379
+ }
23380
+ async function resolveSourceMasterKey(encryptedState, opts) {
23381
+ if (!encryptedState || encryptedState.entries.length === 0) return null;
23382
+ if (opts.sourceMasterKey) return opts.sourceMasterKey;
23383
+ if (opts.sourcePassphrase && encryptedState.source_key_derivation) {
23384
+ return (await deriveMasterKey(
23385
+ opts.sourcePassphrase,
23386
+ encryptedState.source_key_derivation
23387
+ )).key;
23388
+ }
23389
+ if (opts.sourceRecoveryKey) {
23390
+ const key = fromBase64url(opts.sourceRecoveryKey);
23391
+ if (key.length !== 32) {
23392
+ throw new Error("Source recovery key must decode to 32 bytes.");
23393
+ }
23394
+ return key;
23395
+ }
23396
+ return null;
23397
+ }
23398
+ async function rekeyState(encryptedState, opts, sourceMasterKey, publicKeysByIdentityId) {
23399
+ const destinationSigner = opts.destinationSignerIdentityId ? opts.identityManager.get(opts.destinationSignerIdentityId) : opts.identityManager.getDefault();
23400
+ if (!destinationSigner) {
23401
+ return {
23402
+ status: "skipped_no_destination_signer",
23403
+ imported_keys: 0,
23404
+ skipped_keys: encryptedState.entries.length,
23405
+ skipped_invalid_sig: 0,
23406
+ skipped_unknown_kid: 0,
23407
+ conflicts: 0
23408
+ };
23409
+ }
23410
+ const stateStore = new StateStore(opts.storage, opts.masterKey);
23411
+ const identityEncryptionKey = derivePurposeKey(opts.masterKey, "identity-encryption");
23412
+ let imported = 0;
23413
+ let skipped = 0;
23414
+ let skippedInvalidSig = 0;
23415
+ let skippedUnknownKid = 0;
23416
+ let conflicts = 0;
23417
+ for (const item of encryptedState.entries) {
23418
+ if (isReservedNamespace(item.namespace)) {
23419
+ skipped++;
23420
+ continue;
23421
+ }
23422
+ const signerPubkey = publicKeysByIdentityId.get(item.entry.kid);
23423
+ if (!signerPubkey) {
23424
+ skippedUnknownKid++;
23425
+ skipped++;
23426
+ continue;
23427
+ }
23428
+ const sourceSigValid = verify(
23429
+ fromBase64url(item.entry.payload.ct),
23430
+ fromBase64url(item.entry.sig),
23431
+ signerPubkey
23432
+ );
23433
+ if (!sourceSigValid) {
23434
+ skippedInvalidSig++;
23435
+ skipped++;
23436
+ continue;
23437
+ }
23438
+ const exists = await opts.storage.exists(item.namespace, item.key);
23439
+ if (exists) {
23440
+ conflicts++;
23441
+ const resolution = opts.conflictResolution ?? "skip";
23442
+ if (resolution === "skip") {
23443
+ skipped++;
23444
+ continue;
23445
+ }
23446
+ if (resolution === "version") {
23447
+ const raw = await opts.storage.read(item.namespace, item.key);
23448
+ if (raw) {
23449
+ try {
23450
+ const existing = JSON.parse(bytesToString(raw));
23451
+ if (item.entry.ver <= existing.ver) {
23452
+ skipped++;
23453
+ continue;
23454
+ }
23455
+ } catch {
23456
+ }
23457
+ }
23458
+ }
23459
+ }
23460
+ try {
23461
+ const plaintext = decrypt(
23462
+ item.entry.payload,
23463
+ deriveNamespaceKey(sourceMasterKey, item.namespace)
23464
+ );
23465
+ if (hashToString(plaintext) !== item.entry.integrity_hash) {
23466
+ skippedInvalidSig++;
23467
+ skipped++;
23468
+ continue;
23469
+ }
23470
+ await stateStore.write(
23471
+ item.namespace,
23472
+ item.key,
23473
+ bytesToString(plaintext),
23474
+ destinationSigner.identity_id,
23475
+ destinationSigner.encrypted_private_key,
23476
+ identityEncryptionKey,
23477
+ {
23478
+ content_type: item.entry.metadata.content_type,
23479
+ ttl_seconds: item.entry.metadata.ttl_seconds,
23480
+ tags: [
23481
+ ...item.entry.metadata.tags ?? [],
23482
+ "exit-import",
23483
+ `source:${item.entry.kid}`
23484
+ ]
23485
+ }
23486
+ );
23487
+ imported++;
23488
+ } catch {
23489
+ skippedInvalidSig++;
23490
+ skipped++;
23491
+ }
23492
+ }
23493
+ return {
23494
+ status: "rekeyed",
23495
+ imported_keys: imported,
23496
+ skipped_keys: skipped,
23497
+ skipped_invalid_sig: skippedInvalidSig,
23498
+ skipped_unknown_kid: skippedUnknownKid,
23499
+ conflicts
23500
+ };
23501
+ }
23502
+ async function stageArtifact(storage, namespace, key, value) {
23503
+ await storage.write(namespace, key, jsonBytes(value));
23504
+ }
23505
+ async function importExitBundle(opts) {
23506
+ const verification = await verifyExitBundle(opts.bundleDir);
23507
+ if (!verification.passed) {
23508
+ return {
23509
+ verified: false,
23510
+ activated: false,
23511
+ conflicts: {
23512
+ public_identity_exists: false,
23513
+ state_conflicts: [],
23514
+ reputation_conflicts: [],
23515
+ policy_set_exists: false,
23516
+ audit_receipts_exist: false
23517
+ },
23518
+ state: {
23519
+ status: "not_requested",
23520
+ imported_keys: 0,
23521
+ skipped_keys: 0,
23522
+ skipped_invalid_sig: 0,
23523
+ skipped_unknown_kid: 0,
23524
+ conflicts: 0
23525
+ },
23526
+ reputation: {
23527
+ imported_attestations: 0,
23528
+ invalid_attestations: 0,
23529
+ unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
23530
+ },
23531
+ staged_artifacts: [],
23532
+ warnings: verification.warnings,
23533
+ unsupported_artifacts: verification.unsupported_artifacts
23534
+ };
23535
+ }
23536
+ const manifest = await readManifest(opts.bundleDir);
23537
+ const identityArtifact = await loadExitArtifact(
23538
+ opts.bundleDir,
23539
+ manifest,
23540
+ "public_identity"
23541
+ );
23542
+ const encryptedState = await loadExitArtifact(
23543
+ opts.bundleDir,
23544
+ manifest,
23545
+ "encrypted_state"
23546
+ );
23547
+ const policySet = await loadExitArtifact(
23548
+ opts.bundleDir,
23549
+ manifest,
23550
+ "policy_set"
23551
+ );
23552
+ const auditReceipts = await loadExitArtifact(
23553
+ opts.bundleDir,
23554
+ manifest,
23555
+ "audit_receipts"
23556
+ );
23557
+ const reputationArtifact = await loadExitArtifact(
23558
+ opts.bundleDir,
23559
+ manifest,
23560
+ "reputation_bundle"
23561
+ );
23562
+ const commitments = await loadExitArtifact(
23563
+ opts.bundleDir,
23564
+ manifest,
23565
+ "commitments"
23566
+ );
23567
+ const placeholderMetadata = await loadExitArtifact(
23568
+ opts.bundleDir,
23569
+ manifest,
23570
+ "placeholder_vault_metadata"
23571
+ );
23572
+ const conflicts = await conflictReport(
23573
+ opts.storage,
23574
+ identityArtifact?.json ?? null,
23575
+ encryptedState?.json ?? null,
23576
+ reputationArtifact?.json ?? null,
23577
+ manifest
23578
+ );
23579
+ if (!opts.activate) {
23580
+ return {
23581
+ verified: true,
23582
+ activated: false,
23583
+ conflicts,
23584
+ state: {
23585
+ status: "not_requested",
23586
+ imported_keys: 0,
23587
+ skipped_keys: 0,
23588
+ skipped_invalid_sig: 0,
23589
+ skipped_unknown_kid: 0,
23590
+ conflicts: conflicts.state_conflicts.length
23591
+ },
23592
+ reputation: {
23593
+ imported_attestations: 0,
23594
+ invalid_attestations: 0,
23595
+ unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
23596
+ },
23597
+ staged_artifacts: [],
23598
+ warnings: verification.warnings,
23599
+ unsupported_artifacts: verification.unsupported_artifacts
23600
+ };
23601
+ }
23602
+ const importId = importIdForManifest(manifest);
23603
+ const stagedArtifacts = [];
23604
+ if (identityArtifact) {
23605
+ await stageArtifact(
23606
+ opts.storage,
23607
+ EXIT_PUBLIC_IDENTITIES_NAMESPACE,
23608
+ identityArtifact.json.bundle.identity_id,
23609
+ identityArtifact.json
23610
+ );
23611
+ stagedArtifacts.push("public_identity");
23612
+ }
23613
+ if (policySet) {
23614
+ await stageArtifact(opts.storage, EXIT_POLICY_SETS_NAMESPACE, importId, policySet.json);
23615
+ stagedArtifacts.push("policy_set");
23616
+ }
23617
+ if (auditReceipts) {
23618
+ await stageArtifact(
23619
+ opts.storage,
23620
+ EXIT_AUDIT_RECEIPTS_NAMESPACE,
23621
+ importId,
23622
+ auditReceipts.json
23623
+ );
23624
+ stagedArtifacts.push("audit_receipts");
23625
+ }
23626
+ if (commitments) {
23627
+ await stageArtifact(opts.storage, EXIT_COMMITMENTS_NAMESPACE, importId, commitments.json);
23628
+ stagedArtifacts.push("commitments");
23629
+ }
23630
+ if (placeholderMetadata) {
23631
+ await stageArtifact(
23632
+ opts.storage,
23633
+ EXIT_PLACEHOLDER_METADATA_NAMESPACE,
23634
+ importId,
23635
+ placeholderMetadata.json
23636
+ );
23637
+ stagedArtifacts.push("placeholder_vault_metadata");
23638
+ }
23639
+ await stageArtifact(opts.storage, EXIT_IMPORT_NAMESPACE, importId, {
23640
+ manifest: manifest.body,
23641
+ verified_at: verification.verified_at,
23642
+ activated_at: (/* @__PURE__ */ new Date()).toISOString()
23643
+ });
23644
+ const publicKeys = identityArtifact ? publicKeysFromIdentityArtifact(identityArtifact.json) : { byIdentityId: /* @__PURE__ */ new Map(), byDid: /* @__PURE__ */ new Map() };
23645
+ let reputationResult = {
23646
+ imported_attestations: 0,
23647
+ invalid_attestations: 0,
23648
+ unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
23649
+ };
23650
+ if (reputationArtifact) {
23651
+ const reputationStore = opts.reputationStore ?? new ReputationStore(opts.storage, opts.masterKey);
23652
+ const imported = await reputationStore.importBundle(
23653
+ reputationArtifact.json,
23654
+ true,
23655
+ publicKeys.byDid
23656
+ );
23657
+ reputationResult = {
23658
+ imported_attestations: imported.imported,
23659
+ invalid_attestations: imported.invalid,
23660
+ unverifiable_attestations: verification.reputation?.unverifiable_attestations ?? 0
23661
+ };
23662
+ stagedArtifacts.push("reputation_bundle");
23663
+ }
23664
+ const sourceMasterKey = await resolveSourceMasterKey(
23665
+ encryptedState?.json ?? null,
23666
+ opts
23667
+ );
23668
+ const stateResult = encryptedState && encryptedState.json.entries.length > 0 ? sourceMasterKey ? await rekeyState(
23669
+ encryptedState.json,
23670
+ opts,
23671
+ sourceMasterKey,
23672
+ publicKeys.byIdentityId
23673
+ ) : {
23674
+ status: "staged_requires_source_key",
23675
+ imported_keys: 0,
23676
+ skipped_keys: encryptedState.json.entries.length,
23677
+ skipped_invalid_sig: 0,
23678
+ skipped_unknown_kid: 0,
23679
+ conflicts: conflicts.state_conflicts.length
23680
+ } : {
23681
+ status: "not_requested",
23682
+ imported_keys: 0,
23683
+ skipped_keys: 0,
23684
+ skipped_invalid_sig: 0,
23685
+ skipped_unknown_kid: 0,
23686
+ conflicts: 0
23687
+ };
23688
+ opts.auditLog.append("l1", "exit_bundle_import_activate", manifest.body.identity_binding.identity_id, {
23689
+ import_id: importId,
23690
+ manifest_version: manifest.body.manifest_version,
23691
+ state_status: stateResult.status,
23692
+ state_imported_keys: stateResult.imported_keys,
23693
+ reputation_imported_attestations: reputationResult.imported_attestations
23694
+ });
23695
+ await opts.auditLog.flush();
23696
+ return {
23697
+ verified: true,
23698
+ activated: true,
23699
+ conflicts,
23700
+ state: stateResult,
23701
+ reputation: reputationResult,
23702
+ staged_artifacts: stagedArtifacts,
23703
+ warnings: verification.warnings,
23704
+ unsupported_artifacts: verification.unsupported_artifacts
23705
+ };
23706
+ }
23707
+ function exitBundleManifestShape() {
23708
+ return {
23709
+ manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
23710
+ artifacts: [...EXIT_BUNDLE_ARTIFACT_KINDS],
23711
+ hash_alg: "sha256",
23712
+ signature_scheme: SIGNATURE_SCHEME_V1,
23713
+ required_top_level_file: "manifest.json",
23714
+ artifact_paths: [
23715
+ "artifacts/public_identity.json",
23716
+ "artifacts/encrypted_state.json",
23717
+ "artifacts/policy_set.json",
23718
+ "artifacts/audit_receipts.json",
23719
+ "artifacts/reputation_bundle.json",
23720
+ "artifacts/commitments.json",
23721
+ "artifacts/placeholder_vault_metadata.json"
23722
+ ]
23723
+ };
23724
+ }
23725
+ init_encoding();
23726
+ function write(stream, text) {
23727
+ stream.write(text);
23728
+ }
23729
+ function flagValue(argv, name) {
23730
+ const index = argv.indexOf(name);
23731
+ if (index === -1) return void 0;
23732
+ return argv[index + 1];
23733
+ }
23734
+ function hasFlag(argv, name) {
23735
+ return argv.includes(name);
23736
+ }
23737
+ function repeatedFlagValues(argv, name) {
23738
+ const values = [];
23739
+ for (let i = 0; i < argv.length; i++) {
23740
+ if (argv[i] === name && argv[i + 1]) values.push(argv[++i]);
23741
+ }
23742
+ return values;
23743
+ }
23744
+ async function confirmTier1(prompt, assumeYes, stdin, err) {
23745
+ if (assumeYes) return true;
23746
+ const readline = await import('readline/promises');
23747
+ const rl = readline.createInterface({
23748
+ input: stdin,
23749
+ output: err
23750
+ });
23751
+ const answer = await rl.question(`${prompt} [y/N] `);
23752
+ rl.close();
23753
+ return /^y(es)?$/i.test(answer.trim());
23754
+ }
23755
+ async function openExitContext(argv, env) {
23756
+ const passphrase = flagValue(argv, "--passphrase") ?? env.SANCTUARY_PASSPHRASE;
23757
+ const recoveryKey = env.SANCTUARY_RECOVERY_KEY;
23758
+ const config = await loadConfig();
23759
+ await mkdir(config.storage_path, { recursive: true, mode: 448 });
23760
+ const stateStoragePath = join(config.storage_path, "state");
23761
+ const storage = new FilesystemStorage(stateStoragePath);
23762
+ let masterKey;
23763
+ let keySource = "unknown";
23764
+ if (passphrase) {
23765
+ let existingParams;
23766
+ const raw = await storage.read("_meta", "key-params");
23767
+ if (raw) existingParams = JSON.parse(bytesToString(raw));
23768
+ const derived = await deriveMasterKey(passphrase, existingParams);
23769
+ masterKey = derived.key;
23770
+ if (!existingParams) {
23771
+ await storage.write(
23772
+ "_meta",
23773
+ "key-params",
23774
+ stringToBytes(JSON.stringify(derived.params))
23775
+ );
23776
+ }
23777
+ keySource = "passphrase";
23778
+ } else if (recoveryKey) {
23779
+ masterKey = fromBase64url(recoveryKey);
23780
+ if (masterKey.length !== 32) {
23781
+ throw new Error("SANCTUARY_RECOVERY_KEY must decode to 32 bytes.");
23782
+ }
23783
+ keySource = "recovery-key";
23784
+ } else {
23785
+ throw new Error(
23786
+ "sanctuary exit requires SANCTUARY_PASSPHRASE, --passphrase, or SANCTUARY_RECOVERY_KEY."
23787
+ );
23788
+ }
23789
+ const auditLog = new AuditLog(storage, masterKey);
23790
+ const identityManager = new IdentityManager(storage, masterKey);
23791
+ await identityManager.load();
23792
+ const reputationStore = new ReputationStore(storage, masterKey);
23793
+ return {
23794
+ storagePath: config.storage_path,
23795
+ stateStoragePath,
23796
+ storage,
23797
+ masterKey,
23798
+ auditLog,
23799
+ identityManager,
23800
+ reputationStore,
23801
+ keySource
23802
+ };
23803
+ }
23804
+ function printUsage(out) {
23805
+ write(out, `
23806
+ Usage: sanctuary exit <command> [options]
23807
+
23808
+ Commands:
23809
+ export --out <dir> Create a SANCTUARY_EXIT_BUNDLE_V1 directory
23810
+ verify <dir> Verify manifest, audit receipts, and reputation bundle
23811
+ import <dir> [--activate] Verify, report conflicts, and optionally activate
23812
+ manifest-shape Print the v1.1 manifest shape
23813
+
23814
+ Options:
23815
+ --passphrase <value> Current destination/source passphrase
23816
+ --source-passphrase <value> Source passphrase for state re-key on import
23817
+ --source-recovery-key <value> Source recovery key for state re-key on import
23818
+ --destination-identity-id <id> Destination signer for re-keyed state
23819
+ --state-namespace <name> Export a namespace; repeatable
23820
+ --conflict <skip|overwrite|version>
23821
+ --json
23822
+ --yes, -y Explicit non-interactive Tier 1 approval
23823
+ --help, -h
23824
+ `);
23825
+ }
23826
+ async function runExitCommand(args) {
23827
+ const argv = args.argv;
23828
+ const out = args.out ?? process.stdout;
23829
+ const err = args.err ?? process.stderr;
23830
+ const stdin = args.stdin ?? process.stdin;
23831
+ const env = args.env ?? process.env;
23832
+ if (argv.length === 0 || hasFlag(argv, "--help") || hasFlag(argv, "-h")) {
23833
+ printUsage(out);
23834
+ return 0;
23835
+ }
23836
+ const command = argv[0];
23837
+ const json = hasFlag(argv, "--json");
23838
+ try {
23839
+ if (command === "manifest-shape") {
23840
+ write(out, JSON.stringify(exitBundleManifestShape(), null, 2) + "\n");
23841
+ return 0;
23842
+ }
23843
+ if (command === "verify") {
23844
+ const dir = argv[1];
23845
+ if (!dir) {
23846
+ write(err, "Usage: sanctuary exit verify <dir>\n");
23847
+ return 2;
23848
+ }
23849
+ const result = await verifyExitBundle(dir);
23850
+ if (json) {
23851
+ write(out, JSON.stringify(result, null, 2) + "\n");
23852
+ } else {
23853
+ write(out, `manifest: ${result.passed ? "verified" : "failed"}
23854
+ `);
23855
+ write(out, `identity: ${result.manifest_summary.identity_id}
23856
+ `);
23857
+ write(out, `artifacts: ${result.manifest_summary.artifact_count}
23858
+ `);
23859
+ if (result.reputation) {
23860
+ write(
23861
+ out,
23862
+ `reputation: ${result.reputation.verified_attestations}/${result.reputation.attestation_count} attestations verified
23863
+ `
23864
+ );
23865
+ }
23866
+ for (const warning of result.warnings) write(out, `warning: ${warning}
23867
+ `);
23868
+ for (const item of result.unsupported_artifacts) {
23869
+ write(out, `unsupported: ${item}
23870
+ `);
23871
+ }
23872
+ }
23873
+ return result.passed ? 0 : 1;
23874
+ }
23875
+ if (command === "export") {
23876
+ const outDir = flagValue(argv, "--out");
23877
+ if (!outDir) {
23878
+ write(err, "Usage: sanctuary exit export --out <dir>\n");
23879
+ return 2;
23880
+ }
23881
+ const approved = await confirmTier1(
23882
+ "Tier 1 approval required: export complete Sanctuary exit bundle?",
23883
+ hasFlag(argv, "--yes") || hasFlag(argv, "-y"),
23884
+ stdin,
23885
+ err
23886
+ );
23887
+ if (!approved) {
23888
+ write(err, "Aborted.\n");
23889
+ return 1;
23890
+ }
23891
+ const config = await loadConfig();
23892
+ const ctx = await openExitContext(argv, env);
23893
+ const policy = await loadPrincipalPolicy(ctx.storagePath);
23894
+ const result = await exportExitBundle({
23895
+ bundleDir: outDir,
23896
+ storage: ctx.storage,
23897
+ masterKey: ctx.masterKey,
23898
+ identityManager: ctx.identityManager,
23899
+ auditLog: ctx.auditLog,
23900
+ reputationStore: ctx.reputationStore,
23901
+ policy,
23902
+ config,
23903
+ stateStoragePath: ctx.stateStoragePath,
23904
+ stateNamespaces: repeatedFlagValues(argv, "--state-namespace"),
23905
+ keySource: ctx.keySource
23906
+ });
23907
+ if (json) write(out, JSON.stringify(result, null, 2) + "\n");
23908
+ else {
23909
+ write(out, `exported: ${result.bundle_dir}
23910
+ `);
23911
+ write(out, `manifest_hash: ${result.manifest_hash}
23912
+ `);
23913
+ for (const item of result.unsupported_artifacts) {
23914
+ write(out, `unsupported: ${item}
23915
+ `);
23916
+ }
23917
+ }
23918
+ return 0;
23919
+ }
23920
+ if (command === "import") {
23921
+ const dir = argv[1];
23922
+ if (!dir) {
23923
+ write(err, "Usage: sanctuary exit import <dir> [--activate]\n");
23924
+ return 2;
23925
+ }
23926
+ const activate = hasFlag(argv, "--activate");
23927
+ if (activate) {
23928
+ const approved = await confirmTier1(
23929
+ "Tier 1 approval required: activate verified imported exit bundle?",
23930
+ hasFlag(argv, "--yes") || hasFlag(argv, "-y"),
23931
+ stdin,
23932
+ err
23933
+ );
23934
+ if (!approved) {
23935
+ write(err, "Aborted.\n");
23936
+ return 1;
23937
+ }
23938
+ }
23939
+ const ctx = await openExitContext(argv, env);
23940
+ const conflict = flagValue(argv, "--conflict") ?? "skip";
23941
+ if (!["skip", "overwrite", "version"].includes(conflict)) {
23942
+ write(err, "--conflict must be skip, overwrite, or version\n");
23943
+ return 2;
23944
+ }
23945
+ const result = await importExitBundle({
23946
+ bundleDir: dir,
23947
+ storage: ctx.storage,
23948
+ masterKey: ctx.masterKey,
23949
+ identityManager: ctx.identityManager,
23950
+ auditLog: ctx.auditLog,
23951
+ reputationStore: ctx.reputationStore,
23952
+ activate,
23953
+ conflictResolution: conflict,
23954
+ sourcePassphrase: flagValue(argv, "--source-passphrase"),
23955
+ sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
23956
+ destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
23957
+ });
23958
+ if (json) write(out, JSON.stringify(result, null, 2) + "\n");
23959
+ else {
23960
+ write(out, `verified: ${result.verified}
23961
+ `);
23962
+ write(out, `activated: ${result.activated}
23963
+ `);
23964
+ write(out, `state_conflicts: ${result.conflicts.state_conflicts.length}
23965
+ `);
23966
+ write(out, `reputation_conflicts: ${result.conflicts.reputation_conflicts.length}
23967
+ `);
23968
+ write(out, `state_status: ${result.state.status}
23969
+ `);
23970
+ write(out, `state_imported_keys: ${result.state.imported_keys}
23971
+ `);
23972
+ write(out, `reputation_imported_attestations: ${result.reputation.imported_attestations}
23973
+ `);
23974
+ for (const warning of result.warnings) write(out, `warning: ${warning}
23975
+ `);
23976
+ for (const item of result.unsupported_artifacts) {
23977
+ write(out, `unsupported: ${item}
23978
+ `);
23979
+ }
23980
+ }
23981
+ return result.verified ? 0 : 1;
23982
+ }
23983
+ write(err, `Unknown exit command: ${command}
23984
+ `);
23985
+ return 2;
23986
+ } catch (error) {
23987
+ write(err, error instanceof Error ? `${error.message}
23988
+ ` : `${String(error)}
23989
+ `);
23990
+ return 1;
23991
+ }
23992
+ }
23993
+
23994
+ // src/dashboard/aggregator.ts
23995
+ var L4_DEGRADATION_IMPACT = {
23996
+ critical: 40,
23997
+ warning: 25,
23998
+ info: 10
23999
+ };
24000
+ function computeL4LayerScore(degradations, status) {
24001
+ if (status === "compromised") return 0;
24002
+ let score = 100;
24003
+ for (const deg of degradations) {
24004
+ score -= L4_DEGRADATION_IMPACT[deg.severity] ?? 10;
24005
+ }
24006
+ score = Math.max(0, score);
24007
+ if (degradations.length === 0 && score > 50) {
24008
+ score = Math.min(100, score + 5);
24009
+ }
24010
+ return Math.round(score);
24011
+ }
24012
+ var MAX_ACTIVITY = 50;
24013
+ var MAX_AUDIT = 50;
24014
+ function fingerprintDID(did) {
24015
+ const raw = did.replace(/^did:[a-z0-9]+:/i, "");
24016
+ if (raw.length <= 12) return raw;
24017
+ return `${raw.slice(0, 6)}\u2026${raw.slice(-6)}`;
24018
+ }
24019
+ function countInjectionsToday(audit) {
24020
+ const startOfDay = /* @__PURE__ */ new Date();
24021
+ startOfDay.setHours(0, 0, 0, 0);
24022
+ const cutoff = startOfDay.getTime();
24023
+ return audit.filter((e) => {
24024
+ const ts = new Date(e.timestamp).getTime();
24025
+ if (isNaN(ts) || ts < cutoff) return false;
24026
+ const op = (e.operation ?? "").toLowerCase();
24027
+ return op.includes("injection") || op.includes("blocked");
24028
+ }).length;
24029
+ }
24030
+ var PROOF_CREATION_OPS = /* @__PURE__ */ new Set([
24031
+ "zk_prove",
24032
+ "zk_range_prove",
24033
+ "proof_commitment"
24034
+ ]);
24035
+ function countProofsToday(audit) {
24036
+ const startOfDay = /* @__PURE__ */ new Date();
24037
+ startOfDay.setHours(0, 0, 0, 0);
24038
+ const cutoff = startOfDay.getTime();
24039
+ return audit.filter((e) => {
24040
+ if (e.layer !== "l3") return false;
24041
+ if (!PROOF_CREATION_OPS.has(e.operation)) return false;
24042
+ const ts = new Date(e.timestamp).getTime();
24043
+ return !isNaN(ts) && ts >= cutoff;
24044
+ }).length;
24045
+ }
24046
+ function buildAgent(sources) {
24047
+ if (!sources.identityManager) {
24048
+ return {
24049
+ display_name: "Unclaimed agent",
24050
+ did: null,
24051
+ did_fingerprint: null,
24052
+ identity_count: 0,
24053
+ primary_identity_id: null
24054
+ };
24055
+ }
24056
+ const primary = sources.identityManager.getDefault();
24057
+ const identities = sources.identityManager.list();
24058
+ if (!primary) {
24059
+ return {
24060
+ display_name: "Unclaimed agent",
24061
+ did: null,
24062
+ did_fingerprint: null,
24063
+ identity_count: identities.length,
24064
+ primary_identity_id: null
24065
+ };
24066
+ }
24067
+ return {
24068
+ display_name: primary.label || "Sovereign agent",
24069
+ did: primary.did,
24070
+ did_fingerprint: fingerprintDID(primary.did),
24071
+ identity_count: identities.length,
24072
+ primary_identity_id: primary.identity_id
24073
+ };
24074
+ }
24075
+ function buildL1(sources, audit) {
24076
+ const hasIdentity = !!sources.identityManager?.getDefault();
24077
+ const state = hasIdentity ? "full" : "degraded";
24078
+ return {
24079
+ label: "L1 Cognitive",
24080
+ state,
24081
+ headline: hasIdentity ? "State encrypted at rest" : "No sovereign identity \u2014 run sanctuary_bootstrap",
24082
+ encryption: "AES-256-GCM + HKDF per namespace",
24083
+ injection_blocked_today: countInjectionsToday(audit),
24084
+ memory_attest_ready: hasIdentity
24085
+ };
24086
+ }
24087
+ function buildL2(sources) {
24088
+ const teeAvailable = sources.teeAvailable ?? false;
21482
24089
  const state = teeAvailable ? "full" : "degraded";
21483
24090
  return {
21484
24091
  label: "L2 Operational",
@@ -21613,6 +24220,36 @@ function buildUpstreamServers(sources) {
21613
24220
  return entry;
21614
24221
  });
21615
24222
  }
24223
+ function buildPrivacySummary(audit) {
24224
+ const classes = {};
24225
+ let filteredEvents = 0;
24226
+ let filteredSpans = 0;
24227
+ let lastFilteredAt = null;
24228
+ for (const entry of audit) {
24229
+ const details = entry.details ?? {};
24230
+ const rawFindings = details.privacy_findings;
24231
+ const findings = typeof rawFindings === "number" && Number.isFinite(rawFindings) ? Math.max(0, rawFindings) : 0;
24232
+ if (findings <= 0) continue;
24233
+ filteredEvents++;
24234
+ filteredSpans += findings;
24235
+ if (!lastFilteredAt || new Date(entry.timestamp).getTime() > new Date(lastFilteredAt).getTime()) {
24236
+ lastFilteredAt = entry.timestamp;
24237
+ }
24238
+ const rawClasses = details.privacy_classes;
24239
+ if (Array.isArray(rawClasses)) {
24240
+ for (const rawClass of rawClasses) {
24241
+ const key = String(rawClass);
24242
+ classes[key] = (classes[key] ?? 0) + 1;
24243
+ }
24244
+ }
24245
+ }
24246
+ return {
24247
+ filtered_events: filteredEvents,
24248
+ filtered_spans: filteredSpans,
24249
+ classes,
24250
+ last_filtered_at: lastFilteredAt
24251
+ };
24252
+ }
21616
24253
  async function getProtectionSnapshot(sources) {
21617
24254
  let audit = [];
21618
24255
  if (sources.auditLog) {
@@ -21630,6 +24267,7 @@ async function getProtectionSnapshot(sources) {
21630
24267
  const l4 = buildL4(sources);
21631
24268
  const activity = (sources.activity ?? []).slice(0, MAX_ACTIVITY);
21632
24269
  const pending_approvals = sources.pendingApprovals ?? [];
24270
+ const privacy = buildPrivacySummary(audit);
21633
24271
  const upstream_servers = buildUpstreamServers(sources);
21634
24272
  return {
21635
24273
  overall: computeOverall(l1, l2, l3, l4),
@@ -21638,6 +24276,7 @@ async function getProtectionSnapshot(sources) {
21638
24276
  activity,
21639
24277
  pending_approvals,
21640
24278
  audit: audit.slice(-MAX_AUDIT).reverse(),
24279
+ privacy,
21641
24280
  upstream_servers,
21642
24281
  mode: sources.mode,
21643
24282
  server_version: sources.server_version,
@@ -21751,7 +24390,7 @@ function l4EvidenceBlock(l4) {
21751
24390
  }
21752
24391
  function renderDashboardHTML(options) {
21753
24392
  const { snapshot } = options;
21754
- const { overall, agent, layers, activity, pending_approvals, audit, upstream_servers } = snapshot;
24393
+ const { overall, agent, layers, activity, pending_approvals, audit, privacy, upstream_servers } = snapshot;
21755
24394
  const activityRows = activity.length === 0 ? `<tr class="empty"><td colspan="5">Waiting for tool calls\u2026</td></tr>` : activity.map((entry) => {
21756
24395
  const time = new Date(entry.timestamp).toLocaleTimeString();
21757
24396
  return `<tr class="result-${escHtml(entry.result)}">
@@ -21784,6 +24423,8 @@ function renderDashboardHTML(options) {
21784
24423
  <span class="mono">${escHtml(s.name)}</span>
21785
24424
  <span class="server-meta">${escHtml(s.state)} \xB7 ${escHtml(s.tool_count)} tool${s.tool_count === 1 ? "" : "s"}</span>
21786
24425
  </li>`).join("");
24426
+ const privacyClasses = Object.entries(privacy.classes).sort((a, b) => b[1] - a[1]).map(([name, count]) => `<span class="privacy-chip">${escHtml(name)} ${escHtml(count)}</span>`).join("");
24427
+ const privacyLast = privacy.last_filtered_at ? new Date(privacy.last_filtered_at).toLocaleString() : "No filtering events yet";
21787
24428
  const initialSnapshot = JSON.stringify(snapshot).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
21788
24429
  return `<!DOCTYPE html>
21789
24430
  <html lang="en">
@@ -22061,6 +24702,13 @@ button { font: inherit; cursor: pointer; }
22061
24702
  .panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
22062
24703
  .panel-head { display: flex; justify-content: space-between; align-items: center; padding: 12px 18px; border-bottom: 1px solid var(--border); }
22063
24704
  .panel-head h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-dim); }
24705
+ .privacy-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
24706
+ .privacy-metric { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px 16px; }
24707
+ .privacy-metric dt { color: var(--ink-mute); font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
24708
+ .privacy-metric dd { color: var(--ink); font-size: 22px; font-weight: 700; }
24709
+ .privacy-metric dd.small { font-size: 13px; font-weight: 500; color: var(--ink-dim); line-height: 1.35; overflow-wrap: anywhere; }
24710
+ .privacy-classes { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
24711
+ .privacy-chip { border: 1px solid var(--border); color: var(--ink-dim); border-radius: 999px; padding: 4px 8px; font-size: 12px; background: rgba(255,255,255,0.03); }
22064
24712
  .filter-row { display: flex; gap: 6px; }
22065
24713
  .filter-row button {
22066
24714
  padding: 4px 10px;
@@ -22158,6 +24806,27 @@ details.audit-details .audit-filters { display: flex; gap: 6px; padding: 0 18px
22158
24806
  <ul class="server-list" id="server-list">${serverRows}</ul>
22159
24807
  </section>
22160
24808
 
24809
+ <section class="section">
24810
+ <h2>Privacy boundary <span class="count" id="privacy-count">${privacy.filtered_spans}</span></h2>
24811
+ <dl class="privacy-grid">
24812
+ <div class="privacy-metric">
24813
+ <dt>Filtered spans</dt>
24814
+ <dd id="privacy-spans">${escHtml(privacy.filtered_spans)}</dd>
24815
+ </div>
24816
+ <div class="privacy-metric">
24817
+ <dt>Filtered events</dt>
24818
+ <dd id="privacy-events">${escHtml(privacy.filtered_events)}</dd>
24819
+ </div>
24820
+ <div class="privacy-metric">
24821
+ <dt>Last filter</dt>
24822
+ <dd class="small" id="privacy-last">${escHtml(privacyLast)}</dd>
24823
+ </div>
24824
+ </dl>
24825
+ <div class="privacy-classes" id="privacy-classes">
24826
+ ${privacyClasses || `<span class="privacy-chip">No classes recorded</span>`}
24827
+ </div>
24828
+ </section>
24829
+
22161
24830
  <section class="section">
22162
24831
  <h2>Live activity <span class="count" id="activity-count">${activity.length}</span></h2>
22163
24832
  <div class="panel">
@@ -22285,6 +24954,23 @@ details.audit-details .audit-filters { display: flex; gap: 6px; padding: 0 18px
22285
24954
  )).join("");
22286
24955
  }
22287
24956
 
24957
+ function renderPrivacy(summary) {
24958
+ const count = document.getElementById("privacy-count");
24959
+ const spans = document.getElementById("privacy-spans");
24960
+ const events = document.getElementById("privacy-events");
24961
+ const last = document.getElementById("privacy-last");
24962
+ const classes = document.getElementById("privacy-classes");
24963
+ if (!summary || !spans || !events || !last || !classes) return;
24964
+ if (count) count.textContent = String(summary.filtered_spans || 0);
24965
+ spans.textContent = String(summary.filtered_spans || 0);
24966
+ events.textContent = String(summary.filtered_events || 0);
24967
+ last.textContent = summary.last_filtered_at ? new Date(summary.last_filtered_at).toLocaleString() : "No filtering events yet";
24968
+ const rows = Object.entries(summary.classes || {}).sort((a, b) => b[1] - a[1]);
24969
+ classes.innerHTML = rows.length
24970
+ ? rows.map(([name, n]) => '<span class="privacy-chip">' + esc(name) + ' ' + esc(n) + '</span>').join("")
24971
+ : '<span class="privacy-chip">No classes recorded</span>';
24972
+ }
24973
+
22288
24974
  function renderAll(snap) {
22289
24975
  snapshot = snap;
22290
24976
  renderShield(snap.overall.light, snap.overall.headline);
@@ -22294,6 +24980,7 @@ details.audit-details .audit-filters { display: flex; gap: 6px; padding: 0 18px
22294
24980
  document.getElementById("agent-did").textContent = snap.agent.did_fingerprint || "unclaimed";
22295
24981
  renderActivity(snap.activity);
22296
24982
  renderApprovals(snap.pending_approvals);
24983
+ renderPrivacy(snap.privacy);
22297
24984
  renderAudit(snap.audit, currentAuditFilter());
22298
24985
  }
22299
24986
 
@@ -22385,9 +25072,7 @@ var TEMPLATE_NAMES = [
22385
25072
  "coding-assistant",
22386
25073
  "ops-runner",
22387
25074
  "planner",
22388
- "handoff-coordinator",
22389
- "x-miner",
22390
- "github-miner"
25075
+ "handoff-coordinator"
22391
25076
  ];
22392
25077
  function isTemplateName(value) {
22393
25078
  return typeof value === "string" && TEMPLATE_NAMES.includes(value);
@@ -22763,88 +25448,6 @@ function applyChannelTemplate(id, params) {
22763
25448
  return entry.factory(params);
22764
25449
  }
22765
25450
 
22766
- // src/mesh/errors.ts
22767
- var MeshError = class extends Error {
22768
- constructor(message) {
22769
- super(message);
22770
- this.name = "MeshError";
22771
- }
22772
- };
22773
- var MeshEnvelopeError = class extends MeshError {
22774
- constructor(message) {
22775
- super(message);
22776
- this.name = "MeshEnvelopeError";
22777
- }
22778
- };
22779
- var MeshReservedExtensionKeyError = class extends MeshEnvelopeError {
22780
- constructor(key) {
22781
- super(
22782
- `v0.1 emitters MUST NOT populate reserved extension_envelope key: ${key}`
22783
- );
22784
- this.name = "MeshReservedExtensionKeyError";
22785
- }
22786
- };
22787
- var MeshReservedEventTypeError = class extends MeshEnvelopeError {
22788
- constructor(eventType) {
22789
- super(
22790
- `v0.1 emitters MUST NOT emit reserved-namespace event_type: ${eventType}`
22791
- );
22792
- this.name = "MeshReservedEventTypeError";
22793
- }
22794
- };
22795
-
22796
- // src/mesh/canonical-json.ts
22797
- var MeshCanonicalJsonError = class extends MeshError {
22798
- constructor(message) {
22799
- super(message);
22800
- this.name = "MeshCanonicalJsonError";
22801
- }
22802
- };
22803
- function canonicalize2(value) {
22804
- if (value === void 0) {
22805
- throw new MeshCanonicalJsonError(
22806
- "canonicalize(): top-level undefined is not serializable"
22807
- );
22808
- }
22809
- return encode(value);
22810
- }
22811
- function encode(value) {
22812
- if (value === null) return "null";
22813
- if (typeof value === "boolean") return value ? "true" : "false";
22814
- if (typeof value === "number") {
22815
- if (!Number.isFinite(value)) {
22816
- throw new MeshCanonicalJsonError(
22817
- `canonicalize(): non-finite number (${String(value)}) is not serializable`
22818
- );
22819
- }
22820
- return JSON.stringify(value);
22821
- }
22822
- if (typeof value === "string") return JSON.stringify(value);
22823
- if (Array.isArray(value)) return encodeArray(value);
22824
- if (typeof value === "object") return encodeObject(value);
22825
- throw new MeshCanonicalJsonError(
22826
- `canonicalize(): unsupported type ${typeof value}`
22827
- );
22828
- }
22829
- function encodeArray(arr) {
22830
- const parts = [];
22831
- for (const item of arr) {
22832
- parts.push(item === void 0 ? "null" : encode(item));
22833
- }
22834
- return "[" + parts.join(",") + "]";
22835
- }
22836
- function encodeObject(obj) {
22837
- const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
22838
- const parts = [];
22839
- for (const k of keys) {
22840
- parts.push(JSON.stringify(k) + ":" + encode(obj[k]));
22841
- }
22842
- return "{" + parts.join(",") + "}";
22843
- }
22844
- function canonicalizeToBytes(value) {
22845
- return new TextEncoder().encode(canonicalize2(value));
22846
- }
22847
-
22848
25451
  // src/policy-engine/canonical-policy.ts
22849
25452
  init_encoding();
22850
25453
 
@@ -23312,6 +25915,176 @@ function initTemplate(params) {
23312
25915
  });
23313
25916
  return { compiled, signed_event, bundle };
23314
25917
  }
25918
+ var DEFAULT_STORAGE_DIR = ".sanctuary";
25919
+ var KEYCHAIN_SERVICE_DEFAULT = "sanctuary-passphrase";
25920
+ function keychainServiceFor(storagePath, home = homedir()) {
25921
+ const defaultPath = join(home, DEFAULT_STORAGE_DIR);
25922
+ if (storagePath === defaultPath) return KEYCHAIN_SERVICE_DEFAULT;
25923
+ const digest = sha256(Buffer.from(storagePath, "utf-8"));
25924
+ const suffix = Buffer.from(digest).toString("hex").slice(0, 12);
25925
+ return `${KEYCHAIN_SERVICE_DEFAULT}-${suffix}`;
25926
+ }
25927
+ var RUNTIME_FILE_NAME = "runtime.json";
25928
+ function runtimePath(storagePath) {
25929
+ return join(storagePath, RUNTIME_FILE_NAME);
25930
+ }
25931
+ async function readTenantRuntime(storagePath) {
25932
+ try {
25933
+ const raw = await readFile(runtimePath(storagePath), "utf-8");
25934
+ const parsed = JSON.parse(raw);
25935
+ if (typeof parsed.dashboard_port !== "number" || typeof parsed.pid !== "number" || typeof parsed.started_at !== "string" || typeof parsed.version !== "string" || typeof parsed.dashboard_host !== "string" || typeof parsed.mode !== "string") {
25936
+ return null;
25937
+ }
25938
+ const state = {
25939
+ version: parsed.version,
25940
+ pid: parsed.pid,
25941
+ started_at: parsed.started_at,
25942
+ dashboard_host: parsed.dashboard_host,
25943
+ dashboard_port: parsed.dashboard_port,
25944
+ mode: parsed.mode
25945
+ };
25946
+ if (typeof parsed.webhook_callback_port === "number") {
25947
+ state.webhook_callback_port = parsed.webhook_callback_port;
25948
+ }
25949
+ if (typeof parsed.webhook_callback_host === "string") {
25950
+ state.webhook_callback_host = parsed.webhook_callback_host;
25951
+ }
25952
+ return state;
25953
+ } catch {
25954
+ return null;
25955
+ }
25956
+ }
25957
+
25958
+ // src/cli/agents/discovery.ts
25959
+ var EXTRAS_FILE_NAME = "agents-extra.json";
25960
+ async function isTenantDir(path) {
25961
+ const [hasState, hasProfile, hasFallback] = await Promise.all([
25962
+ dirExists(join(path, "state")),
25963
+ fileExists3(join(path, "cocoon-profile.json")),
25964
+ fileExists3(join(path, "passphrase.enc"))
25965
+ ]);
25966
+ const initialized = hasState;
25967
+ let passphraseStatus;
25968
+ if (hasFallback) passphraseStatus = "fallback-file";
25969
+ else if (hasProfile || hasState) passphraseStatus = "keychain";
25970
+ else passphraseStatus = "not-initialized";
25971
+ return { initialized, hasProfile, passphraseStatus };
25972
+ }
25973
+ async function dirExists(path) {
25974
+ try {
25975
+ const s = await stat(path);
25976
+ return s.isDirectory();
25977
+ } catch {
25978
+ return false;
25979
+ }
25980
+ }
25981
+ async function fileExists3(path) {
25982
+ try {
25983
+ const s = await stat(path);
25984
+ return s.isFile();
25985
+ } catch {
25986
+ return false;
25987
+ }
25988
+ }
25989
+ async function newestAuditMtime(storagePath) {
25990
+ const auditDir = join(storagePath, "state", "_audit");
25991
+ let entries = [];
25992
+ try {
25993
+ entries = await readdir(auditDir);
25994
+ } catch {
25995
+ return null;
25996
+ }
25997
+ let newest = 0;
25998
+ for (const name of entries) {
25999
+ try {
26000
+ const s = await stat(join(auditDir, name));
26001
+ if (s.isFile() && s.mtimeMs > newest) newest = s.mtimeMs;
26002
+ } catch {
26003
+ }
26004
+ }
26005
+ if (newest === 0) return null;
26006
+ return new Date(newest).toISOString();
26007
+ }
26008
+ async function readExtraPaths(root, env) {
26009
+ const out = [];
26010
+ const fromEnv = env.SANCTUARY_AGENTS_EXTRA_PATHS;
26011
+ if (fromEnv && fromEnv.length > 0) {
26012
+ for (const part of fromEnv.split(":")) {
26013
+ const trimmed = part.trim();
26014
+ if (trimmed.length > 0) out.push(resolve(trimmed));
26015
+ }
26016
+ }
26017
+ try {
26018
+ const raw = await readFile(join(root, EXTRAS_FILE_NAME), "utf-8");
26019
+ const parsed = JSON.parse(raw);
26020
+ if (Array.isArray(parsed)) {
26021
+ for (const p of parsed) {
26022
+ if (typeof p === "string" && p.trim().length > 0) out.push(resolve(p));
26023
+ }
26024
+ }
26025
+ } catch {
26026
+ }
26027
+ return Array.from(new Set(out));
26028
+ }
26029
+ async function describeTenant(name, storagePath, home) {
26030
+ const exists = await dirExists(storagePath);
26031
+ if (!exists) return null;
26032
+ const { initialized, hasProfile, passphraseStatus } = await isTenantDir(storagePath);
26033
+ if (!initialized && !hasProfile && passphraseStatus === "not-initialized") {
26034
+ return null;
26035
+ }
26036
+ const last_activity = await newestAuditMtime(storagePath);
26037
+ const runtime = await readTenantRuntime(storagePath);
26038
+ return {
26039
+ name,
26040
+ storage_path: storagePath,
26041
+ exists: true,
26042
+ initialized,
26043
+ has_cocoon_profile: hasProfile,
26044
+ keychain_service: keychainServiceFor(storagePath, home),
26045
+ passphrase_status: passphraseStatus,
26046
+ last_activity,
26047
+ runtime
26048
+ };
26049
+ }
26050
+ async function discoverTenants(options = {}) {
26051
+ const home = options.home ?? homedir();
26052
+ const env = options.env ?? process.env;
26053
+ const root = options.root ?? join(home, DEFAULT_STORAGE_DIR);
26054
+ const tenants = [];
26055
+ const rootTenant = await describeTenant("default", root, home);
26056
+ if (rootTenant) tenants.push(rootTenant);
26057
+ let children = [];
26058
+ try {
26059
+ children = await readdir(root);
26060
+ } catch {
26061
+ }
26062
+ for (const child of children) {
26063
+ const childPath = join(root, child);
26064
+ if (child.startsWith(".")) continue;
26065
+ if (child === "state" || child === "backup" || child === "config") continue;
26066
+ const s = await stat(childPath).catch(() => null);
26067
+ if (!s || !s.isDirectory()) continue;
26068
+ const desc = await describeTenant(child, childPath, home);
26069
+ if (desc) tenants.push(desc);
26070
+ }
26071
+ const extras = await readExtraPaths(root, env);
26072
+ for (const extra of extras) {
26073
+ if (tenants.some((t) => t.storage_path === extra)) continue;
26074
+ const desc = await describeTenant(basename(extra), extra, home);
26075
+ if (desc) tenants.push(desc);
26076
+ }
26077
+ tenants.sort((a, b) => {
26078
+ if (a.name === "default") return -1;
26079
+ if (b.name === "default") return 1;
26080
+ return a.name.localeCompare(b.name);
26081
+ });
26082
+ return tenants;
26083
+ }
26084
+ async function findTenant(name, options = {}) {
26085
+ const tenants = await discoverTenants(options);
26086
+ return tenants.find((t) => t.name === name) ?? null;
26087
+ }
23315
26088
 
23316
26089
  // src/dashboard/api.ts
23317
26090
  function constantTimeEquals(a, b) {
@@ -23465,6 +26238,18 @@ async function handleRequest(deps, req, res) {
23465
26238
  });
23466
26239
  return true;
23467
26240
  }
26241
+ const isAgentWrapped = deps.isAgentWrapped ?? (async (agentId) => {
26242
+ const tenant = await findTenant(agentId);
26243
+ if (!tenant) return false;
26244
+ return tenant.initialized || tenant.has_cocoon_profile;
26245
+ });
26246
+ if (!await isAgentWrapped(body.agent_name)) {
26247
+ writeJSON(res, 400, {
26248
+ error: "orphan_agent_id",
26249
+ message: `No wrapped harness found for agent_id "${body.agent_name}". Run \`sanctuary wrap\` to wrap the harness first, then retry template init.`
26250
+ });
26251
+ return true;
26252
+ }
23468
26253
  const nodeId = deps.nodeId ?? "dashboard-node";
23469
26254
  const nodePrivateKey = deps.nodePrivateKey ?? generateEphemeralKey();
23470
26255
  const principalId = deps.principalId ?? "dashboard-principal";
@@ -23573,11 +26358,11 @@ async function startDashboardServer(options) {
23573
26358
  }
23574
26359
  }
23575
26360
  });
23576
- await new Promise((resolve, reject) => {
26361
+ await new Promise((resolve4, reject) => {
23577
26362
  server.once("error", reject);
23578
26363
  server.listen(port, host, () => {
23579
26364
  server.off("error", reject);
23580
- resolve();
26365
+ resolve4();
23581
26366
  });
23582
26367
  });
23583
26368
  const actualPort = (() => {
@@ -23590,12 +26375,14 @@ async function startDashboardServer(options) {
23590
26375
  url,
23591
26376
  port: actualPort,
23592
26377
  host,
23593
- stop: () => new Promise((resolve, reject) => {
23594
- server.close((err) => err ? reject(err) : resolve());
26378
+ stop: () => new Promise((resolve4, reject) => {
26379
+ server.close((err) => err ? reject(err) : resolve4());
23595
26380
  }),
23596
26381
  publish,
23597
26382
  publishActivity: (entry) => publish({ type: "activity", data: entry }),
23598
- publishApproval: (approval) => publish({ type: "approval", data: approval })
26383
+ publishApproval: (approval) => publish({ type: "approval", data: approval }),
26384
+ publishInbox: (item) => publish({ type: "inbox", data: item }),
26385
+ publishAgentStatus: (snapshot) => publish({ type: "agent_status", data: snapshot })
23599
26386
  };
23600
26387
  }
23601
26388
 
@@ -23730,6 +26517,20 @@ async function createSanctuaryServer(options) {
23730
26517
  }
23731
26518
  }
23732
26519
  const auditLog = new AuditLog(storage, masterKey);
26520
+ try {
26521
+ await consumeResetHistoryMarker({
26522
+ storagePath: config.storage_path,
26523
+ auditLog
26524
+ });
26525
+ } catch (err) {
26526
+ if (err instanceof ResetHistoryMalformedError) {
26527
+ throw new Error(
26528
+ `Sanctuary: ${err.message}
26529
+ Refusing to start the cocoon while the reset-history marker is unreadable.`
26530
+ );
26531
+ }
26532
+ throw err;
26533
+ }
23733
26534
  const stateStore = new StateStore(storage, masterKey);
23734
26535
  const { tools: l1Tools, identityManager } = createL1Tools(
23735
26536
  stateStore,
@@ -24003,7 +26804,9 @@ async function createSanctuaryServer(options) {
24003
26804
  );
24004
26805
  const { tools: auditTools } = createAuditTools(config);
24005
26806
  const { tools: siemTools } = createSIEMTools(auditLog);
24006
- const { tools: contextGateTools, enforcer: contextGateEnforcer } = createContextGateTools(storage, masterKey, auditLog);
26807
+ const { tools: contextGateTools, enforcer: contextGateEnforcer } = createContextGateTools(storage, masterKey, auditLog, {
26808
+ privacyFilter: config.privacy_filter
26809
+ });
24007
26810
  const hardeningTools = createL2HardeningTools(config.storage_path, auditLog);
24008
26811
  const profileStore = new SovereigntyProfileStore(storage, masterKey);
24009
26812
  await profileStore.load();
@@ -24192,7 +26995,7 @@ async function createSanctuaryServer(options) {
24192
26995
  clientManager.configure(enabledServers).catch((err) => {
24193
26996
  console.error(`[Sanctuary] Failed to configure upstream servers: ${err instanceof Error ? err.message : "unknown error"}`);
24194
26997
  });
24195
- await new Promise((resolve) => setTimeout(resolve, 2e3));
26998
+ await new Promise((resolve4) => setTimeout(resolve4, 2e3));
24196
26999
  const proxiedTools = proxyRouter.getProxiedTools();
24197
27000
  if (proxiedTools.length > 0) {
24198
27001
  allTools.push(...proxiedTools);
@@ -24234,7 +27037,7 @@ async function createSanctuaryServer(options) {
24234
27037
  if (recoveryKey) {
24235
27038
  console.error(
24236
27039
  `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
24237
- \u2551 SANCTUARY: First Run \u2014 Recovery Key Generated \u2551
27040
+ \u2551 SANCTUARY: First Run, Recovery Key Generated \u2551
24238
27041
  \u2551 \u2551
24239
27042
  \u2551 Recovery Key: ${recoveryKey.slice(0, 20)}... \u2551
24240
27043
  \u2551 \u2551
@@ -24253,6 +27056,6 @@ async function createSanctuaryServer(options) {
24253
27056
  };
24254
27057
  }
24255
27058
 
24256
- export { ATTESTATION_VERSION, ApprovalGate, AuditLog, AutoApproveChannel, BaselineTracker, TEMPLATES as CONTEXT_GATE_TEMPLATES, CallbackApprovalChannel, ClientManager, CommitmentStore, ContextGateEnforcer, ContextGatePolicyStore, DashboardApprovalChannel, FederationRegistry, FilesystemStorage, HERO_COPY, InMemoryModelProvenanceStore, InjectionDetector, MODEL_PRESETS, MemoryStorage, PolicyStore, ProxyRouter, ReputationStore, SovereigntyProfileStore, StateStore, StderrApprovalChannel, TIER_WEIGHTS, WebhookApprovalChannel, canonicalize, classifyField, completeHandshake, computeWeightedScore, createBridgeCommitment, createDefaultProfile, createPedersenCommitment, createProofOfKnowledge, createRangeProof, createSanctuaryServer, evaluateField, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate, initiateHandshake, listTemplateIds, loadConfig, loadPrincipalPolicy, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };
27059
+ export { ATTESTATION_VERSION, ApprovalGate, AuditLog, AutoApproveChannel, BaselineTracker, TEMPLATES as CONTEXT_GATE_TEMPLATES, CallbackApprovalChannel, ClientManager, CommitmentStore, ContextGateEnforcer, ContextGatePolicyStore, DashboardApprovalChannel, FederationRegistry, FilesystemStorage, HERO_COPY, InMemoryModelProvenanceStore, InjectionDetector, MODEL_PRESETS, MemoryStorage, PolicyStore, ProxyRouter, ReputationStore, SovereigntyProfileStore, StateStore, StderrApprovalChannel, TIER_WEIGHTS, WebhookApprovalChannel, canonicalize, classifyField, completeHandshake, computeWeightedScore, createBridgeCommitment, createDefaultProfile, createPedersenCommitment, createProofOfKnowledge, createRangeProof, createSanctuaryServer, evaluateField, exitBundleManifestShape, exportExitBundle, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate, importExitBundle, initiateHandshake, listTemplateIds, loadConfig, loadExitArtifact, loadPrincipalPolicy, readManifest, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, runExitCommand, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyExitBundle, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };
24257
27060
  //# sourceMappingURL=index.js.map
24258
27061
  //# sourceMappingURL=index.js.map