diff --git a/agap-mcp/src/vaultwarden.js b/agap-mcp/src/vaultwarden.js index 46a1213..fd80166 100644 --- a/agap-mcp/src/vaultwarden.js +++ b/agap-mcp/src/vaultwarden.js @@ -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 --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(''); + 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() {