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) {
|
||||
return execFileSync(BW, args, {
|
||||
env: bwEnv(),
|
||||
encoding: 'utf8',
|
||||
input,
|
||||
stdio: input ? ['pipe','pipe','pipe'] : ['ignore','pipe','pipe'],
|
||||
}).trim();
|
||||
try {
|
||||
return execFileSync(BW, args, {
|
||||
env: bwEnv(),
|
||||
encoding: 'utf8',
|
||||
input,
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user