@ahood/cli 0.8.7 → 0.8.8
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/commands/add.js +19 -4
- package/dist/http.js +25 -1
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -480,10 +480,10 @@ export async function resolveMcpServerConfig(buffer, options = {}) {
|
|
|
480
480
|
throw new Error(`${sanitizeForTerminal(variable.name)} is required by ${sanitizeForTerminal(manifest.name)} but is not set -- export ${sanitizeForTerminal(variable.name)} and re-run.`);
|
|
481
481
|
}
|
|
482
482
|
}
|
|
483
|
-
// Required-ness
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
483
|
+
// Required-ness is consulted on the secret path only in the one branch
|
|
484
|
+
// where a secret can still end up unresolved: the unattended update guard
|
|
485
|
+
// below. Every other branch either resolves a value or prompts for one, so
|
|
486
|
+
// an `is_required` check there would change nothing (ahood-cli#120).
|
|
487
487
|
const secretNames = [];
|
|
488
488
|
for (const variable of envVars) {
|
|
489
489
|
if (!variable.is_secret)
|
|
@@ -511,6 +511,21 @@ export async function resolveMcpServerConfig(buffer, options = {}) {
|
|
|
511
511
|
continue;
|
|
512
512
|
}
|
|
513
513
|
if (!allowNonTtyPrompt && !process.stdin.isTTY) {
|
|
514
|
+
// Optional means optional here too, exactly as it does on the
|
|
515
|
+
// non-secret pass above (ahood-cli#126). This guard used to fire for
|
|
516
|
+
// ANY unresolved secret, so a single artifact declaring an optional
|
|
517
|
+
// token that nothing had exported aborted the whole `ahood skill
|
|
518
|
+
// update` walk -- and said the variable "is required" while the
|
|
519
|
+
// manifest that declared it said otherwise. Announced rather than
|
|
520
|
+
// silent because skipping and carrying forward leave different entries
|
|
521
|
+
// in .mcp.json and nothing else distinguishes them; the notice is
|
|
522
|
+
// confined to this branch, so an interactive `add` (which still prompts
|
|
523
|
+
// for optional secrets) and a routine update whose credential carries
|
|
524
|
+
// forward both stay quiet.
|
|
525
|
+
if (!variable.is_required) {
|
|
526
|
+
console.warn(`${sanitizeForTerminal(variable.name)} is optional and is not set -- updating ${sanitizeForTerminal(manifest.name)} without it.`);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
514
529
|
throw new Error(`${sanitizeForTerminal(variable.name)} is required by ${sanitizeForTerminal(manifest.name)} but is not set, and there is no terminal to prompt on -- export ${sanitizeForTerminal(variable.name)} and re-run.`);
|
|
515
530
|
}
|
|
516
531
|
env[variable.name] = await promptSecret(`${sanitizeForTerminal(variable.name)} (${sanitizeForTerminal(variable.description)}): `);
|
package/dist/http.js
CHANGED
|
@@ -37,6 +37,30 @@ export async function apiFetch(path, init = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
40
|
+
// The HTML/length checks below (ahood-cli#31) let a *short, non-HTML* body
|
|
41
|
+
// through verbatim, which meant an error string could still carry ANSI escapes
|
|
42
|
+
// straight to the terminal (ahood-cli#127). That matters here more than in the
|
|
43
|
+
// archive-content path ahood-cli#122 hardened, because sanitizeErrorMessage
|
|
44
|
+
// sits on every apiJson call in the CLI -- including `add`, which prints
|
|
45
|
+
// server-sourced text immediately before a masked secret prompt, the exact
|
|
46
|
+
// moment a cursor-movement/line-clear sequence can repaint what the user sees.
|
|
47
|
+
//
|
|
48
|
+
// Control characters are replaced with a space rather than deleted so that
|
|
49
|
+
// stripping can't silently glue two tokens into one misleading word.
|
|
50
|
+
//
|
|
51
|
+
// Newlines (\x0a) and carriage returns (\x0d) are inside this range and are
|
|
52
|
+
// deliberately flattened too: a forged prompt line ("...\n\nEnter your token:")
|
|
53
|
+
// needs no escape sequence at all, just a newline, so preserving them would
|
|
54
|
+
// leave the spoofing half of the threat model open while closing the escape
|
|
55
|
+
// half. The readability cost is small because anything reaching this branch is
|
|
56
|
+
// already capped at MAX_ERROR_MESSAGE_LENGTH -- a few lines' worth of text, not
|
|
57
|
+
// a stack trace.
|
|
58
|
+
//
|
|
59
|
+
// Known duplication: this is the same substitution as `sanitizeForTerminal` in
|
|
60
|
+
// src/commands/add.ts (ahood-cli#122). It is copied rather than shared on
|
|
61
|
+
// purpose -- promoting a shared helper means editing add.ts's call sites, which
|
|
62
|
+
// is follow-up refactor work kept out of this security fix; see ahood-cli#127.
|
|
63
|
+
const CONTROL_CHARACTERS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
40
64
|
// Defense in depth (ahood-cli#31): the API is expected to never forward a raw
|
|
41
65
|
// upstream error body (an HTML block page from a proxy/WAF, a giant stack
|
|
42
66
|
// trace, etc.) into `error`, but this CLI shouldn't trust that unconditionally
|
|
@@ -46,7 +70,7 @@ const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
|
46
70
|
export function sanitizeErrorMessage(message) {
|
|
47
71
|
const looksLikeHtml = /<!DOCTYPE|<html[\s>]/i.test(message);
|
|
48
72
|
if (!looksLikeHtml && message.length <= MAX_ERROR_MESSAGE_LENGTH)
|
|
49
|
-
return message;
|
|
73
|
+
return message.replace(CONTROL_CHARACTERS, " ");
|
|
50
74
|
// No preview of the raw content: an HTML-shaped body's first bytes are
|
|
51
75
|
// exactly where infra details (e.g. a Cloudflare block page's title, Ray
|
|
52
76
|
// ID) live, so a "helpful" excerpt would leak the same details this
|
package/package.json
CHANGED