T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/rbw-remote-unlock/server.mjs:255
- Finding
- Predictable Shared FIFO Exposes the Vault Password to Same-User Race Attacks## 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**: Medium ### Vulnerable 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
- ## Remediation Suggestions 1. 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. 2. 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 to `0700`. 3. Generate an unpredictable FIFO name inside that private directory and pass the exact path only to the required child processes. 4. 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. 5. Do not use a shared fallback pathname. Make failure to create a private per-run location fatal. 6. Remove the FIFO and private directory in all normal, error, timeout, and signal-handling paths. 7. Add concurrency tests that start multiple helpers simultaneously and verify that credentials cannot cross instance boundaries. 8. Minimize secret exposure in process environments as an additional defense. Avoid supplying the master password through `RBW_REMOTE_UNLOCK_PASSWORD` when direct pipe-based transfer is feasible.
