@sanctuary-framework/mcp-server 1.1.2 → 1.1.3
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 +120 -34
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +125 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +96 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +96 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -27536,14 +27536,14 @@ var init_generator2 = __esm({
|
|
|
27536
27536
|
]);
|
|
27537
27537
|
}
|
|
27538
27538
|
});
|
|
27539
|
-
function
|
|
27539
|
+
function printSecretBanner(secret, filePath, copy, output = process.stderr) {
|
|
27540
27540
|
const lines = [
|
|
27541
|
-
|
|
27541
|
+
copy.bannerHeader,
|
|
27542
27542
|
"",
|
|
27543
|
-
|
|
27543
|
+
`${copy.bannerSecretLabel}: ${secret}`,
|
|
27544
27544
|
"",
|
|
27545
|
-
|
|
27546
|
-
|
|
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
|
|
27566
|
-
const filePath = path.join(opts.storagePath,
|
|
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 =
|
|
27576
|
+
const content = `${opts.copy.fileWarningHeader}
|
|
27577
27577
|
Generated: ${now}
|
|
27578
27578
|
` + fortressLine + `
|
|
27579
|
-
|
|
27580
|
-
${opts.
|
|
27581
|
-
|
|
27582
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
27600
|
+
throw new declinedError();
|
|
27606
27601
|
}
|
|
27607
27602
|
} finally {
|
|
27608
27603
|
rl.close();
|
|
27609
27604
|
}
|
|
27610
27605
|
}
|
|
27611
|
-
async function
|
|
27606
|
+
async function discloseSecret(opts, copy, declinedError, nonInteractiveError) {
|
|
27612
27607
|
const mode = opts.mode ?? "interactive";
|
|
27613
|
-
const
|
|
27608
|
+
const writeOpts = {
|
|
27614
27609
|
storagePath: opts.storagePath,
|
|
27615
|
-
|
|
27616
|
-
|
|
27617
|
-
|
|
27618
|
-
|
|
27619
|
-
|
|
27620
|
-
|
|
27621
|
-
|
|
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
|
|
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
|
-
|
|
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,
|