decolua/9router · error · Error

Sudo password required to install Root CA certificate

Error message

Sudo password required to install Root CA certificate

What it means

During MITM startup the manager auto-installs its generated Root CA into the OS trust store when checkCertInstalled reports it untrusted. Installing a system root CA requires elevated privileges; if no sudo password was supplied (parameter, in-memory cache, or encrypted on-disk store) and isSudoPasswordRequired() says elevation is genuinely needed, startServer throws 'Sudo password required to install Root CA certificate' rather than attempting an install that would fail or hang.

Source

Thrown at src/mitm/manager.js:561

      const password = sudoPassword || getCachedPassword() || await loadEncryptedPassword();
      try { await uninstallCert(password, rootCACertPath); } catch { /* best effort */ }
    }
    log("🔐 Generating Root CA...");
    await generateCert();
  }

  // Step 1.5: Auto-install Root CA if not trusted yet
  const { checkCertInstalled } = require("./cert/install");
  const rootCATrusted = await checkCertInstalled(rootCACertPath);
  const linuxNoSystemTrust = !IS_WIN && !IS_MAC && !isSudoAvailable();
  if (!rootCATrusted) {
    log("🔐 Cert: not trusted → installing...");
    const password = sudoPassword || getCachedPassword() || await loadEncryptedPassword();
    if (linuxNoSystemTrust) {
      log(`🔐 Cert: skipping system trust (no sudo). Install ${rootCACertPath} as a trusted CA on machines that use this proxy.`);
    } else {
      if (!password && isSudoPasswordRequired()) {
        throw new Error("Sudo password required to install Root CA certificate");
      }
      try {
        await installCert(password, rootCACertPath);
        log("🔐 Cert: ✅ trusted");
      } catch (e) {
        throw new Error(`Failed to trust certificate: ${e.message}`);
      }
    }
  } else {
    log("🔐 Cert: already trusted ✅");
  }

  // Step 2: Spawn server (Root CA already installed in Step 1.5)
  // Verify server.js exists — recopy if runtime file was deleted (antivirus/cleanup)
  let effectiveServerPath = SERVER_PATH;
  if (!effectiveServerPath || !fs.existsSync(effectiveServerPath)) {
    log(`[MITM] server.js missing at ${effectiveServerPath} → recopying`);
    effectiveServerPath = ensureRuntimeServer(resolveBundledServerPath());

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Pass the sudo password to the start call: startServer(apiKey, sudoPassword) or the corresponding dashboard/CLI prompt
  2. Save the encrypted sudo password once so loadEncryptedPassword() can retrieve it on later starts
  3. Configure passwordless sudo (NOPASSWD) for the specific cert-install command in /etc/sudoers so isSudoPasswordRequired() returns false
  4. Run 9Router as a user that can elevate without a prompt, or manually trust the CA: install ~/.9router/mitm/rootCA.crt into the system trust store, then restart — the check will pass without sudo

Example fix

// before
await startServer(apiKey);
// after
const sudoPassword = await promptUserForSudoPassword(); // or loadEncryptedPassword()
await startServer(apiKey, sudoPassword);
Defensive patterns

Strategy: validation

Validate before calling

// Check whether elevation will be needed before starting
const { checkCertInstalled } = require('./src/mitm/cert/install');
const certPath = require('os').homedir() + '/.9router/mitm/rootCA.crt';
const needsInstall = !(await checkCertInstalled(certPath));
const sudoPw = loadEncryptedPassword(); // or prompt user
if (needsInstall && !sudoPw) {
  throw new Error('Run setup first: the Root CA is untrusted and no sudo password is saved.');
}
await startServer(apiKey, sudoPw);

Type guard

function hasSudoCredential(p) {
  return typeof p === 'string' && p.length > 0;
}

Try / catch

try {
  await startServer(apiKey, sudoPassword);
} catch (e) {
  if (/Sudo password required/i.test(e.message)) {
    const pw = await promptUserForSudoPassword();
    await startServer(apiKey, pw); // retry once with credentials
  } else throw e;
}

Prevention

When it happens

Trigger: Calling startServer(apiKey) with sudoPassword undefined/empty on a machine where the CA is not yet trusted and passwordless sudo is not configured; the cached password was cleared (process restart, cache invalidated) and loadEncryptedPassword() returns null (never saved, or saved on another machine/user); first-ever MITM run after a fresh install or cert regeneration.

Common situations: Headless Linux server where sudo always prompts; running the dashboard as a non-root user while the CA was generated under root; an expired CA was regenerated so trust must be re-established; macOS/Windows keychain/UAC prompts requiring credentials the app doesn't have; user skipped saving an encrypted sudo password at setup.

Understand the failure class

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/1e143487b8077a19. Report an issue: GitHub.