T09 · Insecure Skill Coding Practices
- Location
scripts/rbw-remote-unlock/server.mjs:255- Finding
Predictable Shared FIFO Exposes the Vault Password to Same-User Race Attacks
- Content
View full analysis
Vulnerability Details
File Location:
scripts/rbw-remote-unlock/server.mjs, lines 17 and 255–278;scripts/rbw-remote-unlock/pinentry.sh, lines 5 and 31–33
Vulnerability Type: Predictable temporary-file path and non-atomic FIFO creation
Risk Level: MediumVulnerable Code
scripts/rbw-remote-unlock/server.mjs:js const PASSWORD_FIFO = process.env.PASSWORD_FIFO || '/tmp/rbw-remote-unlock-password.fifo'; async function attemptUnlock(password) { console.error('rbw remote unlock: unlock attempt started'); await fs.rm(PASSWORD_FIFO, { force: true }).catch(() => {}); const mkfifoResult = await runCommand('mkfifo', ['-m', '600', PASSWORD_FIFO], { env: process.env, timeoutMs: 5_000, }); if (mkfifoResult.code !== 0) { throw new Error(summarizeCommandFailure(mkfifoResult, 'mkfifo failed')); } const fifoWriter = spawn('bash', ['-lc', 'for _ in 1 2 3; do printf "%s\\n" "$RBW_REMOTE_UNLOCK_PASSWORD" > "$PASSWORD_FIFO" || break; done'], { env: { ...process.env, RBW_REMOTE_UNLOCK_PASSWORD: password, PASSWORD_FIFO, }, stdio: 'ignore', }); try { const childEnv = { ...process.env, PASSWORD_FIFO, }; const unlockResult = await runCommand(RBW_BIN, ['unlock'], { env: childEnv }); if (unlockResult.code !== 0) { const msg = summarizeCommandFailure(unlockResult, 'rbw unlock failed'); console.error(`rbw remote unlock: unlock attempt failed: ${msg}`); throw new Error(msg); } console.error('rbw remote unlock: unlock attempt succeeded'); } finally { fifoWriter.kill('SIGTERM'); await fs.rm(PASSWORD_FIFO, { force: true }).catch(() => {}); } }scripts/rbw-remote-unlock/pinentry.sh:bash password_fifo="${PASSWORD_FIFO:-/tmp/rbw-remote-unlock-password.fifo}" if [[ -z "$password" && -p "$password_fifo" ]]; then IFS= read -r -t 10 pass ...[truncated 2615 chars]- Remediation
View remediation
Remediation Suggestions
- Eliminate the filesystem FIFO where possible. Pass the password through an anonymous pipe connected directly between the controlled parent and child processes so no shared pathname can be raced.
- If a FIFO is required, create a unique private directory for every invocation using
fs.mkdtemp()under a secure runtime location such as$XDG_RUNTIME_DIR. Set the directory permissions to0700. - Generate an unpredictable FIFO name inside that private directory and pass the exact path only to the required child processes.
- Before use, validate the object with
lstat: verify that it is a FIFO, is owned by the expected user, and has no group or world permissions. - Do not use a shared fallback pathname. Make failure to create a private per-run location fatal.
- Remove the FIFO and private directory in all normal, error, timeout, and signal-handling paths.
- Add concurrency tests that start multiple helpers simultaneously and verify that credentials cannot cross instance boundaries.
- Minimize secret exposure in process environments as an additional defense. Avoid supplying the master password through
RBW_REMOTE_UNLOCK_PASSWORDwhen direct pipe-based transfer is feasible.
