{"record":{"id":"daf253afa5c730a0","repo":"decolua/9router","slug":"invalid-sudo-password","errorCode":null,"errorMessage":"Invalid sudo password","messagePattern":"Invalid sudo password","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/tunnel/tailscale/tailscale.js","lineNumber":372,"sourceCode":"      try { execSync(`rm -f ${pkgPath}`, { stdio: \"ignore\", windowsHide: true }); } catch { /* ignore */ }\n      if (c === 0) resolve();\n      else {\n        const msg = (stderr.includes(\"incorrect password\") || stderr.includes(\"Sorry\"))\n          ? \"Wrong sudo password\"\n          : stderr || `Exit code ${c}`;\n        reject(new Error(msg));\n      }\n    });\n    child.on(\"error\", reject);\n    child.stdin.write(`${sudoPassword}\\n`);\n    child.stdin.end();\n  });\n}\n\nasync function installTailscaleLinux(sudoPassword, log) {\n  // Reject password containing newline → prevents stdin command injection\n  if (typeof sudoPassword !== \"string\" || sudoPassword.includes(\"\\n\")) {\n    throw new Error(\"Invalid sudo password\");\n  }\n  log(\"Downloading install script...\");\n  return new Promise((resolve, reject) => {\n    const curlChild = spawn(\"curl\", [\"-fsSL\", \"https://tailscale.com/install.sh\"], {\n      stdio: [\"ignore\", \"pipe\", \"pipe\"],\n      windowsHide: true\n    });\n    let scriptContent = \"\";\n    let curlErr = \"\";\n    curlChild.stdout.on(\"data\", (d) => { scriptContent += d.toString(); });\n    curlChild.stderr.on(\"data\", (d) => { curlErr += d.toString(); });\n    curlChild.on(\"exit\", (code) => {\n      if (code !== 0) return reject(new Error(`Failed to download install script: ${curlErr}`));\n      log(\"Running install script...\");\n      // Persist script to temp file → exec by path (NOT via stdin) → sh never reads attacker-controlled stdin\n      const tmpScript = path.join(os.tmpdir(), `tailscale-install-${crypto.randomBytes(8).toString(\"hex\")}.sh`);\n      try {\n        fs.writeFileSync(tmpScript, scriptContent, { mode: 0o700 });","sourceCodeStart":354,"sourceCodeEnd":390,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/tunnel/tailscale/tailscale.js#L354-L390","documentation":"installTailscaleLinux() pipes the sudo password to child processes over stdin, so a password containing a newline would terminate the sudo prompt early and inject an attacker-chosen command. As a security measure it validates the password is a string without '\\n' and throws 'Invalid sudo password' otherwise, refusing to install.","triggerScenarios":"Calling installTailscale() on Linux with a sudo password that is not a string (undefined/null/number) or that contains an embedded newline character. This happens when the password was read from a config/multi-line paste or a field containing trailing newline(s) beyond a single terminating one handled by the spawn wiring.","commonSituations":"Passwords pasted from a file/editor that kept a trailing blank line, passwords stored in JSON/YAML where '\\n' was literally embedded, passing an empty/undefined variable as the password, or programmatically supplying credentials without trimming.","solutions":["Re-enter the sudo password ensuring it is a single line with no newline characters (trim trailing whitespace/newlines).","Verify the value passed is actually a string (typeof pw === 'string').","If automation supplies the password, sanitize: password.replace(/\\n/g, '') or pick a newline-free password.","On passwordless-sudo systems, run the install manually (`curl -fsSL https://tailscale.com/install.sh | sh`) to avoid the sudo-password path entirely."],"exampleFix":"// before\nawait installTailscale(passwordFromFile); // may contain \"\\n\"\n// after\nconst pw = String(passwordFromFile ?? \"\").replace(/[\\r\\n]+$/, \"\");\nif (!pw || pw.includes(\"\\n\")) throw new Error(\"sudo password must be a single line\");\nawait installTailscale(pw);","handlingStrategy":"validation","validationCode":"function assertSudoPassword(pw) {\n  if (typeof pw !== \"string\" || pw.length === 0 || pw.includes(\"\\n\")) {\n    throw new Error(\"sudo password must be a non-empty single-line string\");\n  }\n}\nassertSudoPassword(password);","typeGuard":"const isValidSudoPassword = (pw) => typeof pw === \"string\" && pw.length > 0 && !pw.includes(\"\\n\");","tryCatchPattern":"try {\n  await installTailscale(pw);\n} catch (e) {\n  if (e.message === \"Invalid sudo password\") {\n    console.error(\"Password must be a single line without newlines — re-prompt user.\");\n    return repromptForPassword();\n  }\n  throw e;\n}","preventionTips":["Trim trailing newlines when reading passwords from files/configs.","Never store passwords in multi-line formats; validate at input time.","Prompt interactively instead of piping from files where possible.","Prefer passwordless sudo or run the install script manually in automation."],"tags":["validation","security","tailscale","installation","linux","sudo"],"backgroundTag":"invalid-sudo-password","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}