import type { ToolResult } from "../types.js"; import type { ToolRunOptions } from "./tool-types.js"; /** * Shared sudo authentication for every privileged tool path (nmap stealth * scans, elevated shell commands, privileged background jobs). * * Why this exists: tool calls in one assistant turn run concurrently, and * every UI (classic + OpenTUI) routes the secure password prompt through a * single blocking overlay. Without coordination, two parallel `net.scan` * calls both ask for the sudo password at the same moment — the first * request opens the modal, the second is refused by the busy overlay and * reports "sudo cancelled", silently downgrading that scan to an * unprivileged TCP connect scan. * * Two mechanisms fix it: * 1. Coalescing — while one password prompt is open, every other caller * awaits the same in-flight request instead of prompting again. One * password entry authorizes the whole parallel batch. * 2. A short-lived in-memory cache (mirroring sudo's own 5-minute * timestamp_timeout) — privileged commands that start just after a * successful authentication reuse the password instead of re-prompting. * * The password is only ever piped to `sudo -S` over child stdin. It is never * written to disk, artifacts, or logs, and it is dropped when the TTL * expires or {@link evictSudoSession} reports it stopped working. */ /** Mirrors sudo's default timestamp_timeout (5 minutes). */ export declare const SUDO_SESSION_TTL_MS: number; export declare function formatSudoStdinPassword(password: string): string; export type SudoAuthOutcome = { readonly status: "granted"; readonly password: string; /** True when the password came from the in-memory cache (no prompt shown). */ readonly fromCache: boolean; } | { readonly status: "cancelled"; } | { readonly status: "failed"; readonly detail: string; }; export interface SudoAuthOptions { readonly requestSecret: (request: { title: string; prompt: string; }) => Promise; readonly title: string; readonly prompt: string; readonly signal?: AbortSignal | undefined; readonly onOutput?: ToolRunOptions["onOutput"]; } export interface SudoAuthDependencies { /** Test seam for the `sudo -v` validation spawn. */ readonly runAuth?: ((args: { command: string; argv: string[]; stdinText?: string | undefined; timeoutMs?: number | undefined; signal?: AbortSignal | undefined; onOutput?: ToolRunOptions["onOutput"]; noArtifact?: boolean | undefined; interactiveStdin?: boolean | "auto" | undefined; }) => Promise) | undefined; readonly now?: (() => number) | undefined; readonly ttlMs?: number | undefined; } export declare function obtainSudoPassword(options: SudoAuthOptions, dependencies?: SudoAuthDependencies): Promise; /** * Did a privileged child fail because the sudo password itself was rejected? * Deliberately narrower than generic privilege-detection: nmap's own * "requires root privileges" does not mean the password is wrong, only * sudo's authentication failures do. */ export declare function looksLikeSudoAuthError(output: string): boolean; /** * Drop the cached password. Callers invoke this when a `sudo -S` run using * the cached password was rejected (e.g. the password changed mid-session), * so the next privileged operation prompts again instead of replaying a * stale secret. When `expected` is given, the cache is only cleared if it * still holds that exact password. */ export declare function evictSudoSession(expected?: string): void; /** Test hook: clear the cached password and forget any in-flight prompt. */ export declare function resetSudoSession(): void;