@sanctuary-framework/mcp-server 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -13357,7 +13357,7 @@ function renderDashboardV11Html(options = {}) {
13357
13357
  streamUrl,
13358
13358
  identityId,
13359
13359
  fortressId
13360
- });
13360
+ }).replace(/</g, "\\u003c");
13361
13361
  const clientBlock = embedClient ? `<script type="module">${getClientScript()}</script>` : `<!-- client script omitted by render option -->`;
13362
13362
  return `<!doctype html>
13363
13363
  <html lang="en">
@@ -13388,7 +13388,7 @@ function renderDashboardV11Html(options = {}) {
13388
13388
  <aside class="fortress" id="fortress"><p class="muted">Loading fortress column.</p></aside>
13389
13389
  </div>
13390
13390
  <div id="toast-host" aria-live="polite"></div>
13391
- <script id="dashboard-config" type="application/json">${escHtml3(config)}</script>
13391
+ <script id="dashboard-config" type="application/json">${config}</script>
13392
13392
  ${clientBlock}
13393
13393
  </body>
13394
13394
  </html>`;
@@ -27536,14 +27536,14 @@ var init_generator2 = __esm({
27536
27536
  ]);
27537
27537
  }
27538
27538
  });
27539
- function printRecoveryKeyBanner(recoveryKey, filePath, output = process.stderr) {
27539
+ function printSecretBanner(secret, filePath, copy, output = process.stderr) {
27540
27540
  const lines = [
27541
- "SANCTUARY: First Run, Recovery Key Generated",
27541
+ copy.bannerHeader,
27542
27542
  "",
27543
- `Recovery Key: ${recoveryKey}`,
27543
+ `${copy.bannerSecretLabel}: ${secret}`,
27544
27544
  "",
27545
- "SAVE THIS KEY. It will not be shown again.",
27546
- "Without it, your encrypted state is unrecoverable.",
27545
+ copy.bannerSaveLine,
27546
+ copy.bannerLossLine,
27547
27547
  "",
27548
27548
  "Plaintext copy written to:",
27549
27549
  ` ${filePath}`,
@@ -27562,8 +27562,8 @@ ${bottom}
27562
27562
 
27563
27563
  `);
27564
27564
  }
27565
- async function writeRecoveryKeyFile(opts) {
27566
- const filePath = path.join(opts.storagePath, RECOVERY_KEY_FILENAME);
27565
+ async function writeSecretFile(opts) {
27566
+ const filePath = path.join(opts.storagePath, opts.copy.fileName);
27567
27567
  try {
27568
27568
  await promises.access(filePath, promises.constants.F_OK);
27569
27569
  return { filePath, written: false };
@@ -27573,54 +27573,47 @@ async function writeRecoveryKeyFile(opts) {
27573
27573
  const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
27574
27574
  const fortressLine = opts.fortressId ? `Fortress: ${opts.fortressId}
27575
27575
  ` : "";
27576
- const content = `SANCTUARY RECOVERY KEY \u2014 DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.
27576
+ const content = `${opts.copy.fileWarningHeader}
27577
27577
  Generated: ${now}
27578
27578
  ` + fortressLine + `
27579
- Recovery key:
27580
- ${opts.recoveryKey}
27581
-
27582
- This file was created on first init. Sanctuary will NOT regenerate this file on
27583
- subsequent runs and will NOT display the key again. After moving this file off
27584
- the host (encrypted backup, password manager, paper safe), delete it from the
27585
- fortress directory. Do NOT keep it in the fortress; the recovery key bypasses
27586
- the cocoon passphrase by design.
27587
- `;
27579
+ ${opts.copy.fileSecretLabel}
27580
+ ${opts.secret}
27581
+
27582
+ ` + opts.copy.fileBody;
27588
27583
  await promises.writeFile(filePath, content, { mode: 384 });
27589
27584
  return { filePath, written: true };
27590
27585
  }
27591
- async function confirmRecoveryKeySaved(io) {
27586
+ async function confirmSecretSaved(copy, declinedError, nonInteractiveError, io) {
27592
27587
  const input = io?.input ?? process.stdin;
27593
27588
  const output = io?.output ?? process.stderr;
27594
27589
  const realStdin = !io && process.stdin.isTTY !== true;
27595
27590
  if (realStdin) {
27596
- throw new RecoveryKeyConfirmationNonInteractiveError();
27591
+ throw new nonInteractiveError();
27597
27592
  }
27598
27593
  const rl = promises$1.createInterface({ input, output });
27599
27594
  try {
27600
27595
  const answer = await rl.question(
27601
- "Have you saved the recovery key? [y/N] "
27596
+ `Have you saved the ${copy.promptLabel}? [y/N] `
27602
27597
  );
27603
27598
  const normalized = answer.trim().toLowerCase();
27604
27599
  if (normalized !== "y" && normalized !== "yes") {
27605
- throw new RecoveryKeyConfirmationDeclinedError();
27600
+ throw new declinedError();
27606
27601
  }
27607
27602
  } finally {
27608
27603
  rl.close();
27609
27604
  }
27610
27605
  }
27611
- async function discloseRecoveryKey(opts) {
27606
+ async function discloseSecret(opts, copy, declinedError, nonInteractiveError) {
27612
27607
  const mode = opts.mode ?? "interactive";
27613
- const fileResult = await writeRecoveryKeyFile({
27608
+ const writeOpts = {
27614
27609
  storagePath: opts.storagePath,
27615
- recoveryKey: opts.recoveryKey,
27616
- ...opts.fortressId !== void 0 ? { fortressId: opts.fortressId } : {},
27617
- ...opts.now !== void 0 ? { now: opts.now } : {}
27618
- });
27619
- printRecoveryKeyBanner(
27620
- opts.recoveryKey,
27621
- fileResult.filePath,
27622
- opts.io?.output
27623
- );
27610
+ secret: opts.secret,
27611
+ copy
27612
+ };
27613
+ if (opts.fortressId !== void 0) writeOpts.fortressId = opts.fortressId;
27614
+ if (opts.now !== void 0) writeOpts.now = opts.now;
27615
+ const fileResult = await writeSecretFile(writeOpts);
27616
+ printSecretBanner(opts.secret, fileResult.filePath, copy, opts.io?.output);
27624
27617
  if (mode === "no-confirm" || mode === "stdio-server") {
27625
27618
  return {
27626
27619
  filePath: fileResult.filePath,
@@ -27628,17 +27621,72 @@ async function discloseRecoveryKey(opts) {
27628
27621
  confirmed: false
27629
27622
  };
27630
27623
  }
27631
- await confirmRecoveryKeySaved(opts.io);
27624
+ await confirmSecretSaved(copy, declinedError, nonInteractiveError, opts.io);
27632
27625
  return {
27633
27626
  filePath: fileResult.filePath,
27634
27627
  fileWritten: fileResult.written,
27635
27628
  confirmed: true
27636
27629
  };
27637
27630
  }
27638
- var RECOVERY_KEY_FILENAME, RecoveryKeyConfirmationDeclinedError, RecoveryKeyConfirmationNonInteractiveError;
27631
+ async function discloseRecoveryKey(opts) {
27632
+ const internalOpts = {
27633
+ secret: opts.recoveryKey,
27634
+ storagePath: opts.storagePath
27635
+ };
27636
+ if (opts.fortressId !== void 0) internalOpts.fortressId = opts.fortressId;
27637
+ if (opts.mode !== void 0) internalOpts.mode = opts.mode;
27638
+ if (opts.now !== void 0) internalOpts.now = opts.now;
27639
+ if (opts.io !== void 0) internalOpts.io = opts.io;
27640
+ return discloseSecret(
27641
+ internalOpts,
27642
+ RECOVERY_KEY_COPY,
27643
+ RecoveryKeyConfirmationDeclinedError,
27644
+ RecoveryKeyConfirmationNonInteractiveError
27645
+ );
27646
+ }
27647
+ async function disclosePassphrase(opts) {
27648
+ const internalOpts = {
27649
+ secret: opts.passphrase,
27650
+ storagePath: opts.storagePath
27651
+ };
27652
+ if (opts.fortressId !== void 0) internalOpts.fortressId = opts.fortressId;
27653
+ if (opts.mode !== void 0) internalOpts.mode = opts.mode;
27654
+ if (opts.now !== void 0) internalOpts.now = opts.now;
27655
+ if (opts.io !== void 0) internalOpts.io = opts.io;
27656
+ return discloseSecret(
27657
+ internalOpts,
27658
+ PASSPHRASE_BACKUP_COPY,
27659
+ PassphraseConfirmationDeclinedError,
27660
+ PassphraseConfirmationNonInteractiveError
27661
+ );
27662
+ }
27663
+ var RECOVERY_KEY_FILENAME, PASSPHRASE_BACKUP_FILENAME, RECOVERY_KEY_COPY, PASSPHRASE_BACKUP_COPY, RecoveryKeyConfirmationDeclinedError, RecoveryKeyConfirmationNonInteractiveError, PassphraseConfirmationDeclinedError, PassphraseConfirmationNonInteractiveError;
27639
27664
  var init_recovery_key_disclosure = __esm({
27640
27665
  "src/cocoon/recovery-key-disclosure.ts"() {
27641
27666
  RECOVERY_KEY_FILENAME = "recovery-key.txt";
27667
+ PASSPHRASE_BACKUP_FILENAME = "passphrase-backup.txt";
27668
+ RECOVERY_KEY_COPY = {
27669
+ fileName: RECOVERY_KEY_FILENAME,
27670
+ bannerHeader: "SANCTUARY: First Run, Recovery Key Generated",
27671
+ bannerSecretLabel: "Recovery Key",
27672
+ bannerSaveLine: "SAVE THIS KEY. It will not be shown again.",
27673
+ bannerLossLine: "Without it, your encrypted state is unrecoverable.",
27674
+ fileWarningHeader: "SANCTUARY RECOVERY KEY, DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.",
27675
+ fileSecretLabel: "Recovery key:",
27676
+ fileBody: "This file was created on first init. Sanctuary will NOT regenerate this file on\nsubsequent runs and will NOT display the key again. After moving this file off\nthe host (encrypted backup, password manager, paper safe), delete it from the\nfortress directory. Do NOT keep it in the fortress; the recovery key bypasses\nthe cocoon passphrase by design.\n",
27677
+ promptLabel: "recovery key"
27678
+ };
27679
+ PASSPHRASE_BACKUP_COPY = {
27680
+ fileName: PASSPHRASE_BACKUP_FILENAME,
27681
+ bannerHeader: "SANCTUARY: First Run, Passphrase Generated",
27682
+ bannerSecretLabel: "Passphrase",
27683
+ bannerSaveLine: "SAVE THIS PASSPHRASE. It will not be shown again.",
27684
+ bannerLossLine: "Without it, your encrypted state is unrecoverable.",
27685
+ fileWarningHeader: "SANCTUARY PASSPHRASE, DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.",
27686
+ fileSecretLabel: "Passphrase:",
27687
+ fileBody: "This file was created on first wrap when Sanctuary generated the passphrase.\nSanctuary will NOT regenerate this file on subsequent runs and will NOT display\nthe passphrase again. After moving this file off the host (encrypted backup,\npassword manager, paper safe), delete it from the fortress directory. Do NOT\nkeep it in the fortress; the keychain copy is recoverable only while the host\nand its OS keyring are intact.\n",
27688
+ promptLabel: "passphrase"
27689
+ };
27642
27690
  RecoveryKeyConfirmationDeclinedError = class extends Error {
27643
27691
  constructor() {
27644
27692
  super(
@@ -27655,6 +27703,22 @@ var init_recovery_key_disclosure = __esm({
27655
27703
  this.name = "RecoveryKeyConfirmationNonInteractiveError";
27656
27704
  }
27657
27705
  };
27706
+ PassphraseConfirmationDeclinedError = class extends Error {
27707
+ constructor() {
27708
+ super(
27709
+ "Passphrase confirmation declined. Save the passphrase (printed above and written to passphrase-backup.txt) before re-running wrap."
27710
+ );
27711
+ this.name = "PassphraseConfirmationDeclinedError";
27712
+ }
27713
+ };
27714
+ PassphraseConfirmationNonInteractiveError = class extends Error {
27715
+ constructor() {
27716
+ super(
27717
+ "Passphrase confirmation requires an interactive terminal. Re-run with --no-open for scripted use, or run from a TTY."
27718
+ );
27719
+ this.name = "PassphraseConfirmationNonInteractiveError";
27720
+ }
27721
+ };
27658
27722
  }
27659
27723
  });
27660
27724
 
@@ -31443,6 +31507,27 @@ async function runWrap(options, deps = {}) {
31443
31507
  );
31444
31508
  }
31445
31509
  await promises.mkdir(storagePath, { recursive: true, mode: 448 });
31510
+ if (passphraseSource === "generated" && passphraseValue !== void 0) {
31511
+ try {
31512
+ await disclosePassphrase({
31513
+ passphrase: passphraseValue,
31514
+ storagePath,
31515
+ fortressId: fortressIdFromStoragePath(storagePath),
31516
+ // --no-open (CI / scripted) or non-TTY stdin both skip the prompt
31517
+ // the same way init's --no-confirm does. Operator who scripted the
31518
+ // call still gets the banner + the file; they will not see a hang.
31519
+ mode: options.noOpen || process.stdin.isTTY !== true ? "no-confirm" : "interactive"
31520
+ });
31521
+ } catch (err) {
31522
+ if (err instanceof PassphraseConfirmationDeclinedError || err instanceof PassphraseConfirmationNonInteractiveError) {
31523
+ console.error(`
31524
+ Sanctuary wrap: ${err.message}
31525
+ `);
31526
+ process.exit(2);
31527
+ }
31528
+ throw err;
31529
+ }
31530
+ }
31446
31531
  const profile = createWrapProfile(upstreamServers);
31447
31532
  const profilePath = path.join(storagePath, "cocoon-profile.json");
31448
31533
  await promises.writeFile(profilePath, JSON.stringify(profile, null, 2), {
@@ -31903,6 +31988,7 @@ var init_cli2 = __esm({
31903
31988
  init_config();
31904
31989
  init_paths();
31905
31990
  init_runtime();
31991
+ init_recovery_key_disclosure();
31906
31992
  COCOON_GOVERNOR_DEFAULTS = {
31907
31993
  volume_limit: 200,
31908
31994
  rate_limit_per_tool: 20,