agap-mcp: stop leaking BW master password via bw error messages
vaultwarden.js run() passed BW_PASSWORD as argv to `bw login`/`bw unlock`.
On any non-zero exit, execFileSync throws an Error whose message is
"Command failed: bw unlock <BW_PASSWORD> --raw", which propagated to
server.js's `console.error('Init failed:', e.message)` -> the master password
landed in container stdout / `docker logs` on every Vaultwarden init failure
(wrong password, server down, TLS reset). Empirically confirmed by a security
audit 2026-07-24.
run() now catches the exec error and re-throws with the argv stripped: only the
subcommand, exit code, and stderr survive, and BW_PASSWORD is scrubbed from
stderr defensively. Verified: a forced failure yields "bw unlock failed (exit 1)"
with no password substring.
Not yet active: the running agap-mcp container predates this file; a rebuild
(`docker compose build agap-mcp && docker compose up -d agap-mcp`) is needed to
deploy it. The live container is still vulnerable until then.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,12 +15,29 @@ function bwEnv() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function run(args, input) {
|
function run(args, input) {
|
||||||
return execFileSync(BW, args, {
|
try {
|
||||||
env: bwEnv(),
|
return execFileSync(BW, args, {
|
||||||
encoding: 'utf8',
|
env: bwEnv(),
|
||||||
input,
|
encoding: 'utf8',
|
||||||
stdio: input ? ['pipe','pipe','pipe'] : ['ignore','pipe','pipe'],
|
input,
|
||||||
}).trim();
|
stdio: input ? ['pipe','pipe','pipe'] : ['ignore','pipe','pipe'],
|
||||||
|
}).trim();
|
||||||
|
} catch (e) {
|
||||||
|
// SECURITY: execFileSync puts the FULL argv in e.message
|
||||||
|
// ("Command failed: bw unlock <BW_PASSWORD> --raw"), and login/unlock pass
|
||||||
|
// the master password as argv. That message propagates to console.error /
|
||||||
|
// tool errors -> docker logs. Re-throw with the argv stripped: keep only the
|
||||||
|
// subcommand + exit code + stderr, and scrub the password out of stderr too
|
||||||
|
// (defensive; bw doesn't normally echo it). Never let argv reach a log.
|
||||||
|
const sub = Array.isArray(args) && args.length ? args[0] : '?';
|
||||||
|
let stderr = (e && e.stderr ? e.stderr.toString() : '').trim();
|
||||||
|
const secret = process.env.BW_PASSWORD;
|
||||||
|
if (secret && stderr.includes(secret)) stderr = stderr.split(secret).join('<redacted>');
|
||||||
|
const err = new Error(`bw ${sub} failed (exit ${e && e.status != null ? e.status : '?'})` +
|
||||||
|
(stderr ? `: ${stderr}` : ''));
|
||||||
|
err.status = e && e.status;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initVaultwarden() {
|
export async function initVaultwarden() {
|
||||||
|
|||||||
Reference in New Issue
Block a user