paratix 0.10.0 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/dryRunRecipe.ts","../src/runnerHelpers.ts","../src/runner.ts"],"sourcesContent":["import { Command } from \"commander\"\nimport { realpathSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport { inspect } from \"node:util\"\nimport pc from \"picocolors\"\n\nimport type { Environment, ServerDefinition } from \"./types.js\"\n\nimport { printCliHeader } from \"./output.js\"\nimport { runPlaybook } from \"./runner.js\"\nimport { collectSshConfigErrors } from \"./serverDefinitionValidation.js\"\n\ndeclare const PACKAGE_DISPLAY_VERSION: string\n\nconst SECONDS_TO_MS = 1000\nconst DEFAULT_RECONNECT_TIMEOUT_SECONDS = 300\nconst ENVIRONMENT_KEY_PATTERN = /^[A-Za-z_]\\w*$/v\nconst FIRST_RUN_ENV_NAME = \"PARATIX_FIRST_RUN\"\n\nfunction resolveRealPath(path: string): string {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- path is either import.meta-derived or process argv entrypoint; direct file resolution is the intended check\n return realpathSync(path)\n}\n\n/**\n * Type guard that checks whether `value` has the shape of a\n * {@link ServerDefinition} — an object with a non-empty string `name`,\n * a non-empty string `host`, a valid `ssh` config, and a non-empty `run` array.\n *\n * @param value - The value to inspect.\n * @returns `true` when `value` satisfies the structural requirements of `ServerDefinition`.\n */\nexport function isServerDefinitionLike(value: unknown): value is ServerDefinition {\n return collectDefinitionErrors(value).length === 0\n}\n\n/** Descriptor for a property validation check. */\ntype PropertyCheck = {\n /** The key to look up in the object. */\n key: string\n /** The label to use in error messages (defaults to `key`). */\n label?: string\n}\n\n/**\n * Validates that a required string property exists, has the correct type, and\n * is not empty. Pushes a human-readable error into `errors` when any check\n * fails.\n *\n * @param object - The object to inspect.\n * @param check - Property key and optional display label.\n * @param errors - Accumulator for error messages.\n */\nfunction collectStringErrors(\n object: Record<string, unknown>,\n check: PropertyCheck,\n errors: string[]\n): void {\n const name = check.label ?? check.key\n if (!(check.key in object)) {\n errors.push(`Missing property '${name}' (expected string)`)\n } else if (typeof object[check.key] !== \"string\") {\n errors.push(`Invalid property '${name}' (expected string, got ${typeof object[check.key]})`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by typeof check above\n } else if ((object[check.key] as string).length === 0) {\n errors.push(`Property '${name}' must not be empty`)\n }\n}\n\n/**\n * Validates that a required array property exists, has the correct type, and\n * is not empty. Pushes a human-readable error into `errors` when any check\n * fails.\n *\n * @param object - The object to inspect.\n * @param check - Property key and optional display label.\n * @param errors - Accumulator for error messages.\n */\nfunction collectArrayErrors(\n object: Record<string, unknown>,\n check: PropertyCheck,\n errors: string[]\n): void {\n const name = check.label ?? check.key\n if (!(check.key in object)) {\n errors.push(`Missing property '${name}' (expected array)`)\n } else if (!Array.isArray(object[check.key])) {\n errors.push(`Invalid property '${name}' (expected array, got ${typeof object[check.key]})`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by Array.isArray check above\n } else if ((object[check.key] as unknown[]).length === 0) {\n errors.push(`Property '${name}' must not be empty`)\n }\n}\n\nfunction collectSshErrors(value: Record<string, unknown>, errors: string[]): void {\n if (!(\"ssh\" in value)) {\n errors.push(\"Missing property 'ssh' (expected object)\")\n return\n }\n errors.push(...collectSshConfigErrors(value.ssh))\n}\n\n/**\n * Collects human-readable error messages for every property of `value` that\n * does not conform to the `ServerDefinition` shape.\n *\n * @param value - The value to validate.\n * @returns An array of error strings, empty when `value` is structurally valid.\n */\nexport function collectDefinitionErrors(value: unknown): string[] {\n const errors: string[] = []\n if (typeof value !== \"object\" || value === null) {\n errors.push(\"Export is not an object\")\n return errors\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by typeof/null checks above\n const object = value as Record<string, unknown>\n collectStringErrors(object, { key: \"name\" }, errors)\n collectStringErrors(object, { key: \"host\" }, errors)\n collectSshErrors(object, errors)\n collectArrayErrors(object, { key: \"run\" }, errors)\n return errors\n}\n\n/**\n * Assertion function that ensures `value` is a valid {@link ServerDefinition}.\n *\n * When validation fails, all collected errors are printed to stderr and the\n * process exits with code `2`, so callers can treat the function as a\n * narrowing assertion without additional error handling.\n *\n * @param value - The value to validate.\n * @param file - Path of the file that exported `value`, used in the error message.\n */\nfunction validateServerDefinition(value: unknown, file: string): asserts value is ServerDefinition {\n if (isServerDefinitionLike(value)) {\n return\n }\n const errors = collectDefinitionErrors(value)\n const details = errors.map((entry) => ` - ${entry}`).join(\"\\n\")\n console.error(\n `Error: ${file} does not export a valid ServerDefinition.\\n${details}\\n Use the server() helper to create a valid definition.`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n}\n\n/**\n * Returns a human-readable string for any caught value.\n * Uses `.message` for `Error` instances and falls back to the string\n * representation for primitives. For plain objects that have no meaningful\n * `toString`, the JSON representation is used instead of `[object Object]`.\n *\n * @param value - The value to convert to a string.\n * @returns A human-readable string representation of `value`.\n */\nfunction errorToString(value: unknown): string {\n if (value instanceof Error) return value.message\n if (typeof value === \"object\" && value !== null) {\n try {\n return JSON.stringify(value)\n } catch {\n return inspect(value, { breakLength: Infinity, depth: 5 })\n }\n }\n return String(value)\n}\n\n/**\n * Walks the cause chain of `error` and prints each cause to stderr.\n *\n * @param error - The root `Error` whose `.cause` chain should be printed.\n */\nfunction printCauseChain(error: Error): void {\n let cause = error.cause\n while (cause != null) {\n console.error(` Caused by: ${errorToString(cause)}`)\n cause = cause instanceof Error ? cause.cause : undefined\n }\n}\n\n/**\n * Prints a structured error message to stderr, including the cause chain and\n * optionally the full stack trace when `verbose` is `true`.\n *\n * @param error - The caught value (may be any type).\n * @param verbose - When `true`, the stack trace of `error` is printed.\n */\nexport function printExceptionError(error: unknown, verbose: boolean): void {\n console.error(`Error: ${errorToString(error)}`)\n\n if (error instanceof Error) {\n printCauseChain(error)\n\n if (verbose && error.stack != null) {\n console.error(`\\n${error.stack}`)\n }\n }\n}\n\n/**\n * Handles a failed attempt to load the `tsx` runtime.\n *\n * When the failed import is for a TypeScript file (`.ts`, `.mts`, `.cts`), a\n * clear error message is printed to stderr and the process exits with code 2.\n * For JavaScript files the failure is silently ignored because `tsx` is not\n * required there.\n *\n * @param filePath - The resolved path of the playbook file being loaded.\n */\nexport function handleTsxLoadFailure(filePath: string): void {\n if (/\\.[cm]?ts$/v.test(filePath)) {\n console.error(\n `${pc.red(\"Error:\")} tsx is required to run TypeScript playbooks but could not be loaded.\\n` +\n ` Install it with: ${pc.bold(\"npm install -g tsx\")} or add it as a devDependency.`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n}\n\n/**\n * Returns whether the current CLI module is the direct process entrypoint.\n *\n * This resolves symlinks on both sides so pnpm-style executable shims and\n * symlinked `node_modules` entries still count as direct execution.\n *\n * @param moduleUrl - The current module URL, usually `import.meta.url`.\n * @param candidateEntryScript - The process entry script path, usually `process.argv[1]`.\n * @returns `true` when both paths resolve to the same file on disk.\n */\nexport function isDirectCliExecution(moduleUrl: string, candidateEntryScript?: string): boolean {\n if (candidateEntryScript == null) {\n return false\n }\n\n return resolveRealPath(fileURLToPath(moduleUrl)) === resolveRealPath(candidateEntryScript)\n}\n\nexport function applyCliEnvironmentOverrides(\n environment: Environment,\n options: { firstRun: boolean }\n): Environment {\n if (!options.firstRun) return environment\n return { ...environment, [FIRST_RUN_ENV_NAME]: \"true\" }\n}\n\nexport function applyCliProcessEnvironment(options: { firstRun: boolean }): void {\n if (!options.firstRun) return\n process.env[FIRST_RUN_ENV_NAME] = \"true\"\n}\n\nexport async function loadServerDefinitionFromFile(\n file: string,\n options: { firstRun: boolean }\n): Promise<ServerDefinition> {\n const filePath = resolve(file)\n const fileUrl = pathToFileURL(filePath).href\n\n applyCliProcessEnvironment(options)\n\n // Register tsx for TypeScript imports\n await import(\"tsx/esm/api\")\n .then((tsx: { register: () => void }) => {\n tsx.register()\n })\n .catch(() => {\n handleTsxLoadFailure(filePath)\n })\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Dynamic import has unknown shape\n const imported = await import(fileUrl)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-type-assertion -- Accessing .default on dynamic import\n const definition = (imported.default ?? imported) as ServerDefinition\n\n validateServerDefinition(definition, filePath)\n return definition\n}\n\nconst program = new Command()\n\nprogram\n .name(\"paratix\")\n .description(\"Idempotent VPS setup tool in TypeScript\")\n .version(PACKAGE_DISPLAY_VERSION)\n\nprogram\n .command(\"apply <file>\")\n .description(\"Apply a server definition\")\n .option(\n \"--dry-run\",\n \"Only check, do not apply. Some modules validate prospective config but cannot verify runtime restarts.\",\n false\n )\n .option(\"--env <key=value...>\", \"Set env values\", collectEnvironment, {})\n .option(\"--env-file <path>\", \"Load dotenv file\")\n .option(\"--first-run\", \"Set PARATIX_FIRST_RUN=true before loading the playbook\", false)\n .option(\n \"--reconnect-timeout <seconds>\",\n \"SSH reconnect timeout\",\n parsePositiveNumber,\n DEFAULT_RECONNECT_TIMEOUT_SECONDS\n )\n .option(\"--verbose\", \"Show full stack traces on error\", false)\n .action(async (file: string, options: Record<string, unknown>) => {\n try {\n printCliHeader(PACKAGE_DISPLAY_VERSION)\n const environmentOverrides = applyCliEnvironmentOverrides(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n options.env as Environment,\n {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n firstRun: options.firstRun as boolean,\n }\n )\n const definition = await loadServerDefinitionFromFile(file, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n firstRun: options.firstRun as boolean,\n })\n\n await runPlaybook(definition, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n dryRun: options.dryRun as boolean,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n envFile: options.envFile as string | undefined,\n envOverrides: environmentOverrides,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n reconnectTimeout: (options.reconnectTimeout as number) * SECONDS_TO_MS,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n verbose: options.verbose as boolean,\n })\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n printExceptionError(error, options.verbose as boolean)\n // eslint-disable-next-line node/no-process-exit\n process.exit(process.exitCode ?? 2)\n }\n })\n\nexport function parsePositiveNumber(value: string): number {\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed <= 0) {\n console.error(`Invalid --reconnect-timeout value: ${value} (expected a positive number)`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n return parsed\n}\n\nexport function collectEnvironment(\n value: string,\n previous: Record<string, string>\n): Record<string, string> {\n const eqIndex = value.indexOf(\"=\")\n if (eqIndex === -1) {\n console.error(`Invalid --env format: ${value} (expected key=value)`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n const key = value.slice(0, eqIndex)\n const value_ = value.slice(eqIndex + 1)\n if (!ENVIRONMENT_KEY_PATTERN.test(key)) {\n console.error(\n `Invalid --env name: ${key === \"\" ? \"(empty)\" : key} (expected [A-Za-z_][A-Za-z0-9_]*)`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n return { ...previous, [key]: value_ }\n}\n\n// Only parse when executed directly, not when imported (e.g. in tests)\nconst entryScript = process.argv[1]\nif (isDirectCliExecution(import.meta.url, entryScript)) {\n await program.parseAsync()\n}\n","import type { RecipeModule } from \"./recipe.js\"\nimport type { SshConnectionImpl } from \"./ssh.js\"\nimport type { Environment, ModuleStatus } from \"./types.js\"\n\nimport { mergeEnvironmentFromMeta } from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n startModuleSpinner,\n withRecipeOutputScope,\n} from \"./output.js\"\n\ntype StepResult = { env: Environment; shouldBreak: boolean; status?: ModuleStatus; stopRun?: true }\n\nfunction shouldExecuteApplyDuringDryRun(module: RecipeModule[\"_modules\"][number]): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nasync function executeDryRunBlockingModule(parameters: {\n childModule: RecipeModule[\"_modules\"][number]\n connection: null | SshConnectionImpl\n environment: Environment\n verbose: boolean\n}): Promise<StepResult> {\n const { childModule, connection, environment, verbose } = parameters\n startModuleSpinner(childModule.name)\n const result =\n childModule._applyDryRun == null\n ? await childModule.apply(connection, environment)\n : await childModule._applyDryRun(connection, environment)\n const nextEnvironment =\n result.meta == null ? environment : await mergeEnvironmentFromMeta(environment, result.meta)\n printModuleResult(childModule.name, result.status, result._dryRunDetail ?? \"(dry-run)\")\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n return {\n env: nextEnvironment,\n shouldBreak: result.status === \"failed\" || result._stopRun === true,\n status: result.status,\n stopRun: result._stopRun,\n }\n}\n\nasync function executeDryRunChildModule(parameters: {\n childModule: RecipeModule[\"_modules\"][number]\n environment: Environment\n ssh: SshConnectionImpl\n verbose: boolean\n}): Promise<StepResult> {\n const { childModule, environment, ssh, verbose } = parameters\n const connection = childModule.local === true ? null : ssh\n startModuleSpinner(childModule.name)\n const checkResult = await childModule.check(connection, environment)\n if (checkResult !== \"ok\" && shouldExecuteApplyDuringDryRun(childModule)) {\n return executeDryRunBlockingModule({ childModule, connection, environment, verbose })\n }\n const status = checkResult === \"ok\" ? \"ok\" : \"changed\"\n const suffix = checkResult === \"ok\" ? undefined : \"(dry-run)\"\n printModuleResult(childModule.name, status, suffix)\n return { env: environment, shouldBreak: false, status }\n}\n\nexport async function dryRunRecipeModule(parameters: {\n environment: Environment\n options?: {\n verbose?: boolean\n }\n recipeModule: RecipeModule\n ssh: SshConnectionImpl\n}): Promise<StepResult> {\n return withRecipeOutputScope(async () => {\n const { environment, recipeModule, ssh } = parameters\n printRecipeHeader(recipeModule.name)\n let aggregatedStatus: \"changed\" | \"ok\" = \"ok\"\n let currentEnvironment = environment\n const verbose = parameters.options?.verbose ?? false\n\n for (const childModule of recipeModule._modules) {\n // eslint-disable-next-line no-await-in-loop\n const result = await executeDryRunChildModule({\n childModule,\n environment: currentEnvironment,\n ssh,\n verbose,\n })\n if (result.shouldBreak) return result\n currentEnvironment = result.env\n if (result.status === \"changed\") aggregatedStatus = \"changed\"\n }\n\n return { env: currentEnvironment, shouldBreak: false, status: aggregatedStatus }\n })\n}\n","const SIGNAL_EXIT_BASE = 128\nconst SIGTERM_NUMBER = 15\nconst SIGINT_NUMBER = 2\n\n/**\n * Returns the conventional exit code for a termination signal.\n * Follows the POSIX convention of 128 + signal number.\n *\n * @param signal - The received signal (`SIGTERM` or `SIGINT`).\n * @returns The exit code to use when the process is terminated by `signal`.\n */\nexport function signalExitCode(signal: NodeJS.Signals): number {\n return SIGNAL_EXIT_BASE + (signal === \"SIGTERM\" ? SIGTERM_NUMBER : SIGINT_NUMBER)\n}\n\n/**\n * Sets `process.exitCode` based on the run outcome.\n * A received shutdown signal takes precedence over module failures.\n *\n * @param shutdownSignal - The signal that interrupted the run, or `null` if the\n * run completed normally.\n * @param stats - Accumulated run statistics used to detect module failures.\n * @param stats.failed - Number of modules that failed.\n */\nexport function resolveExitCode(\n shutdownSignal: NodeJS.Signals | null,\n stats: { failed: number }\n): void {\n if (shutdownSignal != null) {\n process.exitCode = signalExitCode(shutdownSignal)\n } else if (stats.failed > 0) {\n process.exitCode = 1\n }\n}\n","/* eslint-disable max-lines -- central runner orchestration stays intentionally co-located */\nimport type { RecipeModule } from \"./recipe.js\"\nimport type {\n Environment,\n Module,\n ModuleResult,\n ModuleStatus,\n OrchestrationStep,\n ServerDefinition,\n} from \"./types.js\"\n\nimport { dryRunRecipeModule } from \"./dryRunRecipe.js\"\nimport { loadDotEnvironment, mergeEnvironment } from \"./environment.js\"\nimport {\n assertValidModuleMetaEntries,\n isSshdPortMetaEntry,\n isSystemHostMetaEntry,\n isSystemRebootMetaEntry,\n mergeEnvironmentFromMeta,\n} from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n printRunContext,\n printSummary,\n startModuleSpinner,\n} from \"./output.js\"\nimport { resolveExitCode, signalExitCode } from \"./runnerHelpers.js\"\nimport { runSignalModules, type SignalRunStatus } from \"./signalOrchestration.js\"\nimport { SshConnectionImpl } from \"./ssh.js\"\n\n/** Holds the shutdown listener, SSH setter, and getter for the first received signal. */\ntype ShutdownState = {\n handleShutdownSignal: (signal: NodeJS.Signals) => void\n promptAbortSignal: AbortSignal\n setSsh: (connection: SshConnectionImpl) => void\n shutdownSignal: () => NodeJS.Signals | null\n}\n\n/**\n * Registers shutdown handlers.\n * @returns The listener state and first-signal getter.\n */\nfunction setupShutdownHandlers(): ShutdownState {\n let receivedSignal: NodeJS.Signals | null = null\n let ssh: null | SshConnectionImpl = null\n const promptAbortController = new AbortController()\n\n const handleShutdownSignal = (signal: NodeJS.Signals): void => {\n if (receivedSignal != null) {\n // eslint-disable-next-line node/no-process-exit\n process.exit(signalExitCode(signal))\n }\n receivedSignal = signal\n promptAbortController.abort(new Error(`Terminal prompt interrupted by ${signal}`))\n console.error(`\\nReceived ${signal}, shutting down…`)\n ssh?.disconnect()\n }\n\n process.on(\"SIGINT\", handleShutdownSignal)\n process.on(\"SIGTERM\", handleShutdownSignal)\n\n return {\n handleShutdownSignal,\n promptAbortSignal: promptAbortController.signal,\n setSsh: (connection: SshConnectionImpl) => {\n ssh = connection\n },\n shutdownSignal: () => receivedSignal,\n }\n}\n\nexport type RunOptions = {\n /** When `true`, modules report what would change without applying anything. Defaults to `false`. */\n dryRun?: boolean\n /** Path to a `.env` file whose variables are merged into the run environment. */\n envFile?: string\n /** Additional environment variables that override values from `envFile` and the server definition. */\n envOverrides?: Environment\n /** Custom reconnect timeout in milliseconds passed to SSH, overriding the config default. */\n reconnectTimeout?: number\n /** When `true`, failed commands print full stdout/stderr in addition to the summary error. */\n verbose?: boolean\n}\n\nclass RunStats {\n public changed = 0\n public failed = 0\n public ok = 0\n public signals = 0\n public skipped = 0\n\n public incrementSignals(): void {\n this.signals++\n }\n\n public update(status: ModuleStatus): void {\n switch (status) {\n case \"changed\": {\n this.changed++\n break\n }\n case \"failed\": {\n this.failed++\n break\n }\n case \"ok\": {\n this.ok++\n break\n }\n case \"skipped\": {\n this.skipped++\n break\n }\n }\n }\n}\n\ntype StepResult = {\n env: Environment\n flushSignals?: true\n shouldBreak: boolean\n status?: ModuleStatus\n stopRun?: true\n}\n\nfunction interruptedStepResult(environment: Environment): StepResult {\n return { env: environment, shouldBreak: true }\n}\n\nfunction shouldBreakAfterResult(result: Pick<ModuleResult, \"_stopRun\" | \"status\">): boolean {\n return result.status === \"failed\" || result._stopRun === true\n}\n\nfunction interruptedBeforeApply(\n environment: Environment,\n shutdownSignal: () => NodeJS.Signals | null\n): StepResult | undefined {\n if (shutdownSignal() == null) return undefined\n return interruptedStepResult(environment)\n}\n\nasync function applyCheckedModule(parameters: {\n currentEnvironment: Environment\n dryRun?: boolean\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}): Promise<StepResult> {\n const interrupted = interruptedBeforeApply(\n parameters.currentEnvironment,\n parameters.shutdownSignal\n )\n if (interrupted != null) return interrupted\n\n return applyModule({\n currentEnvironment: parameters.currentEnvironment,\n dryRun: parameters.dryRun,\n ssh: parameters.ssh,\n targetModule: parameters.targetModule,\n verbose: parameters.verbose,\n })\n}\n\nfunction shouldExecuteApplyDuringDryRun(module: Module): boolean {\n return (\n module._applyDryRun != null ||\n module._dryRunBlocker === true ||\n module._dryRunMetaProducer === true\n )\n}\n\nfunction handleCaughtStepError(parameters: {\n environment: Environment\n error: unknown\n moduleName: string\n shutdownSignal: () => NodeJS.Signals | null\n verbose: boolean\n}): StepResult {\n if (parameters.shutdownSignal() != null) {\n return interruptedStepResult(parameters.environment)\n }\n printModuleResult(parameters.moduleName, \"failed\")\n printCommandFailure(parameters.error, parameters.verbose)\n return { env: parameters.environment, shouldBreak: true, status: \"failed\" }\n}\n\nfunction isRecipe(target: Module): target is RecipeModule {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- RecipeModule uses _isRecipe as discriminator\n return \"_isRecipe\" in target && (target as RecipeModule)._isRecipe\n}\n\nasync function initializeEnvironment(\n options: RunOptions,\n definition: ServerDefinition\n): Promise<Environment> {\n const dotEnvironment =\n options.envFile == null ? undefined : await loadDotEnvironment(options.envFile)\n return mergeEnvironment({}, dotEnvironment, definition.env, options.envOverrides)\n}\n\nasync function handlePortChange(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): Promise<void> {\n const portEntry = metaEntries?.find((entry) => isSshdPortMetaEntry(entry))\n if (portEntry == null) return\n\n const newPort = portEntry.port\n ssh.addPort(newPort)\n\n // Skip reconnect when a reboot is pending — the reboot handler will\n // reconnect on all registered ports (including the newly added one).\n if (metaEntries?.some((entry) => isSystemRebootMetaEntry(entry)) ?? false) return\n\n try {\n await ssh.reconnect()\n } catch (error) {\n console.error(\n `Failed to reconnect on port ${newPort} after port change: ${String(error)}. ` +\n `Verify that port ${newPort} is allowed by the server's firewall rules.`\n )\n throw error\n }\n}\n\nasync function handleReboot(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): Promise<void> {\n if (!(metaEntries?.some((entry) => isSystemRebootMetaEntry(entry)) ?? false)) return\n\n const hostEntry = metaEntries?.find((entry) => isSystemHostMetaEntry(entry))\n if (hostEntry != null) {\n ssh.updateHost(hostEntry.host)\n }\n\n try {\n await ssh.reconnect()\n } catch (error) {\n console.error(`Failed to reconnect after reboot: ${String(error)}`)\n throw error\n }\n}\n\nasync function applyRunnerControlPlaneMeta(\n ssh: SshConnectionImpl,\n step: Pick<OrchestrationStep, \"meta\">\n): Promise<void> {\n if (step.meta == null) return\n assertValidModuleMetaEntries(step.meta)\n await handlePortChange(ssh, step.meta)\n await handleReboot(ssh, step.meta)\n}\n\nasync function handleMetaAndBuildResult(\n ssh: SshConnectionImpl,\n environment: Environment,\n result: ModuleResult\n): Promise<StepResult> {\n let currentEnvironment = environment\n\n if (result.meta != null) {\n currentEnvironment = await mergeEnvironmentFromMeta(currentEnvironment, result.meta)\n await applyRunnerControlPlaneMeta(ssh, { meta: result.meta })\n }\n\n return {\n env: currentEnvironment,\n flushSignals: result._flushSignals,\n shouldBreak: shouldBreakAfterResult(result),\n status: result.status,\n stopRun: result._stopRun,\n }\n}\n\n// eslint-disable-next-line max-params -- verbose and dryRun flags need to be threaded through\nasync function runDryRunRecipeModule(\n recipeModule: RecipeModule,\n environment: Environment,\n ssh: SshConnectionImpl,\n verbose: boolean\n): Promise<StepResult> {\n return dryRunRecipeModule({\n environment,\n options: { verbose },\n recipeModule,\n ssh,\n })\n}\n\n// eslint-disable-next-line max-params -- verbose and dryRun flags need to be threaded through\nasync function runRecipeModule(\n recipeModule: RecipeModule,\n environment: Environment,\n ssh: SshConnectionImpl,\n stats: RunStats,\n verbose: boolean,\n dryRun: boolean,\n shutdownSignal: () => NodeJS.Signals | null\n): Promise<StepResult> {\n try {\n if (dryRun) return await runDryRunRecipeModule(recipeModule, environment, ssh, verbose)\n\n // check() iterates all child modules; apply() checks them again internally via executeModules().\n startModuleSpinner(recipeModule.name)\n const checkResult = await recipeModule.check(ssh, environment)\n if (checkResult === \"ok\") {\n printModuleResult(recipeModule.name, \"ok\")\n return { env: environment, shouldBreak: false, status: \"ok\" }\n }\n\n const result = await recipeModule.apply(ssh, environment, {\n onChildStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n onSignalStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n shutdownSignal,\n signalHooks: {\n onSignalFinished: (status: ModuleStatus) => {\n stats.update(status)\n },\n onSignalStarted: () => {\n stats.incrementSignals()\n },\n },\n verbose,\n })\n return await handleMetaAndBuildResult(ssh, environment, result)\n } catch (error) {\n return handleCaughtStepError({\n environment,\n error,\n moduleName: recipeModule.name,\n shutdownSignal,\n verbose,\n })\n }\n}\n\nasync function applyModule(parameters: {\n currentEnvironment: Environment\n dryRun?: boolean\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}): Promise<StepResult> {\n const { currentEnvironment, dryRun = false, ssh, targetModule, verbose } = parameters\n const connection = targetModule.local === true ? null : ssh\n const result =\n dryRun && targetModule._applyDryRun != null\n ? await targetModule._applyDryRun(connection, currentEnvironment)\n : await targetModule.apply(connection, currentEnvironment)\n const stepResult = await handleMetaAndBuildResult(ssh, currentEnvironment, result)\n const detail = dryRun ? (result._dryRunDetail ?? \"(dry-run)\") : result.detail\n printModuleResult(targetModule.name, result.status, detail)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n return stepResult\n}\n\nasync function checkRegularModule(parameters: {\n env: Environment\n ssh: SshConnectionImpl\n targetModule: Module\n}): Promise<\"needs-apply\" | \"ok\"> {\n const { env, ssh, targetModule } = parameters\n const connection = targetModule.local === true ? null : ssh\n startModuleSpinner(targetModule.name)\n return targetModule.check(connection, env)\n}\n\nfunction buildDryRunChangedResult(environment: Environment): StepResult {\n return { env: environment, shouldBreak: false, status: \"changed\" }\n}\n\ntype RegularModuleArguments = {\n dryRun: boolean\n env: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}\n\nasync function runRegularModule(parameters: RegularModuleArguments): Promise<StepResult> {\n const { dryRun, env, ssh, targetModule, verbose } = parameters\n const shutdownSignal = parameters.shutdownSignal\n\n try {\n const checkResult = await checkRegularModule({ env, ssh, targetModule })\n\n if (checkResult === \"ok\") {\n printModuleResult(targetModule.name, \"ok\")\n return { env, shouldBreak: false, status: \"ok\" }\n }\n\n if (dryRun) {\n if (shouldExecuteApplyDuringDryRun(targetModule)) {\n return await applyCheckedModule({\n currentEnvironment: env,\n dryRun: true,\n shutdownSignal,\n ssh,\n targetModule,\n verbose,\n })\n }\n printModuleResult(targetModule.name, \"changed\", \"(dry-run)\")\n return buildDryRunChangedResult(env)\n }\n\n return await applyCheckedModule({\n currentEnvironment: env,\n dryRun: false,\n shutdownSignal,\n ssh,\n targetModule,\n verbose,\n })\n } catch (error) {\n return handleCaughtStepError({\n environment: env,\n error,\n moduleName: targetModule.name,\n shutdownSignal,\n verbose,\n })\n }\n}\n\ntype LoopArguments = {\n definitionSignals?: Module[]\n dryRun: boolean\n env: Environment\n modules: Module[]\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\ntype ModuleLoopState = {\n currentEnvironment: Environment\n signalsPending: boolean\n stopRun?: true\n}\n\nfunction updateLoopSignalState(input: {\n currentSignalsPending: boolean\n result: StepResult\n stats: RunStats\n}): boolean {\n if (input.result.status == null) return input.currentSignalsPending\n input.stats.update(input.result.status)\n return input.result.status === \"changed\" ? true : input.currentSignalsPending\n}\n\nfunction shouldFlushTopLevelSignals(input: {\n definitionSignals?: Module[]\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalsPending: boolean\n stats: RunStats\n stepResult: StepResult\n}): input is {\n definitionSignals: Module[]\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalsPending: boolean\n stats: RunStats\n stepResult: { flushSignals: true } & StepResult\n} {\n return (\n input.stepResult.flushSignals === true &&\n !input.dryRun &&\n input.shutdownSignal() == null &&\n input.signalsPending &&\n input.stats.failed === 0 &&\n input.definitionSignals != null\n )\n}\n\nasync function flushPendingTopLevelSignals(input: {\n currentEnvironment: Environment\n definitionSignals: Module[]\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<SignalRunStatus> {\n return runSignals({\n env: input.currentEnvironment,\n shutdownSignal: input.shutdownSignal,\n signals: input.definitionSignals,\n ssh: input.ssh,\n stats: input.stats,\n verbose: input.verbose,\n })\n}\n\nfunction applyLoopResultToState(\n state: ModuleLoopState,\n result: StepResult,\n stats: RunStats\n): ModuleLoopState {\n return {\n currentEnvironment: result.env,\n signalsPending: updateLoopSignalState({\n currentSignalsPending: state.signalsPending,\n result,\n stats,\n }),\n stopRun: result.stopRun === true ? true : state.stopRun,\n }\n}\n\nasync function flushTopLevelSignalsIfRequested(parameters: {\n definitionSignals?: Module[]\n dryRun: boolean\n loopState: ModuleLoopState\n result: StepResult\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<{ nextSignalsPending: boolean; outcome: \"break\" | \"continue\" }> {\n if (\n !shouldFlushTopLevelSignals({\n definitionSignals: parameters.definitionSignals,\n dryRun: parameters.dryRun,\n shutdownSignal: parameters.shutdownSignal,\n signalsPending: parameters.loopState.signalsPending,\n stats: parameters.stats,\n stepResult: parameters.result,\n })\n ) {\n return { nextSignalsPending: parameters.loopState.signalsPending, outcome: \"continue\" }\n }\n const definitionSignals = parameters.definitionSignals\n if (definitionSignals == null) {\n return { nextSignalsPending: parameters.loopState.signalsPending, outcome: \"continue\" }\n }\n\n const signalStatus = await flushPendingTopLevelSignals({\n currentEnvironment: parameters.loopState.currentEnvironment,\n definitionSignals,\n shutdownSignal: parameters.shutdownSignal,\n ssh: parameters.ssh,\n stats: parameters.stats,\n verbose: parameters.verbose,\n })\n return {\n nextSignalsPending: false,\n outcome: signalStatus === \"failed\" ? \"break\" : \"continue\",\n }\n}\n\nasync function createModuleStepPromise(parameters: {\n currentEnvironment: Environment\n currentModule: Module\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<StepResult> {\n const { currentEnvironment, currentModule, dryRun, shutdownSignal, ssh, stats, verbose } =\n parameters\n\n return isRecipe(currentModule)\n ? runRecipeModule(\n currentModule,\n currentEnvironment,\n ssh,\n stats,\n verbose,\n dryRun,\n shutdownSignal\n )\n : runRegularModule({\n dryRun,\n env: currentEnvironment,\n shutdownSignal,\n ssh,\n targetModule: currentModule,\n verbose,\n })\n}\n\nasync function runModuleLoop(parameters: LoopArguments): Promise<{\n env: Environment\n signalsPending: boolean\n stopRun?: true\n}> {\n const { definitionSignals, dryRun, modules, shutdownSignal, ssh, stats, verbose } = parameters\n const loopState: ModuleLoopState = {\n currentEnvironment: parameters.env,\n signalsPending: false,\n stopRun: undefined,\n }\n\n for (const currentModule of modules) {\n // A module already running when the signal arrived completes normally\n // and its result is still counted in stats before the loop exits here.\n if (shutdownSignal() != null) break\n const stepPromise = createModuleStepPromise({\n currentEnvironment: loopState.currentEnvironment,\n currentModule,\n dryRun,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n\n // eslint-disable-next-line no-await-in-loop\n const result = await stepPromise\n\n Object.assign(loopState, applyLoopResultToState(loopState, result, stats))\n // eslint-disable-next-line no-await-in-loop\n const flushResult = await flushTopLevelSignalsIfRequested({\n definitionSignals,\n dryRun,\n loopState,\n result,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n loopState.signalsPending = flushResult.nextSignalsPending\n if (flushResult.outcome === \"break\") break\n if (result.shouldBreak) break\n }\n\n return {\n env: loopState.currentEnvironment,\n signalsPending: loopState.signalsPending,\n stopRun: loopState.stopRun,\n }\n}\n\ntype SignalArguments = {\n env: Environment\n shutdownSignal: () => NodeJS.Signals | null\n signals: Module[]\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\nasync function runSignals(parameters: SignalArguments): Promise<SignalRunStatus> {\n const { env, shutdownSignal, signals, ssh, stats, verbose } = parameters\n return runSignalModules({\n environment: env,\n hooks: {\n onSignalFinished: (status: ModuleStatus) => {\n stats.update(status)\n },\n onSignalStarted: () => {\n stats.incrementSignals()\n },\n },\n onSignalStep: async (step) => {\n await applyRunnerControlPlaneMeta(ssh, step)\n },\n shutdownSignal,\n signals,\n ssh,\n verbose,\n })\n}\n\nfunction throwIfShutdownRequested(shutdownSignal: () => NodeJS.Signals | null): void {\n const signal = shutdownSignal()\n if (signal == null) return\n throw new Error(`Bootstrap interrupted by ${signal}`)\n}\n\nasync function connectAndRegister(parameters: {\n definition: ServerDefinition\n options: RunOptions\n promptAbortSignal: AbortSignal\n setSsh: (c: SshConnectionImpl) => void\n shutdownSignal: () => NodeJS.Signals | null\n}): Promise<SshConnectionImpl> {\n const { definition, options, promptAbortSignal, setSsh, shutdownSignal } = parameters\n const sshConfig = {\n ...definition.ssh,\n ports: [...definition.ssh.ports],\n ...(options.reconnectTimeout == null ? {} : { reconnectTimeout: options.reconnectTimeout }),\n }\n const ssh = new SshConnectionImpl(definition.host, sshConfig)\n setSsh(ssh)\n throwIfShutdownRequested(shutdownSignal)\n await ssh.connect({ abortSignal: promptAbortSignal })\n throwIfShutdownRequested(shutdownSignal)\n return ssh\n}\n\ntype ExecuteRunArguments = {\n definition: ServerDefinition\n dryRun: boolean\n environment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\nasync function executeRun(parameters: ExecuteRunArguments): Promise<void> {\n const { definition, dryRun, environment, shutdownSignal, ssh, stats, verbose } = parameters\n\n printRecipeHeader(definition.name)\n const loopResult = await runModuleLoop({\n definitionSignals: definition.signals,\n dryRun,\n env: environment,\n modules: definition.run,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n const finalEnvironment = loopResult.env\n\n if (\n !dryRun &&\n shutdownSignal() == null &&\n loopResult.signalsPending &&\n stats.failed === 0 &&\n definition.signals != null\n )\n await runSignals({\n env: finalEnvironment,\n shutdownSignal,\n signals: definition.signals,\n ssh,\n stats,\n verbose,\n })\n\n printSummary(stats)\n}\n\nfunction rethrowIfNotShutdown(error: unknown, shutdownSignal: () => NodeJS.Signals | null): void {\n if (shutdownSignal() == null) throw error\n}\n\nexport async function runPlaybook(\n definition: ServerDefinition,\n options: RunOptions = {}\n): Promise<void> {\n const { dryRun = false, verbose = false } = options\n const environment = await initializeEnvironment(options, definition)\n const { handleShutdownSignal, promptAbortSignal, setSsh, shutdownSignal } =\n setupShutdownHandlers()\n const stats = new RunStats()\n let ssh: SshConnectionImpl | undefined\n\n printRunContext({\n dryRun,\n host: definition.host,\n name: definition.name,\n ports: definition.ssh.ports,\n })\n\n // No catch block: connect errors propagate to cli.ts, which prints them and exits with code 2.\n try {\n ssh = await connectAndRegister({\n definition,\n options,\n promptAbortSignal,\n setSsh,\n shutdownSignal,\n })\n await executeRun({ definition, dryRun, environment, shutdownSignal, ssh, stats, verbose })\n } catch (error) {\n rethrowIfNotShutdown(error, shutdownSignal)\n } finally {\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const)\n process.removeListener(signal, handleShutdownSignal)\n ssh?.disconnect()\n }\n\n resolveExitCode(shutdownSignal(), stats)\n}\n"],"mappings":"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","names":["shouldExecuteApplyDuringDryRun"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/cliTsxHelpers.ts","../src/environment.ts","../src/errorRedaction.ts","../src/firstRunContext.ts","../src/hostValidation.ts","../src/output.ts","../src/outputFormatting.ts","../src/secretSink.ts","../src/sshHelpers.ts","../src/terminalSanitizer.ts","../src/dryRunDispatch.ts","../src/knownHosts.ts","../src/knownHostPatterns.ts","../src/serverDefinitionValidation.ts","../src/meta.ts","../src/dryRunRecipe.ts","../src/runnerAbortSignal.ts","../src/runnerHelpers.ts","../src/server.ts","../src/signalBus.ts","../src/signalOrchestration.ts","../src/ssh.ts","../src/sftp.ts","../src/terminal.ts","../src/runner.ts"],"sourcesContent":["/* eslint-disable max-lines -- CLI entrypoint co-locates parsers, validators, and command wiring */\nimport { Command } from \"commander\"\nimport { AsyncLocalStorage } from \"node:async_hooks\"\nimport { realpathSync } from \"node:fs\"\nimport { extname, resolve } from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport pc from \"picocolors\"\n\nimport type { Environment, ServerDefinition } from \"./types.js\"\n\nimport { isMissingTsxDependencyError } from \"./cliTsxHelpers.js\"\nimport { createNullPrototypeEnvironment, ENVIRONMENT_FORBIDDEN_KEYS } from \"./environment.js\"\nimport { inspectRedactedDiagnosticValue } from \"./errorRedaction.js\"\nimport { runWithFirstRunFlag, runWithoutFirstRunFlag } from \"./firstRunContext.js\"\nimport { describeHostValidationFailure, validateHostLabel } from \"./hostValidation.js\"\nimport { printCliHeader } from \"./output.js\"\nimport { type RunOptions, runPlaybook } from \"./runner.js\"\nimport { maskRegisteredSecrets } from \"./secretSink.js\"\nimport { collectSshConfigErrors } from \"./serverDefinitionValidation.js\"\n\ndeclare const PACKAGE_DISPLAY_VERSION: string\n\nconst SECONDS_TO_MS = 1000\nconst ENVIRONMENT_KEY_PATTERN = /^[A-Za-z_]\\w*$/v\nconst FIRST_RUN_ENV_NAME = \"PARATIX_FIRST_RUN\"\nconst TYPESCRIPT_ENTRY_EXTENSIONS = new Set([\".cts\", \".mts\", \".ts\"])\n\n/**\n * Upper bound for second-based CLI timeouts (24h).\n *\n * Avoids overflow when the parsed value is later multiplied by\n * `SECONDS_TO_MS` and added to `Date.now()` for deadline checks.\n */\nconst RECONNECT_TIMEOUT_MAX_SECONDS = 86_400\n\nfunction resolveRealPath(path: string): null | string {\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- path is either import.meta-derived or process argv entrypoint; direct file resolution is the intended check\n return realpathSync(path)\n } catch {\n // Symlink loops, EACCES, ENOENT, or unusual pnpm shim layouts can throw\n // before any try/catch in apply() can intercept the failure. Returning\n // null lets callers fall back safely (e.g. treat as \"not direct CLI\n // execution\") instead of producing an uncaught Node-internal error.\n return null\n }\n}\n\n/**\n * Type guard that checks whether `value` has the shape of a\n * {@link ServerDefinition} — an object with a non-empty string `name`,\n * a non-empty string `host`, a valid `ssh` config, and a non-empty `run` array.\n *\n * @param value - The value to inspect.\n * @returns `true` when `value` satisfies the structural requirements of `ServerDefinition`.\n */\nexport function isServerDefinitionLike(value: unknown): value is ServerDefinition {\n return collectDefinitionErrors(value).length === 0\n}\n\n/** Descriptor for a property validation check. */\ntype PropertyCheck = {\n /** The key to look up in the object. */\n key: string\n /** The label to use in error messages (defaults to `key`). */\n label?: string\n}\n\n/**\n * Validates that a required string property exists, has the correct type, and\n * is not empty. Pushes a human-readable error into `errors` when any check\n * fails.\n *\n * @param object - The object to inspect.\n * @param check - Property key and optional display label.\n * @param errors - Accumulator for error messages.\n */\nfunction collectStringErrors(\n object: Record<string, unknown>,\n check: PropertyCheck,\n errors: string[]\n): void {\n const name = check.label ?? check.key\n if (!(check.key in object)) {\n errors.push(`Missing property '${name}' (expected string)`)\n } else if (typeof object[check.key] !== \"string\") {\n errors.push(`Invalid property '${name}' (expected string, got ${typeof object[check.key]})`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by typeof check above\n } else if ((object[check.key] as string).length === 0) {\n errors.push(`Property '${name}' must not be empty`)\n }\n}\n\n/**\n * Validates that a required array property exists, has the correct type, and\n * is not empty. Pushes a human-readable error into `errors` when any check\n * fails.\n *\n * @param object - The object to inspect.\n * @param check - Property key and optional display label.\n * @param errors - Accumulator for error messages.\n */\nfunction collectArrayErrors(\n object: Record<string, unknown>,\n check: PropertyCheck,\n errors: string[]\n): void {\n const name = check.label ?? check.key\n if (!(check.key in object)) {\n errors.push(`Missing property '${name}' (expected array)`)\n } else if (!Array.isArray(object[check.key])) {\n errors.push(`Invalid property '${name}' (expected array, got ${typeof object[check.key]})`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by Array.isArray check above\n } else if ((object[check.key] as unknown[]).length === 0) {\n errors.push(`Property '${name}' must not be empty`)\n }\n}\n\nfunction collectSshErrors(value: Record<string, unknown>, errors: string[]): void {\n if (!(\"ssh\" in value)) {\n errors.push(\"Missing property 'ssh' (expected object)\")\n return\n }\n errors.push(...collectSshConfigErrors(value.ssh))\n}\n\nfunction collectHostErrors(object: Record<string, unknown>, errors: string[]): void {\n const previousErrorCount = errors.length\n collectStringErrors(object, { key: \"host\" }, errors)\n if (errors.length !== previousErrorCount) return\n\n const failure = validateHostLabel(object.host)\n if (failure != null) {\n errors.push(`Invalid property 'host' (${describeHostValidationFailure(failure)})`)\n }\n}\n\n/**\n * Collects human-readable error messages for every property of `value` that\n * does not conform to the `ServerDefinition` shape.\n *\n * @param value - The value to validate.\n * @returns An array of error strings, empty when `value` is structurally valid.\n */\nexport function collectDefinitionErrors(value: unknown): string[] {\n const errors: string[] = []\n if (typeof value !== \"object\" || value === null) {\n errors.push(\"Export is not an object\")\n return errors\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed by typeof/null checks above\n const object = value as Record<string, unknown>\n collectStringErrors(object, { key: \"name\" }, errors)\n collectHostErrors(object, errors)\n collectSshErrors(object, errors)\n collectArrayErrors(object, { key: \"run\" }, errors)\n return errors\n}\n\n/**\n * Assertion function that ensures `value` is a valid {@link ServerDefinition}.\n *\n * When validation fails, all collected errors are printed to stderr and the\n * process exits with code `2`, so callers can treat the function as a\n * narrowing assertion without additional error handling.\n *\n * @param value - The value to validate.\n * @param file - Path of the file that exported `value`, used in the error message.\n */\nfunction validateServerDefinition(value: unknown, file: string): asserts value is ServerDefinition {\n if (isServerDefinitionLike(value)) {\n return\n }\n const errors = collectDefinitionErrors(value)\n const details = errors.map((entry) => ` - ${entry}`).join(\"\\n\")\n console.error(\n `Error: ${file} does not export a valid ServerDefinition.\\n${details}\\n Use the server() helper to create a valid definition.`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n}\n\n/**\n * Inspect bounds used by {@link errorToString} when rendering plain\n * (non-Error) caught values. The bounds keep error rendering cheap and\n * prevent leaking sensitive Buffer contents (private keys) or huge\n * ssh2-internal arrays into stderr; subsequent maskRegisteredSecrets passes\n * also stay linear in the bounded output size.\n *\n * R-0000262: previously a JSON.stringify branch ran first and only fell back\n * to `inspect` on a thrown error. JSON.stringify(Buffer) produces\n * `{\"type\":\"Buffer\",\"data\":[…]}` — the entire byte array. If a caught error\n * carried a private-key Buffer in its context, those bytes would land in\n * stderr unbounded. Routing every plain object through `inspect` with these\n * bounds removes that path; `compact: true` keeps the rendering similar to\n * the previous JSON output for small, well-behaved objects.\n */\nconst ERROR_INSPECT_DEPTH = 2\nconst ERROR_INSPECT_MAX_ARRAY_LENGTH = 32\nconst ERROR_INSPECT_MAX_STRING_LENGTH = 1024\n\n/**\n * R-0000691: maximum recursion depth applied to the pre-inspect redaction\n * walk. Stays one level beyond `ERROR_INSPECT_DEPTH` so a Buffer that\n * `inspect` would still render is still elided. A bounded depth is\n * mandatory: an attacker-controlled error graph could otherwise hang the\n * walk on a cyclic reference.\n */\nconst REDACT_BUFFER_MAX_DEPTH = ERROR_INSPECT_DEPTH + 1\n\n/**\n * Returns a human-readable string for any caught value.\n * Uses `.message` for `Error` instances and falls back to the string\n * representation for primitives. For plain objects, `util.inspect` with\n * bounded array/string lengths is used instead of `[object Object]` (and\n * instead of `JSON.stringify`, which would unfold full Buffer byte arrays\n * — see R-0000262).\n *\n * @param value - The value to convert to a string.\n * @returns A human-readable string representation of `value`.\n */\nfunction errorToString(value: unknown): string {\n if (value instanceof Error) return maskRegisteredSecrets(value.message)\n if (typeof value === \"object\" && value !== null) {\n // R-0000691: pre-redact every nested Buffer to a static placeholder\n // before `inspect` runs. `util.inspect` would otherwise render the\n // Buffer bytes (subject to `maxArrayLength`) and a sensitive\n // payload — private key, session secret, password ciphertext — could\n // leak into stderr through any caught value with a Buffer-shaped\n // cause / data field. The pre-pass clones the graph defensively so\n // the original error object stays untouched for downstream consumers.\n return maskRegisteredSecrets(\n inspectRedactedDiagnosticValue(value, {\n breakLength: Infinity,\n compact: true,\n depth: ERROR_INSPECT_DEPTH,\n maxArrayLength: ERROR_INSPECT_MAX_ARRAY_LENGTH,\n maxStringLength: ERROR_INSPECT_MAX_STRING_LENGTH,\n redactMaxDepth: REDACT_BUFFER_MAX_DEPTH,\n })\n )\n }\n return maskRegisteredSecrets(String(value))\n}\n\n/**\n * Walks the cause chain of `error` and prints each cause to stderr.\n *\n * Uses a WeakSet to detect cyclic `cause` references (e.g. produced by\n * third-party libraries that wrap the same Error twice) so the loop always\n * terminates instead of hanging the CLI before `process.exit` is reached.\n *\n * @param error - The root `Error` whose `.cause` chain should be printed.\n */\n/**\n * R-0000795: follow `.cause` on both Error instances and plain wrapper\n * objects (e.g. `{ message, cause: realError }`) so a non-Error wrapper\n * inserted in the middle of the chain does not silently truncate the walk.\n *\n * @param cause - The current node of the cause chain.\n * @returns The next cause value, or `undefined` when the chain ends.\n */\nfunction nextCauseValue(cause: unknown): unknown {\n if (cause instanceof Error) return cause.cause\n if (typeof cause === \"object\" && cause !== null && \"cause\" in cause) {\n return (cause as { cause?: unknown }).cause\n }\n return undefined\n}\n\nfunction printCauseChain(error: Error): void {\n // R-0000795: track every object-typed cause — both `Error` instances and\n // plain objects — in the same WeakSet so a chain that mixes them cannot\n // cycle past the cycle-detection guard. A plain `{ cause }` wrapper that\n // points back to an Error already visited (or to another plain object that\n // ultimately points back) would previously have been re-printed each pass\n // because only Error references were tracked.\n const visitedCauses = new WeakSet<object>()\n visitedCauses.add(error)\n let cause: unknown = error.cause\n while (cause != null) {\n if (typeof cause === \"object\") {\n if (visitedCauses.has(cause)) {\n console.error(\" Caused by: <cycle detected>\")\n return\n }\n visitedCauses.add(cause)\n }\n console.error(` Caused by: ${errorToString(cause)}`)\n cause = nextCauseValue(cause)\n }\n}\n\n/**\n * Prints a structured error message to stderr, including the cause chain and\n * optionally the full stack trace when `verbose` is `true`.\n *\n * @param error - The caught value (may be any type).\n * @param verbose - When `true`, the stack trace of `error` is printed.\n */\nexport function printExceptionError(error: unknown, verbose: boolean): void {\n console.error(`Error: ${errorToString(error)}`)\n\n if (error instanceof Error) {\n printCauseChain(error)\n\n if (verbose && error.stack != null) {\n console.error(`\\n${maskRegisteredSecrets(error.stack)}`)\n }\n }\n}\n\n/**\n * Handles a failed attempt to load the `tsx` runtime.\n *\n * When the failed import is for a TypeScript file (`.ts`, `.mts`, `.cts`), a\n * clear error message is printed to stderr and the process exits with code 2.\n * For JavaScript files the failure is silently ignored because `tsx` is not\n * required there.\n *\n * @param filePath - The resolved path of the playbook file being loaded.\n */\nexport function handleTsxLoadFailure(filePath: string): void {\n if (/\\.[cm]?ts$/v.test(filePath)) {\n console.error(\n `${pc.red(\"Error:\")} tsx is required to run TypeScript playbooks but could not be loaded.\\n` +\n ` Install it with: ${pc.bold(\"npm install -g tsx\")} or add it as a devDependency.`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n}\n\n/**\n * Returns whether the current CLI module is the direct process entrypoint.\n *\n * This resolves symlinks on both sides so pnpm-style executable shims and\n * symlinked `node_modules` entries still count as direct execution.\n *\n * @param moduleUrl - The current module URL, usually `import.meta.url`.\n * @param candidateEntryScript - The process entry script path, usually `process.argv[1]`.\n * @returns `true` when both paths resolve to the same file on disk.\n */\nexport function isDirectCliExecution(moduleUrl: string, candidateEntryScript?: string): boolean {\n if (candidateEntryScript == null) {\n return false\n }\n\n const moduleRealPath = resolveRealPath(fileURLToPath(moduleUrl))\n const entryRealPath = resolveRealPath(candidateEntryScript)\n if (moduleRealPath == null || entryRealPath == null) {\n return false\n }\n\n return moduleRealPath === entryRealPath\n}\n\n/**\n * Returns a copy of `environment` augmented with the runtime override flags\n * derived from CLI options.\n *\n * This function is pure: it does not mutate `environment`, the global\n * `process.env`, or any shared state. The augmented Environment is consumed\n * by the runner and threaded into module execution; runner code never reads\n * {@link FIRST_RUN_ENV_NAME} from `process.env` directly.\n *\n * The companion {@link withCliProcessEnvironment} mutates the global\n * `process.env` only for the duration of the serialized playbook\n * `import()` so playbooks can read `process.env.PARATIX_FIRST_RUN` at module\n * scope without contaminating another concurrent playbook import.\n *\n * @param environment - The base environment supplied by the caller.\n * @param options - Overrides derived from CLI flags.\n * @param options.firstRun - When `true`, sets `PARATIX_FIRST_RUN=true` in the\n * returned copy.\n * @returns A new Environment with the overrides applied.\n */\nexport function applyCliEnvironmentOverrides(\n environment: Environment,\n options: { firstRun: boolean }\n): Environment {\n if (!options.firstRun) return environment\n return Object.assign(createNullPrototypeEnvironment(), environment, {\n [FIRST_RUN_ENV_NAME]: \"true\",\n })\n}\n\n/**\n * R-0000695 / R-0000729: the first-run AsyncLocalStorage now lives in\n * `firstRunContext.ts` so the library entry `index.ts` can re-export\n * `isFirstRun` without dragging `cli.ts` (with its `import.meta.url`\n * direct-run guard) into the library bundle. The runtime semantics are\n * unchanged: `isFirstRun()` reads the scoped flag, and\n * {@link withCliProcessEnvironment} below installs it via\n * {@link runWithFirstRunFlag}.\n */\nexport { isFirstRun } from \"./firstRunContext.js\"\n\n/**\n * Runs `body` while the CLI-derived first-run flag is observable through\n * `isFirstRun` and guarantees the flag is cleared before returning,\n * regardless of whether `body` resolves or rejects.\n *\n * R-0000265: this helper replaces the previous `applyCliProcessEnvironment`\n * which returned a manual restore callback. That API trusted every caller\n * to wire up its own try/finally; a missed restore in any code path left\n * the flag stuck on the process for the rest of its lifetime. Wrapping the\n * body internally removes the discipline burden.\n *\n * R-0000695: the flag no longer touches `process.env`. The runner already\n * consumes the value through the typed Environment returned by\n * {@link applyCliEnvironmentOverrides}, so business logic stays free of\n * implicit globals. Playbooks that previously read\n * `process.env.PARATIX_FIRST_RUN` at module scope should call\n * `isFirstRun` inside their async surface area\n * (`init`/`apply`/`check`) instead — the flag is async-local, not global.\n *\n * Reentrant CLI calls are supported: a nested invocation that sets\n * `firstRun: true` extends the inner async context but does not leak the\n * value into the surrounding caller. After every nested call returns, the\n * outer context's flag remains visible until its own `body` completes.\n *\n * @param options - CLI flags that determine which mutations to apply.\n * @param options.firstRun - When `true`, marks the current async context\n * as a first-run invocation for the duration of `body`.\n * @param body - Async work to run while the flag is installed. Its\n * resolved value is forwarded; rejections propagate normally.\n * @returns The value resolved by `body`.\n */\nexport async function withCliProcessEnvironment<T>(\n options: { firstRun: boolean },\n body: () => Promise<T>\n): Promise<T> {\n if (!options.firstRun) {\n // R-0000796: a nested invocation that explicitly disables firstRun must\n // observe `false` even when the outer scope set the flag to `true`.\n // Without a dedicated clear-scope the nested body would inherit the\n // outer AsyncLocalStorage value and silently see `true`, contradicting\n // the option the operator just passed. `runWithoutFirstRunFlag` opens\n // a fresh `firstRunContext.run(false, body)` so `isFirstRun()` returns\n // `false` for the entire async surface of `body` and reverts to the\n // outer state when the scope exits.\n return runWithoutFirstRunFlag(body)\n }\n return runWithFirstRunFlag(body)\n}\n\n/**\n * Cached registration promise so repeated calls share the result.\n *\n * The tsx ESM loader registers globally and is safe to install only once:\n * every additional `register()` call adds another loader entry that stays\n * alive for the rest of the process. Repeated calls happen in practice when\n * multiple TypeScript playbooks are imported sequentially (CLI batch),\n * inside vitest worker pools, or from embedded runners. Memoizing the\n * promise makes the helper idempotent and concurrency-safe so callers can\n * invoke it freely without leaking loader registrations or racing on a\n * boolean flag.\n *\n * On failure the cached promise is dropped so a later invocation can retry\n * (e.g. after the operator installs tsx).\n */\nlet tsxRegistrationPromise: null | Promise<void> = null\n/**\n * R-0000846: per-fileUrl serialization queues. The previous design used a\n * single process-global queue, which meant two independent playbooks A and\n * B serialized against each other unnecessarily — even though A and B\n * cannot interfere with each other's module-record. By keying the queue on\n * the resolved `fileUrl`, parallel imports of distinct playbooks proceed\n * concurrently while repeated imports of the same `fileUrl` (e.g. tests\n * loading the same playbook back-to-back) still observe the prior import's\n * completion.\n *\n * Entries are removed when their settling task is the queue head, so the\n * map does not grow without bound in long-running processes.\n */\nconst playbookImportLocks = new Map<string, Promise<void>>()\nconst playbookImportContext = new AsyncLocalStorage<boolean>()\n\n/**\n * R-0000787: serialize playbook imports across the process so a TypeScript\n * playbook's top-level `await import(\"./other-playbook.ts\")` cannot deadlock\n * against an outer `withSerializedPlaybookImport` that is still holding the\n * lock. The AsyncLocalStorage-based reentrancy check below intentionally\n * uses a boolean flag — every nested import inside the same async context\n * bypasses the queue.\n *\n * WARNING for future maintainers: this reentrancy bypass is safe ONLY for\n * playbook-to-playbook imports. The tsx ESM loader registration\n * (`tsx.register()` in `registerTsxLoader`) and any other host-side\n * machinery that runs underneath `withSerializedPlaybookImport` MUST NOT\n * trigger a nested call into `withSerializedPlaybookImport`. If the loader\n * ever needs to load a playbook itself, the boolean flag would cause that\n * nested import to skip the queue and race with the outer lock, defeating\n * the serialization contract that downstream callers rely on. Keep the tsx\n * registration body free of `withSerializedPlaybookImport` calls or convert\n * this guard into a fileUrl-keyed map of in-flight imports instead.\n *\n * @param body - The async unit of work whose playbook import must be\n * serialized against every other playbook import in the process.\n * @param fileUrl - Optional resolved `pathToFileURL(...).href` of the playbook\n * being imported. Supplied by production callers so the lock is scoped per\n * playbook (R-0000846); omitted by ad-hoc invocations and tests, in which\n * case the legacy single-queue behaviour is used.\n * @returns Whatever `body` resolves to.\n */\nexport async function withSerializedPlaybookImport<T>(\n body: () => Promise<T>,\n fileUrl?: string\n): Promise<T> {\n if (playbookImportContext.getStore() === true) {\n return body()\n }\n\n // R-0000846: callers without a known fileUrl fall back to a sentinel key\n // so the legacy single-queue behaviour stays available for tests and any\n // ad-hoc invocation that cannot supply a stable identifier. Production\n // callers should always pass a resolved `pathToFileURL(...).href`.\n const lockKey = fileUrl ?? \"__paratix_default_playbook_lock__\"\n const previousImport = playbookImportLocks.get(lockKey) ?? Promise.resolve()\n let releaseCurrentImport!: () => void\n const currentImport = new Promise<void>((resolveQueue) => {\n releaseCurrentImport = resolveQueue\n })\n playbookImportLocks.set(lockKey, currentImport)\n\n // R-0000746: swallow rejections from the previous queue head. A\n // playbook import that fails (tsx registration error, dynamic import\n // syntax error, runtime throw at module scope) currently produces a\n // rejected promise here; without the catch a single failed import\n // would freeze the lock for every subsequent caller because the\n // `await previousImport` below would re-throw and skip the\n // `releaseCurrentImport()` in `finally`. Analogous to the\n // catch-and-ignore pattern in knownHosts.ts:31-38.\n try {\n await previousImport\n } catch {\n // The previous import already surfaced its failure to its own\n // caller. The lock only cares that the prior holder is settled, so\n // its outcome is intentionally discarded here.\n }\n\n try {\n return await playbookImportContext.run(true, body)\n } finally {\n releaseCurrentImport()\n // R-0000846: prune the map entry when we are still the head of the\n // queue. If another caller has already enqueued behind us (the map\n // value differs from `currentImport`), they own the cleanup once they\n // settle. This keeps the map bounded in long-running processes.\n if (playbookImportLocks.get(lockKey) === currentImport) {\n playbookImportLocks.delete(lockKey)\n }\n }\n}\n\n/**\n * Resets the cached tsx registration promise. Exported so tests can\n * exercise the registration path in isolation; production code never needs\n * to clear the cache.\n */\nexport function resetTsxRegistrationForTests(): void {\n tsxRegistrationPromise = null\n}\n\nasync function performTsxRegistration(filePath: string): Promise<void> {\n try {\n const tsx = (await import(\"tsx/esm/api\")) as { register: () => void }\n tsx.register()\n } catch (error) {\n if (isMissingTsxDependencyError(error)) {\n handleTsxLoadFailure(filePath)\n return\n }\n throw new Error(\n `Failed to load tsx/esm/api: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n }\n )\n }\n}\n\n/**\n * R-0000692: tracks how many concurrent awaiters are still observing the\n * cached registration promise. The promise can only be cleared after every\n * waiter has settled — otherwise a parallel call would see `null` while\n * the in-flight `.catch` handler is still propagating a rejection, race\n * a fresh `performTsxRegistration`, and end up with the second registration\n * out of sync with the first one's failure semantics.\n */\nlet tsxRegistrationWaiterCount = 0\n\n/**\n * R-0000840: marks that the cached registration promise should be discarded\n * as soon as the last waiter releases. Replaces the prior microtask\n * busy-loop that re-queued `Promise.resolve().then(clearWhenQuiet)` until\n * `tsxRegistrationWaiterCount` reached zero — under heavy contention that\n * loop could spin the microtask queue indefinitely. With this sentinel the\n * waiter `finally` block performs the clear deterministically as part of\n * its own settlement.\n */\nlet tsxRegistrationClearPending = false\n\nfunction clearTsxRegistrationIfQuiet(): void {\n if (!tsxRegistrationClearPending) return\n if (tsxRegistrationWaiterCount !== 0) return\n tsxRegistrationClearPending = false\n tsxRegistrationPromise = null\n}\n\nasync function registerTsxForTypeScriptEntry(filePath: string): Promise<void> {\n // R-0000692: when a cached promise exists, every parallel waiter must\n // observe the same (success or rejection) terminal value before we drop\n // the cache. Increment the waiter counter so a rejection cannot race a\n // fresh registration while a sibling caller is still in `await`. The\n // counter is decremented in the `finally` below regardless of the\n // outcome.\n if (tsxRegistrationPromise != null) {\n tsxRegistrationWaiterCount += 1\n try {\n await tsxRegistrationPromise\n } finally {\n tsxRegistrationWaiterCount -= 1\n // R-0000840: the last sibling awaiter performs the deferred cache\n // clear as part of its own `finally`, so the microtask loop the\n // originating catch used to spin is no longer required.\n clearTsxRegistrationIfQuiet()\n }\n return\n }\n // Wrap the registration so a rejection also clears the cache — but only\n // once every concurrent awaiter has observed the rejection. Holding the\n // catch promise in the cache until the waiter count drops to zero means\n // every parallel `await tsxRegistrationPromise` resolves against the\n // same terminal state. After the cache clears, a later call can retry\n // (e.g. once the operator installs tsx) by entering this branch and\n // creating a fresh registration.\n const registration = performTsxRegistration(filePath).catch((error: unknown) => {\n if (tsxRegistrationWaiterCount === 0) {\n tsxRegistrationPromise = null\n } else {\n // R-0000840: arm the clear sentinel and let the last waiter perform\n // the actual reset from its own `finally` block. No microtask loop\n // is queued — the sentinel is checked once per waiter release.\n tsxRegistrationClearPending = true\n }\n throw error\n })\n tsxRegistrationPromise = registration\n await registration\n}\n\nexport async function loadServerDefinitionFromFile(\n file: string,\n options: { firstRun: boolean }\n): Promise<ServerDefinition> {\n const filePath = resolve(file)\n const fileUrl = pathToFileURL(filePath).href\n const isTypeScriptEntry = TYPESCRIPT_ENTRY_EXTENSIONS.has(extname(filePath).toLowerCase())\n\n return withSerializedPlaybookImport(\n async () =>\n withCliProcessEnvironment(options, async () => {\n // Register tsx for TypeScript imports.\n // R-0000071: narrow the catch so only a genuine missing-tsx error is\n // routed through handleTsxLoadFailure. Any other error from the dynamic\n // import (incompatible Node, broken install, OOM, transitive dep\n // missing) is rethrown with the original cause so the CLI exit handler\n // surfaces the real loader failure instead of falsely reporting that\n // tsx is not installed.\n if (isTypeScriptEntry) await registerTsxForTypeScriptEntry(filePath)\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Dynamic import has unknown shape\n const imported = await import(fileUrl)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-type-assertion -- Accessing .default on dynamic import\n const definition = (imported.default ?? imported) as ServerDefinition\n\n validateServerDefinition(definition, filePath)\n return definition\n }),\n fileUrl\n )\n}\n\ntype ApplyCommandOptions = {\n diff: boolean\n dryRun: boolean\n env: Environment\n envFile?: string\n firstRun: boolean\n reconnectTimeout?: number\n verbose: boolean\n}\n\ntype RunPlaybookFunction = (definition: ServerDefinition, options: RunOptions) => Promise<void>\n\n/**\n * Thrown when the user combined incompatible CLI flags (e.g. `--diff` without\n * `--dry-run`). Carries a fixed exit code so the top-level error handler can\n * surface a clean message without a stack trace.\n */\nexport class CliUsageError extends Error {\n public readonly exitCode: number\n\n public constructor(message: string, exitCode = 2) {\n super(message)\n this.name = \"CliUsageError\"\n this.exitCode = exitCode\n }\n}\n\nexport async function runApplyCommand(\n file: string,\n options: ApplyCommandOptions,\n run: RunPlaybookFunction = runPlaybook\n): Promise<void> {\n if (options.diff && !options.dryRun) {\n // R-0001119: refuse the combination before any side effects (SSH connect,\n // playbook import, secret resolution) so the user gets an instant, clear\n // error instead of an opaque mid-run failure.\n throw new CliUsageError(\"--diff requires --dry-run\")\n }\n printCliHeader(PACKAGE_DISPLAY_VERSION)\n const environmentOverrides = applyCliEnvironmentOverrides(options.env, {\n firstRun: options.firstRun,\n })\n const definition = await loadServerDefinitionFromFile(file, {\n firstRun: options.firstRun,\n })\n\n const runOptions: RunOptions = {\n diff: options.diff,\n dryRun: options.dryRun,\n envFile: options.envFile,\n envOverrides: environmentOverrides,\n verbose: options.verbose,\n }\n if (options.reconnectTimeout !== undefined) {\n runOptions.reconnectTimeout = options.reconnectTimeout * SECONDS_TO_MS\n }\n\n await run(definition, runOptions)\n}\n\nexport function exitAfterApplyError(error: unknown, verbose: boolean): never {\n if (error instanceof CliUsageError) {\n // R-0001119: usage errors are user input mistakes, not runtime failures.\n // Print only the short message (no stack trace, no cause chain) and use\n // the carried exit code so tests/wrappers can distinguish them from the\n // generic exit code 2.\n console.error(`${pc.red(\"Error:\")} ${error.message}`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(error.exitCode)\n }\n printExceptionError(error, verbose)\n const exitCode = process.exitCode === undefined || process.exitCode === 0 ? 2 : process.exitCode\n // eslint-disable-next-line node/no-process-exit\n process.exit(exitCode)\n}\n\nconst program = new Command()\n\nprogram\n .name(\"paratix\")\n .description(\"Idempotent VPS setup tool in TypeScript\")\n .version(PACKAGE_DISPLAY_VERSION)\n\nprogram\n .command(\"apply <file>\")\n .description(\"Apply a server definition\")\n .option(\n \"--diff\",\n \"When combined with --dry-run, show a unified diff per module that would change.\",\n false\n )\n .option(\n \"--dry-run\",\n \"Only check, do not apply. Some modules validate prospective config but cannot verify runtime restarts.\",\n false\n )\n .option(\"--env <key=value...>\", \"Set env values\", collectEnvironment, {})\n .option(\"--env-file <path>\", \"Load dotenv file\")\n .option(\"--first-run\", \"Set PARATIX_FIRST_RUN=true before loading the playbook\", false)\n .option(\n \"--reconnect-timeout <seconds>\",\n \"SSH reconnect timeout (seconds, max 86400)\",\n parseReconnectTimeoutSeconds\n )\n .option(\"--verbose\", \"Show full stack traces on error\", false)\n .action(async (file: string, options: Record<string, unknown>) => {\n try {\n await runApplyCommand(file, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n diff: options.diff as boolean,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n dryRun: options.dryRun as boolean,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n env: options.env as Environment,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n envFile: options.envFile as string | undefined,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n firstRun: options.firstRun as boolean,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n reconnectTimeout: options.reconnectTimeout as number,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n verbose: options.verbose as boolean,\n })\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Commander options typed as Record<string, unknown>\n exitAfterApplyError(error, options.verbose as boolean)\n }\n })\n\nexport function parsePositiveNumber(value: string, options: { max?: number } = {}): number {\n const parsed = Number(value)\n // R-0000839: require a positive integer. Floats like `30.5` would survive\n // the previous `Number.isFinite` check and then silently round when later\n // multiplied to milliseconds, while values like `\"3e9\"` produced numbers\n // far beyond any sane bound. Pin the type so misuse fails fast at CLI\n // parse time with a clear exit code.\n if (!Number.isInteger(parsed) || parsed <= 0) {\n console.error(\n `Invalid --reconnect-timeout value: ${value} (expected a positive integer number of seconds)`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n // Enforce an explicit upper bound to prevent later overflow when the value\n // is converted to milliseconds and added to `Date.now()`.\n if (options.max !== undefined && parsed > options.max) {\n console.error(\n `Invalid --reconnect-timeout value: ${value} (must be at most ${options.max} seconds)`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n return parsed\n}\n\n/**\n * Commander option parser for `--reconnect-timeout` (seconds).\n *\n * Wraps {@link parsePositiveNumber} with a hard upper bound of\n * {@link RECONNECT_TIMEOUT_MAX_SECONDS} so values such as `1e10` are rejected\n * with a clear error message instead of silently producing a deadline that\n * overflows `Number.MAX_SAFE_INTEGER` after the seconds-to-ms multiplication.\n *\n * @param value - The raw CLI string (in seconds) to parse.\n * @returns The validated numeric value.\n */\nexport function parseReconnectTimeoutSeconds(value: string): number {\n return parsePositiveNumber(value, { max: RECONNECT_TIMEOUT_MAX_SECONDS })\n}\n\nexport function collectEnvironment(\n value: string,\n previous: Record<string, string>\n): Record<string, string> {\n const eqIndex = value.indexOf(\"=\")\n if (eqIndex === -1) {\n console.error(`Invalid --env format: ${value} (expected key=value)`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n const key = value.slice(0, eqIndex)\n const value_ = value.slice(eqIndex + 1)\n if (!ENVIRONMENT_KEY_PATTERN.test(key)) {\n console.error(\n `Invalid --env name: ${key === \"\" ? \"(empty)\" : key} (expected [A-Za-z_][A-Za-z0-9_]*)`\n )\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n if (ENVIRONMENT_FORBIDDEN_KEYS.has(key)) {\n console.error(`Forbidden --env name: ${key} (reserved JavaScript identifier)`)\n // eslint-disable-next-line node/no-process-exit\n process.exit(2)\n }\n // R-0000478: use a null-prototype accumulator so the merged environment\n // never inherits keys like `__proto__` or `toString` from Object.prototype.\n // Mirrors the shape produced by `loadDotEnvironment` / `mergeEnvironment`.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- intentional: Object.create(null) is the prototype-pollution defense\n const accumulator = Object.create(null) as Record<string, string>\n return Object.assign(accumulator, previous, { [key]: value_ })\n}\n\n// Only parse when executed directly, not when imported (e.g. in tests)\nconst entryScript = process.argv[1]\nif (isDirectCliExecution(import.meta.url, entryScript)) {\n await program.parseAsync()\n}\n","/**\n * Extract the module specifier from common Node.js module-not-found messages.\n *\n * @param message - Error message produced by Node's module loader.\n * @returns The missing specifier, or null when the message shape is unknown.\n */\nfunction extractMissingModuleSpecifier(message: string): null | string {\n const packageMatch = /Cannot find package ['\"](?<specifier>[^'\"]+)['\"]/v.exec(message)\n if (packageMatch?.groups?.specifier != null) return packageMatch.groups.specifier\n\n const moduleMatch = /Cannot find module ['\"](?<specifier>[^'\"]+)['\"]/v.exec(message)\n if (moduleMatch?.groups?.specifier != null) return moduleMatch.groups.specifier\n\n return null\n}\n\nfunction isTsxLoaderSpecifier(specifier: string): boolean {\n const normalized = specifier.replaceAll(\"\\\\\", \"/\")\n return normalized === \"tsx\" || normalized === \"tsx/esm/api\" || normalized.endsWith(\"/tsx/esm/api\")\n}\n\n/**\n * Detect whether an import failure means the optional tsx loader itself is\n * missing, rather than a transitive dependency or another loader error.\n *\n * @param error - The error caught while importing `tsx/esm/api`.\n * @returns True when the missing specifier is `tsx` or `tsx/esm/api`.\n */\nexport function isMissingTsxDependencyError(error: unknown): boolean {\n if (!(error instanceof Error)) return false\n const code = \"code\" in error ? error.code : undefined\n if (code !== \"ERR_MODULE_NOT_FOUND\" && code !== \"MODULE_NOT_FOUND\") return false\n const specifier = extractMissingModuleSpecifier(error.message)\n return specifier != null && isTsxLoaderSpecifier(specifier)\n}\n","import { readFile, stat } from \"node:fs/promises\"\n\nimport type { Environment } from \"./types.js\"\n\n/**\n * R-0000069: keep this regex in sync with the same pattern used by\n * `collectEnvironment` in cli.ts so values supplied via `--env` and values\n * loaded from a `.env` file go through the same allow-list. Lifting the\n * pattern out of the loader keeps the failure message consistent.\n *\n * R-0000745: exported so `meta.assertAllowedEnvironmentMetaName` can derive\n * its own (dot-aware) variant from the same dotenv allow-list. The pattern\n * itself stays strict here because dotenv keys must not contain dots — the\n * dot-aware extension lives in meta.ts to keep dotenv parsing untouched.\n */\nexport const ENVIRONMENT_KEY_PATTERN = /^[A-Za-z_]\\w*$/v\n\n/**\n * Reserved JavaScript identifiers that, when set as a property, can leak\n * into prototype semantics on a plain object. The loader rejects them\n * explicitly even though the regex above already excludes some of these\n * (e.g. those starting with non-word characters); the explicit list is the\n * defensive failsafe so the behaviour is obvious from the source.\n */\nexport const ENVIRONMENT_FORBIDDEN_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"])\n\n/**\n * R-0000069/R-0000070: produce a null-prototype object typed as\n * {@link Environment}. The wrapper exists because\n * `Object.create(null) as Environment` is reported as an unsafe-`any`\n * assertion by the type-aware lint rule; routing through this helper\n * narrows the cast to a single, documented place.\n *\n * @returns A fresh empty {@link Environment} without a prototype chain.\n */\nexport function createNullPrototypeEnvironment(): Environment {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- intentional: Object.create(null) is the prototype-pollution defense\n return Object.create(null) as Environment\n}\n\n/**\n * Resolve a single env key to its concrete value.\n * Lazy function values are awaited; plain primitive values are returned as-is.\n *\n * @param environment - The env map to look up the key in.\n * @param key - The key to resolve.\n * @returns The resolved primitive value.\n * @throws {Error} When the key is not present in `environment`.\n */\nexport async function resolveEnvironment(\n environment: Environment,\n key: string\n): Promise<boolean | number | string> {\n if (!Object.hasOwn(environment, key)) {\n throw new Error(`Env key \"${key}\" is not defined`)\n }\n const value = environment[key]\n if (typeof value === \"function\") {\n return value()\n }\n return value\n}\n\n/**\n * Parse a `.env` file and return its contents as an {@link Environment} map.\n * Blank lines and lines starting with `#` are ignored.\n * Surrounding single or double quotes are stripped from values.\n * Double-quoted values support escape sequences (`\\\\n`, `\\\\\"`, `\\\\\\\\`).\n * Unquoted values support inline comments (`value # comment`, `value\\t# comment`).\n *\n * @param filePath - Absolute path to the `.env` file.\n * @returns The parsed env map.\n */\n/**\n * R-0000069: enforce the same key allow-list that `collectEnvironment` in\n * cli.ts applies, and explicitly reject reserved JavaScript identifiers\n * that could leak into prototype semantics. Throws an Error that names the\n * file, the 1-based line number and the offending key.\n *\n * @param filePath - Path to the `.env` file (used in the error message).\n * @param lineNumber - 1-based line number (used in the error message).\n * @param key - The candidate key from the parsed line.\n */\nfunction validateDotEnvironmentKey(filePath: string, lineNumber: number, key: string): void {\n if (!ENVIRONMENT_KEY_PATTERN.test(key)) {\n throw new Error(\n `Invalid env key in ${filePath} line ${lineNumber}: ${key === \"\" ? \"(empty)\" : JSON.stringify(key)} (expected [A-Za-z_][A-Za-z0-9_]*)`\n )\n }\n if (ENVIRONMENT_FORBIDDEN_KEYS.has(key)) {\n throw new Error(\n `Forbidden env key in ${filePath} line ${lineNumber}: ${JSON.stringify(key)} (reserved JavaScript identifier)`\n )\n }\n}\n\n/**\n * R-0000843: bound the size of an inbound dotenv file and the size of each\n * decoded value. A misconfigured operator (or a malicious overlay) could\n * otherwise feed a multi-gigabyte file into the loader and exhaust process\n * memory long before the parser reaches its first real assignment.\n *\n * 1 MiB ist großzügig für legitime `.env`-Dateien (Tausende Schlüssel) und\n * gleichzeitig klein genug, damit der Parser frühzeitig fehlschlägt.\n */\nconst KIBIBYTE = 1024\nconst MEBIBYTE = KIBIBYTE * KIBIBYTE\nconst ENVIRONMENT_FILE_BYTE_LIMIT_MIB = 1\nconst ENVIRONMENT_VALUE_BYTE_LIMIT_KIB = 64\nexport const ENVIRONMENT_FILE_BYTE_LIMIT = ENVIRONMENT_FILE_BYTE_LIMIT_MIB * MEBIBYTE\n/** Pro-Wert-Cap (64 KiB) für decodierte Werte vor dem Persistieren in der Map. */\nexport const ENVIRONMENT_VALUE_BYTE_LIMIT = ENVIRONMENT_VALUE_BYTE_LIMIT_KIB * KIBIBYTE\n\n/**\n * R-0000843: control-Bytes (außer Tab, LF, CR) sind in dotenv-Werten nicht\n * vorgesehen. Backspaces, ESC, oder Vertical-Tab überleben den Parser sonst\n * intransparent und können Terminals oder nachgelagerte Shells in\n * unvorhersehbare Zustände bringen. NUL und CR werden bereits in\n * `processValue` abgelehnt; dieses Muster fängt zusätzlich BS, VT, FF und\n * den restlichen C0-Bereich sowie DEL (0x7F) ab. Wird über RegExp()\n * konstruiert, damit die Quelle lesbar bleibt und keine echten\n * Steuerbytes im Source stehen.\n */\n// Reject the entire C0 control range except Tab (0x09), LF (0x0A), CR (0x0D),\n// plus DEL (0x7F). The regex literal uses JavaScript control-character\n// escape sequences so the source stays readable while the produced pattern\n// matches the raw control bytes themselves.\n/* eslint-disable-next-line regexp/no-control-character -- intentional: pattern catches forbidden control bytes */ /* oxlint-disable-next-line no-control-regex */\nconst FORBIDDEN_CONTROL_CHARACTER_PATTERN = /[\\x00-\\x08\\v\\f\\x0E-\\x1F\\x7F]/v\n\n/**\n * R-0000843: enforce the per-value cap and the control-byte allowlist for a\n * single decoded dotenv value. Extracted from {@link loadDotEnvironment} so\n * the loader stays inside the per-function statement budget while keeping\n * the validation behaviour discoverable from a single helper.\n *\n * @param filePath - Path to the dotenv file (used in error messages).\n * @param lineNumber - 1-based line number for the offending value.\n * @param value - The already-decoded value about to be persisted.\n */\nfunction validateDotEnvironmentValue(filePath: string, lineNumber: number, value: string): void {\n if (Buffer.byteLength(value, \"utf8\") > ENVIRONMENT_VALUE_BYTE_LIMIT) {\n throw new Error(\n `Invalid env value in ${filePath} line ${lineNumber}: value exceeds the ${ENVIRONMENT_VALUE_BYTE_LIMIT}-byte cap`\n )\n }\n if (FORBIDDEN_CONTROL_CHARACTER_PATTERN.test(value)) {\n throw new Error(\n `Invalid env value in ${filePath} line ${lineNumber}: contains a forbidden control byte`\n )\n }\n}\n\n/**\n * R-0000843: enforce the file-size cap before the loader walks the content.\n * Extracted so {@link loadDotEnvironment} stays under the max-statements\n * lint budget while keeping the early-fail behaviour traceable.\n *\n * @param filePath - Path to the dotenv file (used in error messages).\n * @param content - The full UTF-8 content read from `filePath`.\n */\nfunction assertDotEnvironmentFileSize(filePath: string, content: string): void {\n const fileByteLength = Buffer.byteLength(content, \"utf8\")\n assertDotEnvironmentFileByteLength(filePath, fileByteLength)\n}\n\nfunction assertDotEnvironmentFileByteLength(filePath: string, fileByteLength: number): void {\n if (fileByteLength > ENVIRONMENT_FILE_BYTE_LIMIT) {\n throw new Error(\n `Refusing to load env file ${filePath}: size ${fileByteLength} bytes exceeds the ${ENVIRONMENT_FILE_BYTE_LIMIT}-byte cap`\n )\n }\n}\n\nasync function readDotEnvironmentContent(filePath: string): Promise<string> {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const stats = await stat(filePath)\n assertDotEnvironmentFileByteLength(filePath, stats.size)\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const content = await readFile(filePath, \"utf8\")\n // R-0000843: enforce the upper bound on the read content before we\n // tokenise. Byte length is approximated via Buffer.byteLength so the\n // limit reflects the on-disk size, not the JS string length.\n assertDotEnvironmentFileSize(filePath, content)\n return content\n}\n\nexport async function loadDotEnvironment(filePath: string): Promise<Environment> {\n const content = await readDotEnvironmentContent(filePath)\n // R-0000069/R-0000070: use a null-prototype object so a malicious\n // `__proto__` line cannot pollute the loaded map even before the\n // explicit reject below catches it. This complements the explicit\n // ENVIRONMENT_FORBIDDEN_KEYS check and the ENVIRONMENT_KEY_PATTERN\n // allow-list.\n const environment: Environment = createNullPrototypeEnvironment()\n\n const lines = content.split(\"\\n\")\n for (const [index, line] of lines.entries()) {\n const trimmed = line.trim()\n if (trimmed === \"\" || trimmed.startsWith(\"#\")) continue\n const eqIndex = trimmed.indexOf(\"=\")\n if (eqIndex === -1) continue\n\n const key = trimmed.slice(0, eqIndex).trim()\n validateDotEnvironmentKey(filePath, index + 1, key)\n const value = processValue(trimmed.slice(eqIndex + 1).trim(), filePath, index + 1)\n // R-0000843: reject decoded values that exceed the per-value cap or\n // carry forbidden control bytes (anything outside Tab/LF/CR in the\n // ASCII control range, plus DEL). Tab/LF/CR may appear after\n // double-quoted escape processing and are intentional, but a literal\n // ESC or vertical-tab byte almost always indicates a copy-paste\n // artefact or a hostile payload trying to influence downstream consumers.\n validateDotEnvironmentValue(filePath, index + 1, value)\n environment[key] = value\n }\n\n return environment\n}\n\nfunction processValue(raw: string, filePath: string, lineNumber: number): string {\n // R-0000747: the double-quoted branch uses NUL (`\\0`) as a temporary\n // sentinel for escaped backslashes (\"\\\\\\\\\" → \"\\0\" → \"\\\\\"). A literal NUL\n // in the raw input would survive the sentinel swap and emerge as a\n // backslash in the decoded value, silently corrupting the loaded\n // environment. Refuse the file outright so the operator notices the\n // unexpected byte instead of debugging a mangled secret hours later.\n // The same byte is rejected for unquoted/single-quoted values because a\n // downstream consumer (shell, sub-process spawn, system call) treats NUL\n // as a string terminator and would silently truncate the value.\n if (raw.includes(\"\\0\")) {\n throw new Error(`Invalid env value in ${filePath} line ${lineNumber}: contains a NUL byte`)\n }\n\n // R-0000791: reject literal CR (0x0D) bytes for the same reason NUL is\n // rejected — a bare \\r emerges as an unprintable byte downstream, hides\n // line-ending mismatches inside dotenv values, and can re-introduce\n // mixed CRLF state into command lines that the SSH command builder\n // already guards against. The double-quoted branch interprets only\n // `\\n`/`\\\\`/`\\\"` escapes, so a literal CR is never the intended way to\n // smuggle a newline into the value.\n if (raw.includes(\"\\r\")) {\n throw new Error(\n `Invalid env value in ${filePath} line ${lineNumber}: contains a carriage return`\n )\n }\n\n if (raw.length >= 2 && raw.startsWith('\"') && raw.endsWith('\"')) {\n // Double-quoted: strip quotes and process escape sequences.\n // Use \\0 as sentinel for escaped backslashes — safe because .env files never contain null bytes.\n return raw\n .slice(1, -1)\n .replaceAll(\"\\\\\\\\\", \"\\0\")\n .replaceAll(\"\\\\n\", \"\\n\")\n .replaceAll('\\\\\"', '\"')\n .replaceAll(\"\\0\", \"\\\\\")\n }\n if (raw.length >= 2 && raw.startsWith(\"'\") && raw.endsWith(\"'\")) {\n // Single-quoted: strip quotes, keep value literal\n return raw.slice(1, -1)\n }\n // Unquoted: strip inline comments after any whitespace before #\n const commentIndex = raw.search(/\\s#/v)\n return commentIndex === -1 ? raw : raw.slice(0, commentIndex).trimEnd()\n}\n\n/**\n * Merge multiple env maps left-to-right into a new object.\n * Later entries overwrite earlier ones for the same key.\n * `undefined` entries are silently skipped.\n *\n * @param environments - One or more env maps to merge.\n * @returns The merged env map.\n */\nexport function mergeEnvironment(...environments: Array<Environment | undefined>): Environment {\n // R-0000070: start from a null-prototype object so reserved property\n // names like `__proto__` and `constructor` cannot inherit prototype\n // semantics on the merged result. Downstream consumers\n // (resolveEnvironment, resolveEnvironmentAsString, template rendering,\n // meta.env) only access the map via bracket notation and Object.entries,\n // both of which work on null-prototype objects.\n const result: Environment = createNullPrototypeEnvironment()\n for (const environment of environments) {\n if (environment != null) {\n Object.assign(result, environment)\n }\n }\n return result\n}\n\n/**\n * Like {@link resolveEnvironment} but always returns a string.\n * Numbers are converted via `String()`.\n *\n * @param environment - The env map to look up the key in.\n * @param key - The key to resolve.\n * @returns The resolved value as a string.\n */\nexport async function resolveEnvironmentAsString(\n environment: Environment,\n key: string\n): Promise<string> {\n const value = await resolveEnvironment(environment, key)\n return String(value)\n}\n","import { inspect, type InspectOptions } from \"node:util\"\n\n/**\n * Token placed in the redacted graph wherever a binary value used to live.\n * Mirrors the masking style used elsewhere in the CLI so operators can grep\n * for \"[REDACTED\" and find every sensitive payload that was elided.\n */\nexport const REDACTED_BINARY_PLACEHOLDER = \"[REDACTED Buffer]\"\nexport const REDACTED_SECRET_FIELD_PLACEHOLDER = \"[REDACTED]\"\n\n/**\n * Keys we must skip even if they appear as own properties so an\n * attacker-controlled error graph cannot mutate the freshly created clone's\n * prototype chain. `Object.create(null)` already removes Object.prototype,\n * but explicit skip is cheap defence-in-depth for the rare case the clone\n * gets re-rooted onto a non-null prototype downstream.\n */\nconst REDACT_FORBIDDEN_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"])\n// Cover common credential-bearing field names that may appear in plain object\n// diagnostics from SDKs, HTTP clients, and module wrappers.\nconst SECRET_FIELD_NAMES = new Set([\n \"apikey\",\n \"auth\",\n \"authorization\",\n \"bearer\",\n \"cookie\",\n \"cred\",\n \"credential\",\n \"credentials\",\n \"jwt\",\n \"key\",\n \"mfa\",\n \"otp\",\n \"pass\",\n \"passphrase\",\n \"passwd\",\n \"password\",\n \"pin\",\n \"privatekey\",\n \"pwd\",\n \"secret\",\n \"sessionkey\",\n \"signature\",\n \"token\",\n])\n\nconst SECRET_FIELD_SUFFIXES = [\n \"token\",\n \"password\",\n \"secret\",\n \"privatekey\",\n \"sessionkey\",\n \"signature\",\n \"jwt\",\n \"pin\",\n \"mfa\",\n \"otp\",\n \"cookie\",\n]\n\nfunction isBufferLikeView(value: unknown): boolean {\n if (Buffer.isBuffer(value)) return true\n if (value instanceof ArrayBuffer) return true\n return ArrayBuffer.isView(value)\n}\n\nfunction isAsciiAlphaNumeric(character: string): boolean {\n return (character >= \"0\" && character <= \"9\") || (character >= \"a\" && character <= \"z\")\n}\n\nexport function isSecretDiagnosticField(key: string): boolean {\n let normalized = \"\"\n for (const character of key.toLowerCase()) {\n if (isAsciiAlphaNumeric(character)) normalized += character\n }\n if (SECRET_FIELD_NAMES.has(normalized)) return true\n return SECRET_FIELD_SUFFIXES.some((suffix) => normalized.endsWith(suffix))\n}\n\nfunction copyRedactedProperty(parameters: {\n depth: number\n destinationKey: string\n maxDepth: number\n redacted: Record<string, unknown>\n seen: WeakSet<object>\n source: Record<string, unknown>\n sourceKey: PropertyKey\n}): void {\n const { depth, destinationKey, maxDepth, redacted, seen, source, sourceKey } = parameters\n const descriptor = Object.getOwnPropertyDescriptor(source, sourceKey)\n if (descriptor == null) return\n if (!(\"value\" in descriptor)) {\n redacted[destinationKey] = \"[Accessor]\"\n return\n }\n const descriptorValue: unknown = descriptor.value\n if (isSecretDiagnosticField(destinationKey) && !isBufferLikeView(descriptorValue)) {\n redacted[destinationKey] = REDACTED_SECRET_FIELD_PLACEHOLDER\n return\n }\n redacted[destinationKey] = redactBinaryValues(descriptorValue, {\n depth: depth + 1,\n maxDepth,\n seen,\n })\n}\n\nfunction redactObjectProperties(parameters: {\n depth: number\n maxDepth: number\n seen: WeakSet<object>\n sourceRecord: Record<string, unknown>\n}): Record<string, unknown> {\n const { depth, maxDepth, seen, sourceRecord } = parameters\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- intentional: Object.create(null) is the prototype-pollution defense\n const redacted: Record<string, unknown> = Object.create(null) as Record<string, unknown>\n for (const key of Object.keys(sourceRecord)) {\n if (REDACT_FORBIDDEN_KEYS.has(key)) continue\n copyRedactedProperty({\n depth,\n destinationKey: key,\n maxDepth,\n redacted,\n seen,\n source: sourceRecord,\n sourceKey: key,\n })\n }\n for (const symbolKey of Object.getOwnPropertySymbols(sourceRecord)) {\n copyRedactedProperty({\n depth,\n destinationKey: `@@symbol:${symbolKey.toString()}`,\n maxDepth,\n redacted,\n seen,\n source: sourceRecord,\n sourceKey: symbolKey,\n })\n }\n return redacted\n}\n\nexport function redactBinaryValues(\n value: unknown,\n options: { depth?: number; maxDepth: number; seen?: WeakSet<object> }\n): unknown {\n const { depth = 0, maxDepth, seen = new WeakSet<object>() } = options\n if (isBufferLikeView(value)) return REDACTED_BINARY_PLACEHOLDER\n if (value === null || typeof value !== \"object\") return value\n if (depth > maxDepth) return value\n if (seen.has(value)) return \"[Circular]\"\n seen.add(value)\n if (Array.isArray(value)) {\n return value.map((entry) => redactBinaryValues(entry, { depth: depth + 1, maxDepth, seen }))\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- the prior object/array guards prove that keys can be enumerated safely\n const sourceRecord = value as Record<string, unknown>\n return redactObjectProperties({ depth, maxDepth, seen, sourceRecord })\n}\n\nexport function inspectRedactedDiagnosticValue(\n value: unknown,\n options: { redactMaxDepth: number } & InspectOptions\n): string {\n const { redactMaxDepth, ...inspectOptions } = options\n const sanitized = redactBinaryValues(value, { maxDepth: redactMaxDepth })\n return inspect(sanitized, inspectOptions)\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\"\n\n/**\n * R-0000695: the first-run flag propagates through an\n * {@link AsyncLocalStorage} context instead of mutating `process.env`.\n *\n * The flag tells a freshly bootstrapped playbook whether the CLI was\n * invoked with `--first-run`. Two real-world hazards motivated lifting it\n * out of `process.env`:\n *\n * - Every other code path running in the same Node process — vitest\n * worker-pool fixtures, embedded runners, unrelated tooling — observed\n * the synthetic value too. Tests had to manually clean up\n * `process.env.PARATIX_FIRST_RUN` to avoid cross-test contamination.\n * - The mutation/restore dance trusted every caller to wire up\n * try/finally semantics correctly. A missed restore in any code path\n * left the flag stuck on the process for the rest of its lifetime.\n *\n * The AsyncLocalStorage-based variant solves both problems: the value is\n * scoped to the async chain that owns the wrapped body and is automatically\n * released when that chain finishes, regardless of how it resolves.\n * Playbooks now read the flag via the {@link isFirstRun} helper (exported\n * as public API) which queries the same async-local store.\n *\n * R-0000729: the helper lives in its own module so the package entry\n * `dist/index.js` can re-export `isFirstRun` without dragging `cli.ts` into\n * the library bundle. `cli.ts` uses `import.meta.url` for its direct-run\n * detection, which interferes with esbuild chunk splitting when the\n * library entries pull the CLI module transitively.\n */\nconst firstRunContext = new AsyncLocalStorage<boolean>()\n\n/**\n * Public API helper that returns the current first-run flag.\n *\n * Returns `true` only when called from inside a\n * `withCliProcessEnvironment` body whose `firstRun` option was `true`.\n * Outside of a CLI invocation, or when the flag was not set, the helper\n * returns `false`. The helper is async-context aware: a playbook that\n * schedules its own microtasks/timers within the CLI body keeps observing\n * the same flag, while concurrent work outside that body sees `false`.\n *\n * @returns `true` when the current async context is a first-run CLI body.\n */\nexport function isFirstRun(): boolean {\n return firstRunContext.getStore() === true\n}\n\n/**\n * Runs `body` while the CLI-derived first-run flag is observable through\n * {@link isFirstRun}. The flag is cleared automatically when the wrapped\n * body settles, regardless of whether `body` resolves or rejects.\n *\n * @param body - Async work to run while the flag is installed. Its\n * resolved value is forwarded; rejections propagate normally.\n * @returns The value resolved by `body`.\n */\nexport async function runWithFirstRunFlag<T>(body: () => Promise<T>): Promise<T> {\n return firstRunContext.run(true, body)\n}\n\n/**\n * R-0000796: open a dedicated clear-scope where {@link isFirstRun} observes\n * `false` for the duration of `body`. Required when a nested CLI invocation\n * sets `firstRun: false` while running inside an outer scope that had set\n * it to `true`: without this helper the nested body would inherit the\n * outer-context flag and silently observe `true` even though the operator\n * explicitly disabled it. Mirrors the success-path of {@link runWithFirstRunFlag}\n * but installs `false` instead of `true`.\n *\n * @param body - Async work to run while the flag is forced to `false`.\n * @returns The value resolved by `body`.\n */\nexport async function runWithoutFirstRunFlag<T>(body: () => Promise<T>): Promise<T> {\n return firstRunContext.run(false, body)\n}\n","const OPENSSH_KNOWN_HOSTS_PATTERN_METACHARACTER = /[*,?!]/v\nconst LAST_ASCII_CONTROL_CODE_POINT = 0x1f\nconst DELETE_CONTROL_CODE_POINT = 0x7f\n\nexport type HostValidationFailure = \"control\" | \"empty\" | \"metacharacter\" | \"type\" | \"whitespace\"\n\nfunction containsControlCharacter(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0)\n if (\n codePoint != null &&\n (codePoint <= LAST_ASCII_CONTROL_CODE_POINT || codePoint === DELETE_CONTROL_CODE_POINT)\n )\n return true\n }\n return false\n}\n\nexport function validateHostLabel(value: unknown): HostValidationFailure | null {\n if (typeof value !== \"string\") return \"type\"\n if (value.length === 0) return \"empty\"\n if (containsControlCharacter(value)) return \"control\"\n if (/\\s/v.test(value)) return \"whitespace\"\n if (OPENSSH_KNOWN_HOSTS_PATTERN_METACHARACTER.test(value)) return \"metacharacter\"\n return null\n}\n\nexport function describeHostValidationFailure(failure: HostValidationFailure): string {\n switch (failure) {\n case \"control\": {\n return \"must not contain control characters\"\n }\n case \"empty\": {\n return \"must be a non-empty string\"\n }\n case \"metacharacter\": {\n return \"must not contain OpenSSH known_hosts pattern metacharacters\"\n }\n case \"type\": {\n return \"must be a string\"\n }\n case \"whitespace\": {\n return \"must not contain whitespace\"\n }\n }\n}\n","/* eslint-disable max-lines -- CLI output rendering is intentionally kept together */\nimport pc from \"picocolors\"\n\nimport type { ModuleStatus } from \"./types.js\"\n\nimport { inspectRedactedDiagnosticValue } from \"./errorRedaction.js\"\nimport { fitAnimatedModuleLine, formatDisplayModule } from \"./outputFormatting.js\"\nimport { maskRegisteredSecrets } from \"./secretSink.js\"\nimport { CommandError } from \"./sshHelpers.js\"\nimport { sanitizeTerminalText } from \"./terminalSanitizer.js\"\n\n// R-0000580: bounds for `util.inspect` when rendering non-Error cause values so\n// a runaway plain object cannot dump unbounded text into stderr.\nconst CAUSE_INSPECT_DEPTH = 2\nconst CAUSE_INSPECT_MAX_STRING_LENGTH = 1024\nconst CAUSE_REDACT_BINARY_MAX_DEPTH = CAUSE_INSPECT_DEPTH + 1\n\nconst MODULE_NAME_WIDTH = 56\nconst MIN_MODULE_NAME_WIDTH = 12\nconst OUTPUT_INDENT_UNIT = \" \"\nconst SPINNER_FRAME_INTERVAL_MS = 80\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"]\ntype DisplayStatus = \"waiting\" | ModuleStatus\n\nconst STATUS_ICONS: Record<DisplayStatus, string> = {\n changed: pc.yellow(\"\\u21ba\"),\n failed: pc.red(\"\\u2717\"),\n ok: pc.green(\"\\u2713\"),\n skipped: pc.dim(\"\\u2298\"),\n waiting: pc.cyan(\"\\u23f8\"),\n}\n\nconst CLI_HEADER_LINES = [\n \" _ _ \",\n \" | | (_) \",\n \" _ __ __ _ _ __ __ _| |_ ___ __\",\n \" | '_ \\\\ / _` | '__/ _` | __| \\\\ \\\\/ /\",\n \" | |_) | (_| | | | (_| | |_| |> < \",\n \" | .__/ \\\\__,_|_| \\\\__,_|\\\\__|_/_/\\\\_\\\\\",\n \" | | \",\n \" |_| \",\n]\n\ntype ActiveSpinner = {\n detail?: string\n frameIndex: number\n interval: NodeJS.Timeout\n}\n\nlet activeSpinner: ActiveSpinner | null = null\nlet activeRecipeGuideDepths: number[] = []\nlet pendingRecipeClosureGuideDepths: number[] = []\nlet recipeOutputDepth = -1\n\nexport function renderCliHeader(version: string): string {\n const versionText = pc.dim(`v${version}`)\n return `${pc.cyan(CLI_HEADER_LINES.join(\"\\n\"))}${versionText}\\n`\n}\n\nexport function printCliHeader(version: string): void {\n console.log(renderCliHeader(version))\n}\n\nfunction supportsAnimatedModuleOutput(): boolean {\n return (\n process.stdout.isTTY &&\n typeof process.stdout.clearLine === \"function\" &&\n typeof process.stdout.cursorTo === \"function\"\n )\n}\n\nfunction getModuleIcon(status: DisplayStatus, waitingFrame?: string): string {\n return status === \"waiting\" ? pc.cyan(waitingFrame ?? \"|\") : STATUS_ICONS[status]\n}\n\nfunction getModuleStatusText(status: DisplayStatus): string {\n switch (status) {\n case \"changed\": {\n return pc.yellow(status)\n }\n case \"failed\": {\n return pc.red(status)\n }\n case \"ok\": {\n return pc.green(status)\n }\n case \"skipped\": {\n return pc.dim(status)\n }\n case \"waiting\": {\n return pc.cyan(\"running\")\n }\n }\n}\n\nfunction getCurrentOutputDepth(): number {\n return Math.max(recipeOutputDepth, 0)\n}\n\nfunction getGuideDot(depth: number): string {\n return depth % 2 === 0 ? pc.gray(\"·\") : pc.cyan(\"·\")\n}\n\nfunction buildGuideIndent(\n baseIndent: string,\n options?: {\n activeGuideDepths?: number[]\n colorize?: boolean\n extraGuideDepths?: number[]\n }\n): string {\n const indentCharacters = Array.from(baseIndent)\n const guideDepths = [\n ...(options?.activeGuideDepths ?? activeRecipeGuideDepths),\n ...(options?.extraGuideDepths ?? []),\n ]\n\n for (const guideDepth of guideDepths) {\n const guideCharacterIndex = OUTPUT_INDENT_UNIT.length * (guideDepth + 1)\n if (guideCharacterIndex >= indentCharacters.length) continue\n indentCharacters[guideCharacterIndex] =\n options?.colorize === false ? \"·\" : getGuideDot(guideDepth)\n }\n\n return indentCharacters.join(\"\")\n}\n\nfunction clearPendingRecipeClosureGuides(): void {\n pendingRecipeClosureGuideDepths = []\n}\n\nfunction getModuleIndent(): string {\n if (recipeOutputDepth < 0) {\n return OUTPUT_INDENT_UNIT\n }\n\n return OUTPUT_INDENT_UNIT.repeat(getCurrentOutputDepth() + 2)\n}\n\nfunction getRecipeHeaderIndent(): string {\n if (recipeOutputDepth < 0) return \"\"\n return OUTPUT_INDENT_UNIT.repeat(getCurrentOutputDepth() + 1)\n}\n\nfunction getErrorIndent(): string {\n return `${getModuleIndent()}│ `\n}\n\nfunction getContinuationIndent(): string {\n return `${getModuleIndent()} `\n}\n\nexport async function withRecipeOutputScope<T>(\n scopedOperation: () => Promise<T> | T\n): Promise<T> {\n recipeOutputDepth += 1\n try {\n return await scopedOperation()\n } finally {\n activeRecipeGuideDepths = activeRecipeGuideDepths.filter((depth) => depth !== recipeOutputDepth)\n pendingRecipeClosureGuideDepths = [recipeOutputDepth]\n recipeOutputDepth -= 1\n }\n}\n\nfunction renderModuleLine(parameters: {\n detail?: string\n extraGuideDepths?: number[]\n name: string\n status: DisplayStatus\n waitingFrame?: string\n}): string {\n const { detail, extraGuideDepths = [], name, status, waitingFrame } = parameters\n const baseIndent = getModuleIndent()\n const indent = buildGuideIndent(baseIndent, { extraGuideDepths })\n const icon = getModuleIcon(status, waitingFrame)\n const statusText = getModuleStatusText(status)\n const detailSuffix = detail == null ? \"\" : ` ${pc.dim(detail)}`\n const alignedNameWidth = Math.max(MODULE_NAME_WIDTH - baseIndent.length, MIN_MODULE_NAME_WIDTH)\n return `${indent}${icon} ${name.padEnd(alignedNameWidth)} ${statusText}${detailSuffix}`\n}\n\nfunction writeAnimatedModuleLine(line: string): void {\n process.stdout.clearLine(0)\n process.stdout.cursorTo(0)\n process.stdout.write(fitAnimatedModuleLine(line, process.stdout.columns))\n}\n\nfunction stopAnimatedModuleLine(clearCurrentLine = false): void {\n if (activeSpinner == null) return\n\n clearInterval(activeSpinner.interval)\n activeSpinner = null\n\n if (clearCurrentLine && supportsAnimatedModuleOutput()) {\n process.stdout.clearLine(0)\n process.stdout.cursorTo(0)\n }\n}\n\nexport function stopLiveModuleOutput(clearCurrentLine = false): void {\n stopAnimatedModuleLine(clearCurrentLine)\n}\n\nexport function startModuleSpinner(name: string, detail?: string): void {\n if (!supportsAnimatedModuleOutput()) return\n\n clearPendingRecipeClosureGuides()\n stopAnimatedModuleLine()\n const maskedName = maskRegisteredSecrets(name)\n const maskedDetail = detail == null ? undefined : maskRegisteredSecrets(detail)\n const displayModule = formatDisplayModule({\n continuationIndentWidth: getContinuationIndent().length,\n detail: maskedDetail,\n name: maskedName,\n status: \"waiting\",\n terminalColumns: process.stdout.columns,\n })\n\n const spinner: ActiveSpinner = {\n detail: displayModule.detail,\n frameIndex: 0,\n interval: setInterval(() => {\n spinner.frameIndex = (spinner.frameIndex + 1) % SPINNER_FRAMES.length\n writeAnimatedModuleLine(\n renderModuleLine({\n detail: spinner.detail,\n name: displayModule.name,\n status: \"waiting\",\n waitingFrame: SPINNER_FRAMES[spinner.frameIndex],\n })\n )\n }, SPINNER_FRAME_INTERVAL_MS),\n }\n // Avoid keeping the event loop alive solely for the spinner timer:\n // if stopAnimatedModuleLine/stopLiveModuleOutput is missed in an\n // unhappy-path (uncaughtException), the process should still be able\n // to exit. Mirrors the pattern used by sleepRespectingShutdown in runner.ts.\n if (typeof spinner.interval.unref === \"function\") spinner.interval.unref()\n\n activeSpinner = spinner\n writeAnimatedModuleLine(\n renderModuleLine({\n detail: displayModule.detail,\n name: displayModule.name,\n status: \"waiting\",\n waitingFrame: SPINNER_FRAMES[0],\n })\n )\n}\n\nexport function resetLiveOutputForTests(): void {\n stopAnimatedModuleLine()\n activeRecipeGuideDepths = []\n clearPendingRecipeClosureGuides()\n recipeOutputDepth = -1\n}\n\n/**\n * Print a bold, colored header line marking the start of a recipe run.\n * @param name - The recipe or server name to display.\n */\nexport function printRecipeHeader(name: string): void {\n stopAnimatedModuleLine(true)\n clearPendingRecipeClosureGuides()\n const header = pc.bold(pc.blue(`[${name}]`))\n console.log(`${buildGuideIndent(getRecipeHeaderIndent())}${header}`)\n if (recipeOutputDepth >= 0) {\n activeRecipeGuideDepths = [...activeRecipeGuideDepths, recipeOutputDepth]\n }\n}\n\nexport function printRunContext(parameters: {\n dryRun: boolean\n host: string\n name: string\n ports: number[]\n}): void {\n const mode = parameters.dryRun ? pc.yellow(\"dry-run\") : pc.green(\"apply\")\n const ports = parameters.ports.join(\", \")\n console.log(\n pc.dim(`Run ${parameters.name} · host ${parameters.host} · ports ${ports} · mode ${mode}`)\n )\n}\n\nfunction colorizeDiffLine(line: string): string {\n if (line.startsWith(\"---\") || line.startsWith(\"+++\") || line.startsWith(\"@@\")) {\n return pc.dim(line)\n }\n if (line.startsWith(\"-\")) return pc.red(line)\n if (line.startsWith(\"+\")) return pc.green(line)\n return pc.dim(line)\n}\n\nfunction renderDiffLines(diff: string): string[] {\n if (diff.trim() === \"\") return []\n const sanitized = sanitizeTerminalText(maskRegisteredSecrets(diff))\n return sanitized.split(\"\\n\").map((line) => `│ ${colorizeDiffLine(line)}`)\n}\n\nfunction writeContinuationLine(\n line: string,\n extraGuideDepths: number[],\n via: \"console\" | \"stdout\"\n): void {\n const composed = `${buildGuideIndent(getContinuationIndent(), { extraGuideDepths })}${line}`\n if (via === \"stdout\") {\n process.stdout.write(`${composed}\\n`)\n } else {\n console.log(composed)\n }\n}\n\nfunction writeContinuationBlock(parameters: {\n detailLines: string[]\n diffLines: string[]\n extraGuideDepths: number[]\n via: \"console\" | \"stdout\"\n}): void {\n for (const detailLine of parameters.detailLines) {\n writeContinuationLine(pc.dim(detailLine), parameters.extraGuideDepths, parameters.via)\n }\n for (const diffLine of parameters.diffLines) {\n writeContinuationLine(diffLine, parameters.extraGuideDepths, parameters.via)\n }\n}\n\nfunction printRenderedModuleResult(parameters: {\n detail?: string\n diff?: string\n extraGuideDepths?: number[]\n name: string\n status: DisplayStatus\n}): void {\n const extraGuideDepths = parameters.extraGuideDepths ?? []\n const maskedName = maskRegisteredSecrets(parameters.name)\n const maskedDetail =\n parameters.detail == null ? undefined : maskRegisteredSecrets(parameters.detail)\n const displayModule = formatDisplayModule({\n continuationIndentWidth: `${buildGuideIndent(\n OUTPUT_INDENT_UNIT.repeat(Math.max(getCurrentOutputDepth() + 2, 1)),\n { extraGuideDepths }\n )} `.length,\n detail: maskedDetail,\n name: maskedName,\n status: parameters.status,\n terminalColumns: process.stdout.columns,\n })\n const line = renderModuleLine({\n detail: displayModule.detail,\n extraGuideDepths,\n name: displayModule.name,\n status: parameters.status,\n })\n const diffLines = parameters.diff == null ? [] : renderDiffLines(parameters.diff)\n const usesSpinner = supportsAnimatedModuleOutput() && activeSpinner != null\n if (usesSpinner) {\n stopAnimatedModuleLine()\n writeAnimatedModuleLine(line)\n process.stdout.write(\"\\n\")\n } else {\n console.log(line)\n }\n writeContinuationBlock({\n detailLines: displayModule.detailLines,\n diffLines,\n extraGuideDepths,\n via: usesSpinner ? \"stdout\" : \"console\",\n })\n}\n\n/**\n * Print a single module result row with a status icon, name, and colored status label.\n *\n * @param name - The module name shown in the left column.\n * @param status - One of the known status strings (`ok`, `changed`, `skipped`, `failed`).\n * @param detail - Optional short detail appended in dim text after the status.\n * @param diff - Optional unified-diff text rendered as a guarded multi-line block\n * below the status line. The block is rendered only when the runner forwards\n * a non-empty diff (i.e. the user passed `--diff` and the module produced one).\n * Every diff line is masked through `maskRegisteredSecrets` and\n * `sanitizeTerminalText` before printing.\n */\n// eslint-disable-next-line max-params -- positional API kept for source compatibility with downstream callers; diff is a leaf-level optional follow-up to detail\nexport function printModuleResult(\n name: string,\n status: DisplayStatus,\n detail?: string,\n diff?: string\n): void {\n clearPendingRecipeClosureGuides()\n printRenderedModuleResult({ detail, diff, name, status })\n}\n\n// eslint-disable-next-line max-params -- mirrors printModuleResult; positional API kept for source compatibility\nexport function printRecipeModuleResult(\n name: string,\n status: DisplayStatus,\n detail?: string,\n diff?: string\n): void {\n printRenderedModuleResult({\n detail,\n diff,\n extraGuideDepths: pendingRecipeClosureGuideDepths,\n name,\n status,\n })\n clearPendingRecipeClosureGuides()\n}\n\n/**\n * Print captured stderr and stdout from a failed command in a red bordered block.\n * Outputs nothing if both streams are empty.\n *\n * @param stdout - Captured standard output of the failed command.\n * @param stderr - Captured standard error of the failed command.\n */\nexport function printCommandError(stdout: string, stderr: string): void {\n // R-0000789: defense-in-depth — apply the process-wide secret sink to the\n // captured stdout/stderr immediately before they reach stderr. Every\n // documented caller already masks via `maskRegisteredSecrets`, but a\n // future caller that forgets the wrapper would otherwise leak verbatim\n // through this terminal write. The double-masking is idempotent.\n const maskedStdout = sanitizeTerminalText(maskRegisteredSecrets(stdout))\n const maskedStderr = sanitizeTerminalText(maskRegisteredSecrets(stderr))\n const lines: string[] = []\n if (maskedStderr.trim()) {\n lines.push(...maskedStderr.trim().split(\"\\n\"))\n }\n if (maskedStdout.trim()) {\n lines.push(...maskedStdout.trim().split(\"\\n\"))\n }\n if (lines.length > 0) {\n console.error(pc.red(`${getErrorIndent()}Error output:`))\n for (const line of lines) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n}\n\n/**\n * Print the full (untruncated) stdout and stderr of a failed command.\n * Used in verbose mode to show the complete output that was truncated in the error message.\n *\n * @param stdout - Full standard output of the failed command.\n * @param stderr - Full standard error of the failed command.\n */\nexport function printVerboseCommandError(stdout: string, stderr: string): void {\n // R-0000789: defense-in-depth — apply the process-wide secret sink before\n // the verbose dump reaches stderr so a caller that forgets to pre-mask the\n // capture buffers still has its output redacted. The double-masking is\n // idempotent for callers that already passed pre-masked strings.\n const maskedStdout = sanitizeTerminalText(maskRegisteredSecrets(stdout))\n const maskedStderr = sanitizeTerminalText(maskRegisteredSecrets(stderr))\n if (maskedStderr.trim()) {\n console.error(pc.red(`${getErrorIndent()}Full stderr:`))\n for (const line of maskedStderr.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n if (maskedStdout.trim()) {\n console.error(pc.red(`${getErrorIndent()}Full stdout:`))\n for (const line of maskedStdout.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n }\n}\n\nfunction printVerboseErrorBlock(label: string, content: string): void {\n if (!content.trim()) {\n return\n }\n\n console.error(pc.red(`${getErrorIndent()}${label}`))\n for (const line of content.trim().split(\"\\n\")) {\n console.error(pc.red(`${getErrorIndent()}${line}`))\n }\n}\n\nfunction getErrorCause(error: Error): unknown {\n return (error as { cause?: unknown } & Error).cause\n}\n\nfunction printVerboseErrorCause(\n cause: unknown,\n depth: number,\n visitedCauses: WeakSet<Error>\n): void {\n const label = `Cause ${depth}:`\n if (cause instanceof Error) {\n if (visitedCauses.has(cause)) {\n printVerboseErrorBlock(label, \"<cycle detected>\")\n return\n }\n visitedCauses.add(cause)\n const stack = cause.stack?.trim() ?? \"\"\n const stackOrMessage = stack.length > 0 ? stack : String(cause)\n printVerboseErrorBlock(label, maskRegisteredSecrets(stackOrMessage))\n const nestedCause = getErrorCause(cause)\n if (nestedCause !== undefined) {\n printVerboseErrorCause(nestedCause, depth + 1, visitedCauses)\n }\n return\n }\n\n printVerboseErrorBlock(label, maskRegisteredSecrets(formatCauseValue(cause)))\n}\n\nfunction printVerboseGenericError(error: Error): void {\n const stack = error.stack?.trim() ?? \"\"\n const stackOrMessage = stack.length > 0 ? stack : String(error)\n // R-0000041: redact any registered secrets that may have flowed into the\n // stack trace through Error wrapping or third-party libraries before it\n // reaches stderr.\n printVerboseErrorBlock(\"Full stack:\", maskRegisteredSecrets(stackOrMessage))\n const cause = getErrorCause(error)\n if (cause !== undefined) {\n // Track every Error seen while walking the cause chain to detect cycles\n // produced by third-party error wrapping (e.g. err.cause === err) so the\n // recursion always terminates.\n const visitedCauses = new WeakSet<Error>()\n visitedCauses.add(error)\n printVerboseErrorCause(cause, 1, visitedCauses)\n }\n}\n\n/**\n * Format the textual representation of a single cause-chain link. `Error`\n * values surface their message; other values are rendered through\n * a bounded inspector so primitives and plain objects still carry diagnostic\n * context without dumping unbounded text.\n */\nfunction formatCauseValue(cause: unknown): string {\n if (cause instanceof Error) return cause.message\n // R-0000580: replace `String(cause)` with `util.inspect` so plain objects\n // produce useful output (\"[object Object]\" → `{ key: \"…\" }`) and the result\n // is capped via depth/string-length bounds.\n return inspectRedactedDiagnosticValue(cause, {\n depth: CAUSE_INSPECT_DEPTH,\n maxStringLength: CAUSE_INSPECT_MAX_STRING_LENGTH,\n redactMaxDepth: CAUSE_REDACT_BINARY_MAX_DEPTH,\n })\n}\n\n/**\n * Walk the `Error.cause` chain of `error` and emit one `Cause: …` block per\n * level on stderr. Used by {@link printCommandFailure} so generic (non-\n * {@link CommandError}) failures surface their wrapped root cause even in\n * non-verbose mode — without this, only `Error.message` would reach stderr\n * and the actual reason (a wrapped `ECONNREFUSED`, a parse failure, …)\n * would stay hidden until the operator re-ran with `--verbose`.\n *\n * The walker keeps a {@link WeakSet} of already-visited `Error` references\n * so a self-referencing or cyclic `cause` chain — which a misbehaving\n * library can construct — cannot loop forever.\n *\n * @param error - The root `Error` whose `.cause` chain should be printed.\n */\nfunction printCauseChain(error: Error): void {\n const visited = new WeakSet<Error>()\n visited.add(error)\n let cause = getErrorCause(error)\n while (cause !== undefined) {\n if (cause instanceof Error) {\n if (visited.has(cause)) return\n visited.add(cause)\n }\n console.error(pc.red(`${getErrorIndent()}Cause: ${maskRegisteredSecrets(formatCauseValue(cause))}`))\n // R-0000580: when a `CommandError` appears as a cause it carries the full\n // stdout/stderr of the failed remote command. Emit those streams the same\n // way `printVerboseCommandError` does so the operator does not lose the\n // command output once it has been wrapped into an outer error. Stdout and\n // stderr go through `maskRegisteredSecrets` like the existing verbose path\n // in `printCommandFailure`.\n if (cause instanceof CommandError) {\n printVerboseCommandError(\n maskRegisteredSecrets(cause.fullStdout),\n maskRegisteredSecrets(cause.fullStderr)\n )\n }\n cause = cause instanceof Error ? getErrorCause(cause) : undefined\n }\n}\n\n/**\n * Print the error message of a failed command and, when verbose mode is active\n * and the error is a {@link CommandError}, the full untruncated output.\n *\n * R-0000041: every string written to stderr is passed through\n * {@link maskRegisteredSecrets} so resolved op values, sudo passwords, user\n * password hashes, and download URL tokens never leak through generic\n * `Error.message` or stack-trace paths. Modules register their secrets via\n * `secretSink.registerSecret` (or `withRegisteredSecrets`) for the duration\n * of the work that produces them.\n *\n * @param error - The caught error value.\n * @param verbose - Whether to show full stdout/stderr.\n */\nexport function printCommandFailure(error: unknown, verbose: boolean): void {\n if (verbose && error instanceof CommandError) {\n // Print only the exit-code line, skip the truncated output and hint\n const summaryLine = error.message.split(\"\\n\")[0]\n printCommandError(\"\", maskRegisteredSecrets(summaryLine))\n printVerboseCommandError(\n maskRegisteredSecrets(error.fullStdout),\n maskRegisteredSecrets(error.fullStderr)\n )\n return\n }\n\n printCommandError(\"\", maskRegisteredSecrets(String(error)))\n if (error instanceof Error) {\n if (!(error instanceof CommandError)) {\n // R-0000520: for generic Errors, surface the Error.cause chain even\n // without --verbose so the wrapped root cause (ECONNREFUSED behind a\n // wrapper Error, a parser failure behind a domain Error, …) reaches\n // stderr instead of being hidden behind the top-level message. Cycle-\n // safe via WeakSet. CommandError has its own structured diagnostic\n // surface (full stdout/stderr) and is handled in verbose mode above.\n printCauseChain(error)\n }\n if (verbose) {\n printVerboseGenericError(error)\n }\n }\n}\n\n/**\n * Print a run summary line with counts for each status category.\n *\n * @param stats - Aggregated counts from the completed run.\n * @param stats.changed - Number of modules that changed state.\n * @param stats.ok - Number of modules already in desired state.\n * @param stats.skipped - Number of modules skipped.\n * @param stats.failed - Number of modules that failed.\n * @param stats.signals - Number of signals triggered.\n */\nexport function printSummary(stats: {\n changed: number\n failed: number\n ok: number\n signals: number\n skipped: number\n}): void {\n stopAnimatedModuleLine(true)\n const parts = [\n pc.yellow(`${stats.changed} changed`),\n pc.green(`${stats.ok} ok`),\n pc.dim(`${stats.skipped} skipped`),\n stats.failed > 0 ? pc.red(`${stats.failed} failed`) : `${stats.failed} failed`,\n pc.cyan(`${stats.signals} signals triggered`),\n ]\n console.log(`\\n${parts.join(pc.dim(\" \\u00b7 \"))}`)\n}\n","import { stripVTControlCharacters } from \"node:util\"\n\nimport type { ModuleStatus } from \"./types.js\"\n\ntype DisplayStatus = \"waiting\" | ModuleStatus\n\nconst PACKAGE_MODULE_SUFFIX_LENGTH = 2\nconst PACKAGE_COLUMN_GAP_WIDTH = 2\nconst DEFAULT_TERMINAL_COLUMNS = 100\nconst MIN_PACKAGE_COLUMNS_WIDTH = 24\nconst MIN_PACKAGE_COLUMN_WIDTH = 18\nconst MIN_ANIMATED_LINE_COLUMNS = 8\n\nexport type DisplayModule = {\n detail?: string\n detailLines: string[]\n name: string\n}\n\nfunction splitPackageModuleName(name: string): { packages: string[]; summaryName: string } | null {\n for (const prefix of [\"package.installed: \", \"package.absent: \"]) {\n if (!name.startsWith(prefix)) continue\n\n const packages = name\n .slice(prefix.length)\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean)\n if (packages.length === 0) return null\n\n return {\n packages,\n summaryName: prefix.slice(0, -PACKAGE_MODULE_SUFFIX_LENGTH),\n }\n }\n\n return null\n}\n\nfunction getPackageColumns(parameters: {\n continuationIndentWidth: number\n packages: string[]\n terminalColumns?: number\n}): string[] {\n if (parameters.packages.length <= 1) return parameters.packages\n\n const availableWidth = Math.max(\n (parameters.terminalColumns ?? DEFAULT_TERMINAL_COLUMNS) - parameters.continuationIndentWidth,\n MIN_PACKAGE_COLUMNS_WIDTH\n )\n const widestPackage = Math.max(...parameters.packages.map((entry) => entry.length))\n const columnWidth = Math.max(widestPackage + PACKAGE_COLUMN_GAP_WIDTH, MIN_PACKAGE_COLUMN_WIDTH)\n const columnCount = Math.max(Math.floor(availableWidth / columnWidth), 1)\n const rowCount = Math.ceil(parameters.packages.length / columnCount)\n\n return Array.from({ length: rowCount }, (_row, rowIndex) =>\n Array.from({ length: columnCount }, (_column, columnIndex) => {\n const packageIndex = rowIndex + columnIndex * rowCount\n const packageName = parameters.packages.at(packageIndex)\n if (packageName == null) return \"\"\n const isLastVisibleColumn =\n columnIndex === columnCount - 1 || packageIndex + rowCount >= parameters.packages.length\n return isLastVisibleColumn ? packageName : packageName.padEnd(columnWidth)\n })\n .filter(Boolean)\n .join(\"\")\n )\n}\n\nfunction getPackageSummaryDetail(packageCount: number, detail?: string): string {\n const packageLabel = packageCount === 1 ? \"1 package\" : `${packageCount} packages`\n return detail == null ? packageLabel : `${packageLabel} · ${detail}`\n}\n\nexport function fitAnimatedModuleLine(line: string, columns?: number): string {\n if (columns === undefined || columns < MIN_ANIMATED_LINE_COLUMNS) return line\n\n const plainLine = stripVTControlCharacters(line)\n if (plainLine.length < columns) return line\n\n return `${plainLine.slice(0, columns - 1)}\\u2026`\n}\n\nexport function formatDisplayModule(parameters: {\n continuationIndentWidth: number\n detail?: string\n name: string\n status: DisplayStatus\n terminalColumns?: number\n}): DisplayModule {\n const packageModule = splitPackageModuleName(parameters.name)\n if (packageModule == null) {\n return {\n detail: parameters.detail,\n detailLines: [],\n name: parameters.name,\n }\n }\n\n return {\n detail: getPackageSummaryDetail(packageModule.packages.length, parameters.detail),\n detailLines:\n parameters.status === \"waiting\"\n ? []\n : getPackageColumns({\n continuationIndentWidth: parameters.continuationIndentWidth,\n packages: packageModule.packages,\n terminalColumns: parameters.terminalColumns,\n }),\n name: packageModule.summaryName,\n }\n}\n","/**\n * Process-scoped registry of sensitive material that must be redacted before\n * any failure diagnostic, stack trace, or verbose output is written to\n * stderr. Modules register secrets just before they pass them to a remote\n * tool (op resolved values, sudo passwords, user password hashes, download\n * URL tokens, signed URLs) and unregister them when the work completes.\n *\n * The sink is intentionally process-scoped — analogous to\n * {@link \"./runnerAbortSignal\".setRunnerAbortSignal} — so that\n * {@link \"./output\"} can observe the registered secrets without having to\n * thread them through every caller. The runner ensures secrets are cleared\n * on shutdown.\n *\n * R-0000041: `printCommandFailure` and `printVerboseGenericError` consult\n * this sink so a thrown {@link Error} whose message or stack trace contains\n * registered secret material is masked before it reaches stderr.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\"\n\nimport type { ModuleResult } from \"./types.js\"\n\nimport { isSecretDiagnosticField, REDACTED_SECRET_FIELD_PLACEHOLDER } from \"./errorRedaction.js\"\nimport { CommandError, maskSecrets } from \"./sshHelpers.js\"\n\n/**\n * Reference-counted registry: a single secret may be registered concurrently\n * by multiple modules. The counter ensures `unregisterSecret` only forgets\n * the value once the last registration goes out of scope.\n */\nconst secretCounts = new Map<string, number>()\nconst runScopedSecretCounts = new AsyncLocalStorage<Map<string, number>>()\nconst REDACTED_PLACEHOLDER = REDACTED_SECRET_FIELD_PLACEHOLDER\nconst CIRCULAR_PLACEHOLDER = \"[Circular]\"\n\nfunction assertRegistrableSecret(secret: string): void {\n if (secret.includes(REDACTED_PLACEHOLDER)) {\n throw new Error(\"Secret registrations must not contain the redaction placeholder\")\n }\n}\n\n/**\n * Minimum length of a secret string that will be accepted by\n * {@link registerSecret}. Values shorter than this are silently ignored so a\n * stray one- or two-character token (a single TOTP digit, a partially\n * extracted PIN, …) cannot turn every byte of diagnostic output into the\n * redaction marker via `replaceAll`.\n *\n * R-0000583: kept at 8 so a 6-digit TOTP code cannot collide with arbitrary\n * 6-digit substrings in diagnostic text, and so reference-counting via plain\n * string identity does not get confused by short 4-character fragments that\n * appear in many unrelated values.\n */\nconst MINIMUM_SECRET_LENGTH = 8\n\n/**\n * Register a secret string for redaction in subsequent diagnostic output.\n *\n * Values shorter than {@link MINIMUM_SECRET_LENGTH} characters are silently\n * ignored to keep `replaceAll(value, REDACTED)` from accidentally turning\n * every byte of the diagnostic output into the redaction marker. The\n * registration is optimistic — callers do not learn that a short secret was\n * dropped because the sink is shared process-wide and an error here would\n * abort the workload that produced the value.\n *\n * @param secret - The sensitive value to mask in stderr output. Ignored when\n * shorter than {@link MINIMUM_SECRET_LENGTH} characters.\n */\nexport function registerSecret(secret: string): void {\n if (secret.length < MINIMUM_SECRET_LENGTH) return\n assertRegistrableSecret(secret)\n secretCounts.set(secret, (secretCounts.get(secret) ?? 0) + 1)\n}\n\n/**\n * Register a secret and attach that registration to the active run scope.\n *\n * This is intentionally separate from {@link registerSecret}: most callers\n * already balance their own registration with {@link unregisterSecret}. Values\n * produced by `op.resolve` must stay redacted for the whole playbook run, then\n * be released when that run finishes.\n *\n * @param secret - The sensitive value to mask for the active run.\n */\nexport function registerRunScopedSecret(secret: string): void {\n registerSecret(secret)\n if (secret.length < MINIMUM_SECRET_LENGTH) return\n const scopedSecrets = runScopedSecretCounts.getStore()\n if (scopedSecrets == null) return\n scopedSecrets.set(secret, (scopedSecrets.get(secret) ?? 0) + 1)\n}\n\n/**\n * Check whether long-lived secret registrations can currently be tied to a\n * run-scoped cleanup boundary.\n *\n * @returns `true` when {@link registerRunScopedSecret} can attach cleanup to\n * the active playbook or direct-apply run scope.\n */\nexport function hasActiveRunScopedSecretScope(): boolean {\n return runScopedSecretCounts.getStore() != null\n}\n\n/**\n * Decrement the registration count for a previously registered secret. When\n * the counter reaches zero the value is forgotten so the sink does not grow\n * unboundedly across long-running CLI sessions.\n *\n * Calling this with an unknown secret is a no-op so callers can use it from\n * `try`/`finally` blocks without worrying about double-frees.\n *\n * @param secret - The previously registered value.\n */\nexport function unregisterSecret(secret: string): void {\n if (secret.length < MINIMUM_SECRET_LENGTH) return\n const current = secretCounts.get(secret)\n if (current == null) return\n if (current <= 1) {\n secretCounts.delete(secret)\n return\n }\n secretCounts.set(secret, current - 1)\n}\n\n/**\n * Run `body` with `secrets` registered for redaction. Any subsequent failure\n * diagnostic that surfaces through {@link \"./output\".printCommandFailure} or\n * {@link \"./output\".printVerboseGenericError} will mask the registered\n * values. The registrations are released on both success and failure paths.\n *\n * Use this helper when secret material flows through synchronous-or-async\n * code that may throw — it keeps the sink balanced even when the caller\n * does not own the error.\n *\n * @param secrets - The values to register process-wide and mask inside this\n * scope. Values shorter than {@link MINIMUM_SECRET_LENGTH} characters are\n * only ignored for process-wide registration; scoped result/error masking\n * still redacts them.\n * @param body - The async unit of work whose failures must be redacted.\n * @returns Whatever `body` resolves to.\n */\nexport async function withRegisteredSecrets<T>(\n secrets: readonly string[],\n body: () => Promise<T>\n): Promise<T> {\n const registered: string[] = []\n const registerableSecrets = secrets.filter((secret) => secret.length >= MINIMUM_SECRET_LENGTH)\n // R-0000195: Validate up-front so the register loop only runs on inputs\n // that are guaranteed registrable. The register loop and the body are then\n // both protected by the same try/finally — if a future API extension\n // causes `registerSecret` to throw mid-iteration, already-registered\n // values are still released.\n for (const secret of secrets) {\n assertRegistrableSecret(secret)\n }\n try {\n for (const secret of registerableSecrets) {\n registerSecret(secret)\n registered.push(secret)\n }\n const result = await body()\n return maskScopedResult(result, secrets)\n } catch (error) {\n throw maskScopedError(error, secrets)\n } finally {\n for (const secret of registered) {\n unregisterSecret(secret)\n }\n }\n}\n\n/**\n * Execute a playbook run with a cleanup scope for long-lived secret\n * registrations. Each scoped secret is released exactly as often as it was\n * registered in this run, preserving reference counts for concurrent runs.\n *\n * @param body - The async run body.\n * @returns Whatever `body` resolves to.\n */\nexport async function withRunScopedSecrets<T>(body: () => Promise<T>): Promise<T> {\n if (runScopedSecretCounts.getStore() != null) {\n return body()\n }\n\n const scopedSecrets = new Map<string, number>()\n try {\n return await runScopedSecretCounts.run(scopedSecrets, body)\n } finally {\n for (const [secret, count] of scopedSecrets) {\n for (let index = 0; index < count; index += 1) {\n unregisterSecret(secret)\n }\n }\n }\n}\n\nfunction maskScopedResult<T>(result: T, secrets: readonly string[]): T {\n if (secrets.length === 0 || !isModuleResult(result)) return result\n // R-0000477: mask `detail` for every status — a module that returns\n // `status: \"ok\"` (or \"changed\" / \"skipped\") with secrets embedded in the\n // detail string would otherwise leak the registered material through the\n // module summary line. The `error` mask remains conditional on the failed\n // status because that field is only populated on failure.\n const secretList = [...secrets]\n let next: ModuleResult | T = result\n if (result.detail != null) {\n const maskedDetail = maskSecrets(result.detail, secretList)\n if (maskedDetail !== result.detail) {\n next = { ...result, detail: maskedDetail }\n }\n }\n if (isModuleResult(next) && next.status === \"failed\" && next.error != null) {\n next = { ...next, error: maskScopedError(next.error, secrets) }\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- preserved at runtime: we only ever clone the same ModuleResult shape that the type guard already verified.\n return next as T\n}\n\nfunction isModuleResult(value: unknown): value is ModuleResult {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"status\" in value &&\n typeof (value as { status?: unknown }).status === \"string\"\n )\n}\n\nfunction maskCauseValue(cause: unknown, secrets: readonly string[], secretList: string[]): unknown {\n if (cause === undefined) return undefined\n if (cause instanceof Error) return maskScopedError(cause, secrets)\n return maskSecrets(stringifyCause(cause, secretList), secretList)\n}\n\nfunction buildMaskedErrorClone(error: Error, maskedMessage: string, secretList: string[]): Error {\n if (error instanceof CommandError) {\n return new CommandError(\n maskedMessage,\n maskSecrets(error.fullStdout, secretList),\n maskSecrets(error.fullStderr, secretList)\n )\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- preserving Error subclass prototype without re-running its constructor; Object.create is the standard pattern.\n const clone: Error = Object.create(Object.getPrototypeOf(error) as object) as Error\n Object.defineProperty(clone, \"message\", {\n configurable: true,\n value: maskedMessage,\n writable: true,\n })\n return clone\n}\n\n// R-0000259: return a masked clone instead of mutating the original Error.\n// Mutating the original made the redaction irreversible — consumers outside\n// the scope (test frameworks, parallel loggers) still saw the masked values\n// after `withRegisteredSecrets` returned. Cloning leaves the caller's Error\n// untouched and keeps the scope-local masking semantics intact.\nfunction maskScopedError(error: unknown, secrets: readonly string[]): Error {\n if (!(error instanceof Error) || secrets.length === 0) {\n return error instanceof Error ? error : new Error(maskSecrets(String(error), [...secrets]))\n }\n\n const secretList = [...secrets]\n const maskedMessage = maskSecrets(error.message, secretList)\n const maskedStack = error.stack == null ? undefined : maskSecrets(error.stack, secretList)\n const maskedCause = maskCauseValue(error.cause, secrets, secretList)\n\n const clone = buildMaskedErrorClone(error, maskedMessage, secretList)\n clone.name = error.name\n if (maskedStack != null) {\n Object.defineProperty(clone, \"stack\", {\n configurable: true,\n value: maskedStack,\n writable: true,\n })\n }\n if (maskedCause !== undefined) {\n Object.defineProperty(clone, \"cause\", {\n configurable: true,\n value: maskedCause,\n writable: true,\n })\n }\n return clone\n}\n\nfunction stringifyCause(cause: unknown, secretList: string[]): string {\n if (typeof cause === \"string\") return cause\n if (typeof cause === \"function\")\n return cause.name.length > 0 ? `[Function: ${cause.name}]` : \"[Function]\"\n if (typeof cause === \"number\") return String(cause)\n if (typeof cause === \"boolean\") return String(cause)\n if (typeof cause === \"bigint\") return String(cause)\n if (typeof cause === \"symbol\") return String(cause)\n if (cause === undefined) return String(cause)\n if (cause === null) return \"null\"\n return stringifyObjectCause(cause, secretList)\n}\n\nfunction stringifyObjectCause(cause: object, secretList: string[]): string {\n try {\n const serialized = JSON.stringify(normalizeObjectCause(cause, secretList, new WeakSet())) as\n | string\n | undefined\n return serialized ?? Object.prototype.toString.call(cause)\n } catch {\n return Object.prototype.toString.call(cause)\n }\n}\n\nfunction normalizeObjectCause(cause: object, secretList: string[], seen: WeakSet<object>): unknown {\n if (isBinaryCauseValue(cause)) return REDACTED_PLACEHOLDER\n if (cause instanceof Date) return cause.toJSON()\n if (seen.has(cause)) return CIRCULAR_PLACEHOLDER\n seen.add(cause)\n try {\n if (Array.isArray(cause)) {\n return cause.map((value) => normalizeCausePropertyValue(value, secretList, seen))\n }\n\n const normalized: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(cause)) {\n if (isSecretDiagnosticField(key)) {\n normalized[key] = REDACTED_PLACEHOLDER\n continue\n }\n normalized[key] = normalizeCausePropertyValue(value, secretList, seen)\n }\n return normalized\n } finally {\n seen.delete(cause)\n }\n}\n\nfunction normalizeCausePropertyValue(\n value: unknown,\n secretList: string[],\n seen: WeakSet<object>\n): unknown {\n if (value === null) return null\n if (typeof value === \"bigint\") return String(value)\n if (typeof value !== \"object\") return value\n return normalizeObjectCause(value, secretList, seen)\n}\n\nfunction isBinaryCauseValue(value: object): boolean {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value)\n}\n\n/**\n * Return a snapshot of currently registered secrets for masking. Used\n * internally by {@link \"./output\"} — modules should not call this directly.\n *\n * @returns A new array of registered secret values.\n */\nexport function getRegisteredSecrets(): string[] {\n return [...secretCounts.keys()]\n}\n\n/**\n * Clear the entire sink. Used by the runner during a hard-exit shutdown\n * (second SIGINT/SIGTERM) and by tests.\n *\n * R-0000518: do NOT call this on normal per-run teardown. The sink is\n * reference-counted via {@link registerSecret} / {@link unregisterSecret},\n * so balanced scopes (in particular {@link withRegisteredSecrets}) drain it\n * automatically. Wiping the sink unconditionally between concurrent\n * `runPlaybook` invocations sharing the same Node process would strip the\n * redaction context of every still-running scope.\n *\n * Intentionally NOT exported through the public package surface so playbooks\n * cannot accidentally drop the redaction context for the rest of the run.\n */\nexport function clearRegisteredSecrets(): void {\n secretCounts.clear()\n}\n\n/**\n * Apply the registered secrets to `text`. When the sink is empty the input\n * is returned unchanged. Convenience wrapper around {@link maskSecrets} that\n * keeps the variant-resolution overhead out of the caller.\n *\n * @param text - The string to redact.\n * @returns The redacted text, or `text` itself when no secrets are registered.\n */\nexport function maskRegisteredSecrets(text: string): string {\n if (secretCounts.size === 0) return text\n return maskSecrets(text, getRegisteredSecrets())\n}\n","/* eslint-disable max-lines, max-statements -- ssh2 stream handlers are intentionally co-located */\nimport type { Client, ClientChannel, ConnectConfig } from \"ssh2\"\n\nimport { StringDecoder } from \"node:string_decoder\"\n\nimport type { ExecOptions, ExecResult } from \"./types.js\"\n\nimport { createTerminalSanitizer } from \"./terminalSanitizer.js\"\n\n/**\n * Validate that a file mode string is a valid octal permission (e.g. \"644\", \"0755\").\n *\n * @param mode - The mode string to validate.\n * @throws {Error} If the mode is not a 3- or 4-digit octal string.\n */\nexport function validateMode(mode: string): void {\n if (!/^[0-7]{3,4}$/v.test(mode)) {\n throw new Error(\n `Invalid file mode '${mode}': expected a 3- or 4-digit octal string (e.g. \"644\", \"0755\")`\n )\n }\n}\n\n/**\n * Safely quote a string for use in a POSIX shell command.\n * Wraps the value in single quotes and escapes any embedded single quotes.\n *\n * @param s - The string to quote.\n * @returns The shell-safe quoted string.\n */\nexport function shellQuote(s: string): string {\n return `'${s.replaceAll(\"'\", \"'\\\\''\")}'`\n}\n\nconst CONNECTION_TIMEOUT = 10_000\nexport const DEFAULT_MAX_OUTPUT_BYTES = Number(\"1048576\")\n/**\n * Marker appended to {@link CapturedOutput} when the captured stream exceeds\n * the configured maxOutputBytes. Exported so callers that compare a captured\n * `result.stdout` against an external source-of-truth (e.g. `ssh.readFile`,\n * `ssh.sha256`) can detect truncation and refuse to act on corrupted data.\n */\nexport const CAPTURE_TRUNCATION_MARKER = \"\\n[output truncated]\"\n\nfunction ignoreTeardownError(): void {\n /* teardown sink: late ssh2 errors after cleanup must not crash Node */\n}\n\n/**\n * Maximum number of characters included in a {@link CommandError} message\n * before the output is truncated. Output beyond this limit is still available\n * on {@link CommandError.fullStdout} and {@link CommandError.fullStderr}, up\n * to the configured capture limit.\n */\nexport const MAX_OUTPUT_LENGTH = 500\n\nfunction truncateOutput(text: string): string {\n let count = 0\n let sliceEnd = 0\n for (const char of text) {\n if (count >= MAX_OUTPUT_LENGTH) return `${text.slice(0, sliceEnd)}…(truncated)`\n sliceEnd += char.length\n count++\n }\n return text\n}\n\nfunction codepointLengthExceeds(text: string, limit: number): boolean {\n let count = 0\n for (const _char of text) {\n count++\n if (count > limit) return true\n }\n return false\n}\n\nfunction fitUtf8Prefix(text: string, maxBytes: number): string {\n let bytes = 0\n let sliceEnd = 0\n for (const char of text) {\n const charBytes = Buffer.byteLength(char, \"utf8\")\n if (bytes + charBytes > maxBytes) break\n bytes += charBytes\n sliceEnd += char.length\n }\n return text.slice(0, sliceEnd)\n}\n\nfunction resolveMaxOutputBytes(options: ExecOptions): number {\n const value = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(\"ExecOptions.maxOutputBytes must be a non-negative finite number\")\n }\n return Math.floor(value)\n}\n\nfunction resolveReadyTimeout(parameters: ConnectParameters): number {\n return Math.max(\n 1,\n Math.min(CONNECTION_TIMEOUT, Math.floor(parameters.readyTimeout ?? CONNECTION_TIMEOUT))\n )\n}\n\nclass CapturedOutput {\n private byteLength = 0\n private text = \"\"\n private truncated = false\n\n public constructor(private readonly maxBytes: number) {}\n\n public append(chunk: string): void {\n if (this.truncated) return\n const chunkBytes = Buffer.byteLength(chunk, \"utf8\")\n const remainingBytes = this.maxBytes - this.byteLength\n if (chunkBytes <= remainingBytes) {\n this.text += chunk\n this.byteLength += chunkBytes\n return\n }\n\n if (remainingBytes > 0) {\n const prefix = fitUtf8Prefix(chunk, remainingBytes)\n this.text += prefix\n this.byteLength += Buffer.byteLength(prefix, \"utf8\")\n }\n this.text += CAPTURE_TRUNCATION_MARKER\n this.truncated = true\n }\n\n public isTruncated(): boolean {\n return this.truncated\n }\n\n public toString(): string {\n return this.text\n }\n}\n\ntype CapturedStreams = {\n capturedStderr: string\n capturedStdout: string\n stderr: CapturedOutput\n stdout: CapturedOutput\n}\n\nfunction outputSummaryWasTruncated(streams: CapturedStreams): boolean {\n return (\n streams.stdout.isTruncated() ||\n streams.stderr.isTruncated() ||\n codepointLengthExceeds(streams.capturedStdout, MAX_OUTPUT_LENGTH) ||\n codepointLengthExceeds(streams.capturedStderr, MAX_OUTPUT_LENGTH)\n )\n}\n\ntype CommandErrorParameters = {\n capturedStderr: string\n capturedStdout: string\n command: string\n reason: string\n secrets: PreparedSecrets\n wasTruncated: boolean\n}\n\nfunction buildCommandError(parameters: CommandErrorParameters): CommandError {\n const hint = parameters.wasTruncated ? \"\\n(use --verbose for full output)\" : \"\"\n return new CommandError(\n `Command failed with ${parameters.reason}: ${maskPreparedSecrets(parameters.command, parameters.secrets)}\\nstdout: ${truncateOutput(parameters.capturedStdout)}\\nstderr: ${truncateOutput(parameters.capturedStderr)}${hint}`,\n parameters.capturedStdout,\n parameters.capturedStderr\n )\n}\n\n/**\n * Error thrown when a remote command exits with a non-zero exit code.\n *\n * The `message` contains a truncated summary of stdout and stderr (up to\n * {@link MAX_OUTPUT_LENGTH} characters each). Captured output is capped by\n * `ExecOptions.maxOutputBytes`; truncated captures are marked in\n * {@link CommandError.fullStdout} and {@link CommandError.fullStderr}.\n *\n * @example\n * ```ts\n * try {\n * await ssh.exec(\"exit 1\")\n * } catch (error) {\n * if (error instanceof CommandError) {\n * console.error(error.fullStderr)\n * }\n * }\n * ```\n */\nexport class CommandError extends Error {\n /** Full, untruncated standard error of the failed command. */\n public readonly fullStderr: string\n /** Full, untruncated standard output of the failed command. */\n public readonly fullStdout: string\n\n public constructor(message: string, fullStdout: string, fullStderr: string) {\n super(message)\n this.name = \"CommandError\"\n this.fullStdout = fullStdout\n this.fullStderr = fullStderr\n Error.captureStackTrace(this, CommandError)\n }\n}\n\nexport type StreamOutputParameters = {\n command: string\n options: ExecOptions\n reject: (reason: Error) => void\n resolve: (value: ExecResult) => void\n secrets?: PreparedSecrets | SecretSource[]\n stream: ClientChannel\n timer: ReturnType<typeof setTimeout>\n}\n\nexport type SecretSource = (() => string) | string\nexport type PreparedSecrets = {\n variants: string[]\n}\n\n/** Placeholder used when redacting secrets from output. */\nconst REDACTED = \"[REDACTED]\"\n\nfunction resolveSecret(secret: SecretSource): string {\n return typeof secret === \"function\" ? secret() : secret\n}\n\nfunction getSecretVariants(secrets: SecretSource[]): string[] {\n const variants = new Set<string>()\n for (const source of secrets) {\n const secret = resolveSecret(source)\n if (secret.length === 0) continue\n if (secret.includes(REDACTED)) {\n throw new Error(`Secret must not contain the redaction placeholder \"${REDACTED}\"`)\n }\n\n variants.add(secret)\n\n const encoded = encodeURIComponent(secret)\n // eslint-disable-next-line security/detect-possible-timing-attacks -- not a secret comparison, just checking if URL encoding changed the string\n if (encoded !== secret) variants.add(encoded)\n\n const quoted = shellQuote(secret)\n // eslint-disable-next-line security/detect-possible-timing-attacks -- not a secret comparison, just checking if shell quoting changed the string\n if (quoted !== secret) variants.add(quoted)\n }\n return [...variants].sort((a, b) => b.length - a.length)\n}\n\nexport function prepareSecrets(secrets: SecretSource[]): PreparedSecrets {\n return { variants: getSecretVariants(secrets) }\n}\n\nfunction ensurePreparedSecrets(secrets: PreparedSecrets | SecretSource[]): PreparedSecrets {\n return \"variants\" in secrets ? secrets : prepareSecrets(secrets)\n}\n\nfunction createVariantResolver(secrets: PreparedSecrets): {\n getMaxLength: () => number\n mask: (text: string) => string\n} {\n return {\n getMaxLength(): number {\n return Math.max(0, ...secrets.variants.map((variant) => variant.length))\n },\n mask(text: string): string {\n let masked = text\n for (const variant of secrets.variants) {\n masked = masked.replaceAll(variant, REDACTED)\n }\n return masked\n },\n }\n}\n\nexport function maskPreparedSecrets(text: string, secrets: PreparedSecrets): string {\n return createVariantResolver(secrets).mask(text)\n}\n\nexport function maskSecrets(text: string, secrets: SecretSource[]): string {\n return maskPreparedSecrets(text, prepareSecrets(secrets))\n}\n\n/**\n * Create a sliding-window masker that buffers up to `maxSecretLength - 1`\n * characters so that secrets split across chunk boundaries are still masked\n * in live output.\n *\n * @param write - Callback that receives masked text for live output.\n * @param secrets - List of secret strings to mask.\n * @returns An object with `push` (feed new data) and `flush` (emit remaining buffer).\n */\nexport function createStreamMasker(\n write: (text: string) => void,\n secrets: PreparedSecrets | SecretSource[]\n): { flush: () => void; push: (chunk: string) => void } {\n const resolver = createVariantResolver(ensurePreparedSecrets(secrets))\n let overlap: null | number = null\n const getOverlap = (): number => {\n overlap ??= Math.max(0, resolver.getMaxLength() - 1)\n return overlap\n }\n\n let pending = \"\"\n\n return {\n flush(): void {\n if (pending.length > 0) {\n write(resolver.mask(pending))\n pending = \"\"\n }\n },\n push(chunk: string): void {\n const currentOverlap = getOverlap()\n if (currentOverlap === 0) {\n write(resolver.mask(chunk))\n return\n }\n pending += chunk\n if (pending.length <= currentOverlap) return\n // Mask the whole buffer first so secrets fully contained in\n // pending are replaced before the split. The overlap is then\n // taken from the *masked* result — safe because maskSecrets()\n // rejects any secret that contains the redaction placeholder.\n const masked = resolver.mask(pending)\n if (masked.length <= currentOverlap) {\n pending = masked\n return\n }\n write(masked.slice(0, -currentOverlap))\n pending = masked.slice(-currentOverlap)\n },\n }\n}\n\nfunction writeStdout(t: string): void {\n process.stdout.write(t)\n}\nfunction writeStderr(t: string): void {\n process.stderr.write(t)\n}\n\nfunction createSanitizedTerminalWriter(silent: boolean): {\n flush: () => void\n stderr: (text: string) => void\n stdout: (text: string) => void\n} {\n const stdoutTerminalSanitizer = createTerminalSanitizer()\n const stderrTerminalSanitizer = createTerminalSanitizer()\n\n return {\n flush(): void {\n if (silent) return\n writeStdout(stdoutTerminalSanitizer.flush())\n writeStderr(stderrTerminalSanitizer.flush())\n },\n stderr(text: string): void {\n if (!silent) writeStderr(stderrTerminalSanitizer.push(text))\n },\n stdout(text: string): void {\n if (!silent) writeStdout(stdoutTerminalSanitizer.push(text))\n },\n }\n}\n\n/**\n * Normalize ssh2 close-event exit codes. ssh2 may pass `undefined` even though\n * its TypeScript type says `number`; treat that as a successful zero exit code.\n *\n * @param code - Exit code from the ssh2 `close` event.\n * @returns A normalized numeric exit code.\n */\nexport function normalizeSshCloseCode(code: null | number | undefined): number {\n return code ?? 0\n}\n\nfunction normalizeSshCloseSignal(signal: null | string | undefined): string | undefined {\n return signal == null || signal === \"\" ? undefined : signal\n}\n\n/**\n * Wire up event listeners on an ssh2 stream to collect stdout/stderr\n * and resolve or reject the promise when the stream closes.\n *\n * @param parameters - Stream collection parameters.\n */\nexport function collectStreamOutput(parameters: StreamOutputParameters): void {\n const { command, options, reject, resolve, stream, timer } = parameters\n let maxOutputBytes: number\n let secrets: PreparedSecrets\n try {\n maxOutputBytes = resolveMaxOutputBytes(options)\n secrets =\n parameters.secrets == null ? prepareSecrets([]) : ensurePreparedSecrets(parameters.secrets)\n } catch (error) {\n clearTimeout(timer)\n reject(error instanceof Error ? error : new Error(String(error)))\n return\n }\n const stdout = new CapturedOutput(maxOutputBytes)\n const stderr = new CapturedOutput(maxOutputBytes)\n const stdoutDecoder = new StringDecoder(\"utf8\")\n const stderrDecoder = new StringDecoder(\"utf8\")\n const terminalWriter = createSanitizedTerminalWriter(options.silent === true)\n\n const stdoutMasker = createStreamMasker((text) => {\n stdout.append(text)\n terminalWriter.stdout(text)\n }, secrets)\n const stderrMasker = createStreamMasker((text) => {\n stderr.append(text)\n terminalWriter.stderr(text)\n }, secrets)\n const finishStdoutDecode = (): void => {\n const text = stdoutDecoder.end()\n if (text.length > 0) stdoutMasker.push(text)\n }\n const finishStderrDecode = (): void => {\n const text = stderrDecoder.end()\n if (text.length > 0) stderrMasker.push(text)\n }\n\n stream.on(\"data\", (data: Buffer) => {\n stdoutMasker.push(stdoutDecoder.write(data))\n })\n stream.stderr.on(\"data\", (data: Buffer) => {\n stderrMasker.push(stderrDecoder.write(data))\n })\n stream.on(\"error\", (error: Error) => {\n clearTimeout(timer)\n finishStdoutDecode()\n finishStderrDecode()\n stdoutMasker.flush()\n stderrMasker.flush()\n terminalWriter.flush()\n reject(error)\n })\n stream.stderr.on(\"error\", (error: Error) => {\n clearTimeout(timer)\n finishStdoutDecode()\n finishStderrDecode()\n stdoutMasker.flush()\n stderrMasker.flush()\n terminalWriter.flush()\n reject(error)\n })\n stream.on(\"close\", (code: null | number | undefined, signal?: null | string) => {\n clearTimeout(timer)\n finishStdoutDecode()\n finishStderrDecode()\n stdoutMasker.flush()\n stderrMasker.flush()\n terminalWriter.flush()\n const capturedStdout = stdout.toString()\n const capturedStderr = stderr.toString()\n const capturedStreams = { capturedStderr, capturedStdout, stderr, stdout }\n const closeSignal = normalizeSshCloseSignal(signal)\n if (closeSignal !== undefined) {\n reject(\n buildCommandError({\n capturedStderr,\n capturedStdout,\n command,\n reason: `signal ${closeSignal}`,\n secrets,\n wasTruncated: outputSummaryWasTruncated(capturedStreams),\n })\n )\n return\n }\n const exitCode = normalizeSshCloseCode(code)\n if (exitCode !== 0 && options.ignoreExitCode !== true) {\n reject(\n buildCommandError({\n capturedStderr,\n capturedStdout,\n command,\n reason: `exit code ${exitCode}`,\n secrets,\n wasTruncated: outputSummaryWasTruncated(capturedStreams),\n })\n )\n return\n }\n resolve({ code: exitCode, stderr: capturedStderr, stdout: capturedStdout })\n })\n}\n\n/** Parameters for a single SSH connection attempt on one port. */\nexport type ConnectParameters = {\n /** Optional signal used to abort an in-flight connect attempt. */\n abortSignal?: AbortSignal\n /** Path to the SSH agent socket (e.g. `SSH_AUTH_SOCK`). Used when no `privateKey` is provided. */\n agent?: string\n /** Forward the local SSH agent to the remote host during this session. */\n agentForward?: boolean\n /** The ssh2 `Client` instance to connect with. */\n client: Client\n /** Hostname or IP address of the remote host. */\n host: string\n /** Optional host key verifier callback for known_hosts checking. */\n hostVerifier?: (key: Buffer) => boolean\n /** Password for keyboard-interactive or password authentication. */\n password?: string\n /** Port to connect on. */\n port: number\n /** PEM-encoded private key content. Mutually exclusive with `agent`. */\n privateKey?: Buffer | string\n /** Per-port connect timeout in milliseconds. Defaults to the standard SSH connection timeout. */\n readyTimeout?: number\n /** Username to authenticate as. */\n username: string\n}\n\n/**\n * Build the ssh2 `ConnectConfig` from the connection parameters.\n *\n * @param parameters - Connection parameters.\n * @returns The populated config object.\n */\nfunction buildConnectConfig(parameters: ConnectParameters): ConnectConfig {\n const { agent, agentForward, host, hostVerifier, password, port, privateKey, username } =\n parameters\n const readyTimeout = resolveReadyTimeout(parameters)\n const connectConfig: ConnectConfig = {\n host,\n port,\n readyTimeout,\n username,\n }\n if (privateKey != null) {\n connectConfig.privateKey = privateKey\n }\n if (agent != null) {\n connectConfig.agent = agent\n }\n if (agentForward === true) {\n connectConfig.agentForward = true\n }\n if (typeof password === \"string\") {\n connectConfig.password = password\n connectConfig.tryKeyboard = true\n }\n if (hostVerifier != null) {\n connectConfig.hostVerifier = hostVerifier\n }\n return connectConfig\n}\n\n/**\n * Defensively release a ssh2 `Client` instance after a failed connect\n * attempt. R-0000039: ssh2 may keep listeners, internal sockets, and buffers\n * alive even when `connect()` rejected. Calling `removeAllListeners()` and\n * `end()` ensures the FD and event-listener footprint does not grow across\n * retry iterations or reconnect storms.\n *\n * @param client - The ssh2 client whose resources should be released.\n */\nexport function cleanupFailedSshClient(client: Client): void {\n // R-0000794: install the best-effort teardown error sink BEFORE detaching\n // any other listener so a late `error` event from `client.end()` cannot\n // escape as an unhandled exception in the window between detaching and\n // attaching. The sink is idempotent — `attachSshClientTeardownErrorSink`\n // removes any prior `ignoreTeardownError` before re-adding itself.\n attachSshClientTeardownErrorSink(client)\n // Detach every non-error listener so caller-installed `ready`/`close`/…\n // handlers do not fire on the teardown. We deliberately keep the `error`\n // channel's listeners intact: the connect path's `handleError` (if still\n // present) would reject its outer Promise but is itself idempotent\n // (R-0000790), and the freshly attached teardown sink keeps any orphan\n // `error` emit from crashing the process.\n try {\n for (const eventName of client.eventNames()) {\n if (eventName === \"error\") continue\n client.removeAllListeners(eventName)\n }\n } catch {\n /* listener teardown must not throw under any circumstance */\n }\n try {\n client.end()\n } catch {\n /* end() may throw when the underlying socket has already been destroyed */\n }\n}\n\nexport function attachSshClientTeardownErrorSink(client: Client): void {\n try {\n client.removeListener(\"error\", ignoreTeardownError)\n client.on(\"error\", ignoreTeardownError)\n } catch {\n /* installing the best-effort teardown sink must not mask cleanup */\n }\n}\n\nfunction getConnectAbortReason(signal: AbortSignal): Error {\n return signal.reason instanceof Error ? signal.reason : new Error(\"SSH connect aborted\")\n}\n\n/**\n * Attempt a single SSH connection on a specific port.\n *\n * R-0000039: any error path closes the client so a failed attempt does not\n * leak sockets or listeners up to the caller.\n *\n * @param parameters - Connection parameters.\n */\nexport async function tryConnectOnPort(parameters: ConnectParameters): Promise<void> {\n const { abortSignal, client, port } = parameters\n if (parameters.hostVerifier == null) {\n cleanupFailedSshClient(client)\n throw new Error(\n \"Refusing to start ssh2 without a host-key verifier. \" +\n \"Use buildHostVerifier and pass the resulting hostVerifier, or explicitly configure a safe host-key policy.\"\n )\n }\n const connectConfig = buildConnectConfig(parameters)\n const readyTimeout = resolveReadyTimeout(parameters)\n return new Promise((resolve, reject) => {\n if (abortSignal?.aborted === true) {\n cleanupFailedSshClient(client)\n reject(getConnectAbortReason(abortSignal))\n return\n }\n\n const cleanupConnectListeners = (): void => {\n client.off(\"ready\", handleReady)\n client.off(\"error\", handleError)\n abortSignal?.removeEventListener(\"abort\", handleAbort)\n }\n\n const handleTimeout = (): void => {\n cleanupConnectListeners()\n cleanupFailedSshClient(client)\n reject(new Error(`Connection timeout on port ${port}`))\n }\n\n const handleAbort = (): void => {\n clearTimeout(timeout)\n cleanupConnectListeners()\n cleanupFailedSshClient(client)\n reject(\n abortSignal == null ? new Error(\"SSH connect aborted\") : getConnectAbortReason(abortSignal)\n )\n }\n\n const handleReady = (): void => {\n clearTimeout(timeout)\n cleanupConnectListeners()\n resolve()\n }\n\n // R-0000790: a synchronous throw out of `client.connect()` invokes\n // `handleError` from the catch block, which detaches listeners and rejects.\n // ssh2 may still queue a late `error` event before the next tick observes\n // the detach, re-entering `handleError` for the same client. The `settled`\n // flag keeps the rejection / cleanup chain idempotent so the duplicate\n // event is a silent no-op instead of a double `cleanupFailedSshClient` /\n // double `reject` call.\n let settled = false\n const handleError = (error: Error): void => {\n if (settled) return\n settled = true\n clearTimeout(timeout)\n cleanupConnectListeners()\n cleanupFailedSshClient(client)\n reject(error)\n }\n\n const timeout = setTimeout(handleTimeout, readyTimeout)\n\n client.on(\"ready\", handleReady)\n client.on(\"error\", handleError)\n abortSignal?.addEventListener(\"abort\", handleAbort, { once: true })\n try {\n client.connect(connectConfig)\n } catch (error) {\n handleError(error instanceof Error ? error : new Error(String(error)))\n }\n })\n}\n","type TerminalSanitizerState = \"csi\" | \"esc\" | \"osc\" | \"oscEsc\" | \"text\"\n\nconst BYTE_BEL = 0x07\nconst BYTE_TAB = 0x09\nconst BYTE_LF = 0x0a\nconst BYTE_C0_MAX = 0x1f\nconst BYTE_ESC_SEQUENCE_MIN = 0x30\nconst BYTE_CSI_FINAL_MIN = 0x40\nconst BYTE_FINAL_MAX = 0x7e\nconst BYTE_DEL = 0x7f\nconst BYTE_C1_MIN = 0x80\nconst BYTE_C1_CSI = 0x9b\nconst BYTE_C1_ST = 0x9c\nconst BYTE_C1_OSC = 0x9d\nconst BYTE_C1_MAX = 0x9f\nconst ESC = \"\\u001B\"\nconst CSI_START = \"[\"\nconst OSC_START = \"]\"\nconst ST_FINAL = \"\\\\\"\n\nfunction isAllowedTextControl(code: number): boolean {\n return code === BYTE_TAB || code === BYTE_LF\n}\n\nfunction isControlByte(code: number): boolean {\n return code <= BYTE_C0_MAX || code === BYTE_DEL || (code >= BYTE_C1_MIN && code <= BYTE_C1_MAX)\n}\n\nfunction isCsiFinalByte(code: number): boolean {\n return code >= BYTE_CSI_FINAL_MIN && code <= BYTE_FINAL_MAX\n}\n\nfunction isEscSequenceFinalByte(code: number): boolean {\n return code >= BYTE_ESC_SEQUENCE_MIN && code <= BYTE_FINAL_MAX\n}\n\nfunction consumeEscState(char: string, code: number): TerminalSanitizerState {\n if (char === CSI_START) return \"csi\"\n if (char === OSC_START) return \"osc\"\n return isEscSequenceFinalByte(code) || isControlByte(code) ? \"text\" : \"esc\"\n}\n\nfunction consumeOscState(char: string, code: number): TerminalSanitizerState {\n if (code === BYTE_BEL || code === BYTE_C1_ST) return \"text\"\n return char === ESC ? \"oscEsc\" : \"osc\"\n}\n\nfunction consumeTextState(\n char: string,\n code: number\n): { nextState: TerminalSanitizerState; text: string } {\n if (char === ESC) return { nextState: \"esc\", text: \"\" }\n if (code === BYTE_C1_CSI) return { nextState: \"csi\", text: \"\" }\n if (code === BYTE_C1_OSC) return { nextState: \"osc\", text: \"\" }\n return {\n nextState: \"text\",\n text: !isControlByte(code) || isAllowedTextControl(code) ? char : \"\",\n }\n}\n\nfunction consumeTerminalChar(\n state: TerminalSanitizerState,\n char: string\n): { nextState: TerminalSanitizerState; text: string } {\n const code = char.charCodeAt(0)\n switch (state) {\n case \"csi\": {\n return { nextState: isCsiFinalByte(code) ? \"text\" : \"csi\", text: \"\" }\n }\n case \"esc\": {\n return { nextState: consumeEscState(char, code), text: \"\" }\n }\n case \"osc\": {\n return { nextState: consumeOscState(char, code), text: \"\" }\n }\n case \"oscEsc\": {\n return { nextState: char === ST_FINAL ? \"text\" : \"osc\", text: \"\" }\n }\n case \"text\": {\n return consumeTextState(char, code)\n }\n }\n}\n\n/**\n * Create a stateful sanitizer for untrusted terminal text.\n *\n * It removes ANSI/CSI/OSC escape sequences and terminal control bytes while\n * preserving printable text, tabs and newlines. Use one sanitizer per stream\n * when data can arrive in chunks.\n *\n * @returns A sanitizer with `push` for chunks and `flush` for final cleanup.\n */\nexport function createTerminalSanitizer(): { flush: () => string; push: (text: string) => string } {\n let state: TerminalSanitizerState = \"text\"\n\n return {\n flush(): string {\n state = \"text\"\n return \"\"\n },\n push(text: string): string {\n let sanitized = \"\"\n\n for (const char of text) {\n const consumed = consumeTerminalChar(state, char)\n state = consumed.nextState\n sanitized += consumed.text\n }\n\n return sanitized\n },\n }\n}\n\nexport function sanitizeTerminalText(text: string): string {\n const sanitizer = createTerminalSanitizer()\n return `${sanitizer.push(text)}${sanitizer.flush()}`\n}\n","import type { Module } from \"./types.js\"\n\n/**\n * Decide whether the dry-run runner should execute a module's `_applyDryRun`\n * hook in addition to its `check()`.\n *\n * The decision is shared between the top-level runner loop\n * (`runner.runRegularModule`) and the recipe-scoped runner\n * (`dryRunRecipe.executeDryRunChildModule`). Centralizing it here keeps the\n * two paths in lockstep: a future marker tweak only needs one edit.\n *\n * Semantics:\n * - Modules marked as `_dryRunBlocker` (e.g. the first-run-stop module) or\n * `_dryRunMetaProducer` (modules that emit downstream-visible meta even\n * in dry-run mode) always run their `_applyDryRun` / `apply` so the\n * blocker fires and meta keeps propagating.\n * - `_dryRunDiffProducer` modules only run their `_applyDryRun` when the\n * user passed `--diff` (`diffEnabled === true`). Without `--diff`, the\n * dry-run keeps its pre-existing behaviour and pays no extra remote\n * round-trip.\n * - Modules that ship a custom `_applyDryRun` without any of the markers\n * above keep the legacy behaviour: their hook always runs (e.g. the\n * sshd module's config validation has to run regardless of `--diff`).\n *\n * @param module - The candidate module.\n * @param diffEnabled - Whether the user passed `--diff` on the CLI.\n * @returns `true` when the runner should dispatch `_applyDryRun` (or\n * `apply` as the fallback path) instead of treating the module as a\n * passive `changed (dry-run)` result.\n */\nexport function shouldExecuteApplyDuringDryRun(module: Module, diffEnabled: boolean): boolean {\n if (module._dryRunBlocker === true || module._dryRunMetaProducer === true) return true\n if (diffEnabled && module._dryRunDiffProducer === true && module._applyDryRun != null) return true\n return module._applyDryRun != null && module._dryRunDiffProducer !== true\n}\n","/* eslint-disable max-lines -- known_hosts lock, parser, verifier, and persist helpers stay co-located */\nimport { createHash, timingSafeEqual } from \"node:crypto\"\nimport { appendFile, mkdir, readFile, stat } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nimport { describeHostValidationFailure, validateHostLabel } from \"./hostValidation.js\"\nimport { matchesKnownHostPatternList } from \"./knownHostPatterns.js\"\nimport { shellQuote } from \"./sshHelpers.js\"\n\n/**\n * R-0000151 / R-0000194: serialize every read and write against\n * `~/.ssh/known_hosts` through a single in-process mutex. `appendHostKey`\n * runs writes via `appendFile` and `buildHostVerifier` reads the file via\n * `readFileSync`. Both paths must share the same queue, otherwise a\n * synchronous read from a parallel handshake could observe a partially\n * written line that `parseKnownHostsLine` would discard, leaking a valid\n * trust anchor.\n */\nlet knownHostsLock: Promise<unknown> = Promise.resolve()\n\n/**\n * Run `operation` while holding the known_hosts mutex. Both writes\n * (`appendHostKey`) and reads (`buildHostVerifier` -> `loadKnownHostEntries`)\n * funnel through this helper so they observe each other in FIFO order and a\n * synchronous read never overlaps with an async append in flight.\n *\n * @param operation - The read or write to perform while the lock is held.\n * @returns The value returned by `operation` once the lock has been acquired.\n */\nasync function withKnownHostsLock<T>(operation: () => Promise<T> | T): Promise<T> {\n const previous = knownHostsLock\n const next = previous.then(async () => operation())\n // Suppress unhandled-rejection bookkeeping on the chained sentinel; callers\n // observe the real settlement through the returned `next` promise.\n knownHostsLock = next.catch(() => null)\n return next\n}\n\ntype HostVerifierOptions = {\n cache?: HostKeyCache\n expectedHostFingerprint?: string\n expectedHostPublicKey?: string\n}\n\nexport type HostVerifierResult = {\n commitAcceptedHostKey?: () => Promise<void>\n hostVerifier?: (key: Buffer) => boolean\n pendingPersist?: Promise<void>\n}\n\ntype HostLocation = {\n host: string\n port: number\n}\n\n/** Thrown when a remote host key does not match the expected key in known_hosts. */\nexport class HostKeyVerificationError extends Error {\n public constructor(message: string) {\n super(message)\n this.name = \"HostKeyVerificationError\"\n Error.captureStackTrace(this, HostKeyVerificationError)\n }\n}\n\nclass KnownHostsValidationError extends Error {\n public constructor(message: string) {\n super(message)\n this.name = \"KnownHostsValidationError\"\n Error.captureStackTrace(this, KnownHostsValidationError)\n }\n}\n\n/** A parsed entry from a known_hosts file. */\nexport type KnownHostEntry = {\n /** Algorithm name as stored in the file (e.g. `\"ssh-ed25519\"`). */\n algo: string\n /** Host pattern as stored in the file (plain hostname or `[host]:port` notation). */\n host: string\n /** OpenSSH comma-separated host pattern list from the original line. */\n hostPatterns?: string[]\n /** Raw public key bytes decoded from the Base64 field. */\n key: Buffer\n /** Optional OpenSSH marker such as `@revoked`. */\n marker?: string\n}\n\n/** Minimum number of whitespace-separated fields in a valid known_hosts line. */\nconst MIN_KNOWN_HOSTS_FIELDS = 3\n\n/** The default SSH port used by OpenSSH. */\nconst DEFAULT_SSH_PORT = 22\n\n/** Byte size of the uint32 length prefix in SSH wire format. */\nconst UINT32_SIZE = 4\n\n/**\n * Strict base64 alphabet used to validate the key field of a known_hosts line.\n *\n * `Buffer.from(value, \"base64\")` silently ignores invalid characters which would\n * truncate corrupt or tampered entries instead of rejecting them. We therefore\n * reject anything that contains whitespace, padding inside the body, or any\n * character outside the standard base64 alphabet before decoding.\n */\nconst STRICT_BASE64_PATTERN = /^[A-Za-z0-9+\\/]+=*$/v\n\n/**\n * R-0000204: human-readable hint surfaced when a `@cert-authority` entry is\n * encountered. paratix does not validate OpenSSH certificates and refuses to\n * silently fall through to accept-new.\n */\nconst CERT_AUTHORITY_MESSAGE =\n \"HOST KEY VERIFICATION FAILED — known_hosts contains a @cert-authority entry, \" +\n \"but paratix does not validate certificate-authority host keys. \" +\n \"Remove the @cert-authority marker or pin the host with ssh.expectedHostFingerprint / \" +\n \"ssh.expectedHostPublicKey instead.\"\n\n/**\n * In-memory cache for accepted host keys that could not be persisted to disk.\n * Keyed by the formatted host needle (e.g. `\"example.com\"` or `\"[example.com]:2222\"`).\n *\n * R-0000479: the cache is no longer a hidden module-level singleton. Each\n * `SshConnectionImpl` instance owns its own {@link HostKeyCache} so that two\n * parallel SSH connections to the same `[host]:port` cannot overwrite each\n * other's pinned host keys. The exported {@link createHostKeyCache} factory\n * builds a fresh per-instance cache; callers that omit the argument (only the\n * legacy test helpers do this) fall back to the module-level\n * {@link defaultHostKeyCache} so existing fixtures keep working.\n */\nexport type HostKeyCache = Map<string, Buffer>\n\nconst defaultHostKeyCache: HostKeyCache = new Map<string, Buffer>()\n\n/**\n * Build a fresh per-connection host key cache. Each `SshConnectionImpl`\n * allocates one and passes it to {@link buildHostVerifier} for every connect\n * / reconnect attempt; that keeps the in-memory trust state scoped to the\n * owning connection while preserving process-lifetime caching across\n * reconnects of the same instance.\n *\n * @returns A fresh, empty per-connection host key cache.\n */\nexport function createHostKeyCache(): HostKeyCache {\n return new Map<string, Buffer>()\n}\n\n/**\n * Clear the module-level default host key cache. Intended for use in tests\n * that do not allocate their own {@link HostKeyCache}; tests with explicit\n * caches simply discard them between cases.\n */\nexport function clearHostKeyCache(): void {\n defaultHostKeyCache.clear()\n}\n\n/**\n * Wait until every queued known_hosts read or write has finished.\n *\n * R-0000151 / R-0000194: callers that need a synchronous read to observe the\n * result of concurrent persists can `await` this helper first; this drains\n * the known_hosts mutex so subsequent `readFileSync` calls see a consistent\n * file with no partially-written lines in flight. `buildHostVerifier` already\n * routes its read through the lock, so this helper is mainly intended for\n * tests and out-of-band callers.\n */\nexport async function waitForKnownHostsWrites(): Promise<void> {\n await knownHostsLock.catch(() => null)\n}\n\nfunction parseKnownHostsLine(line: string): KnownHostEntry[] {\n const parts = line.split(/\\s+/v)\n const offset = parts[0]?.startsWith(\"@\") ? 1 : 0\n if (parts.length < MIN_KNOWN_HOSTS_FIELDS + offset) return []\n\n const marker = offset === 1 ? parts[0] : undefined\n const hostsPart = parts[offset]\n const algo = parts[offset + 1]\n const base64Key = parts[offset + 2]\n\n // Reject entries whose key field is not strict base64. `Buffer.from` would\n // otherwise drop unknown characters and produce a truncated buffer, which\n // could let corrupt or tampered known_hosts entries influence later lookups\n // such as findRevokedEntry.\n if (!STRICT_BASE64_PATTERN.test(base64Key)) return []\n\n const key = Buffer.from(base64Key, \"base64\")\n const hostPatterns = hostsPart.split(\",\")\n return hostPatterns.map((host) => ({ algo, host, hostPatterns, key, marker }))\n}\n\n/**\n * Parse the contents of an OpenSSH `known_hosts` file into structured entries.\n *\n * - Blank lines and comment lines (starting with `#`) are skipped.\n * - Hosts separated by commas produce one entry per hostname.\n *\n * @param content - The raw file content.\n * @returns An array of parsed entries.\n */\nexport function parseKnownHosts(content: string): KnownHostEntry[] {\n const entries: KnownHostEntry[] = []\n for (const raw of content.split(\"\\n\")) {\n const line = raw.trim()\n if (line.length === 0 || line.startsWith(\"#\")) continue\n entries.push(...parseKnownHostsLine(line))\n }\n return entries\n}\n\n/**\n * Format the host lookup needle for known_hosts matching.\n *\n * For port 22 the plain hostname is returned. For non-standard ports the\n * bracketed `[host]:port` notation is used, matching OpenSSH behavior.\n *\n * @param host - The hostname or IP.\n * @param port - The SSH port.\n * @returns The formatted needle string.\n */\nfunction formatHostNeedle(host: string, port: number): string {\n return port === DEFAULT_SSH_PORT ? host : `[${host}]:${port}`\n}\n\nfunction assertSafeHostForKnownHosts(host: string): void {\n const hostValidationFailure = validateHostLabel(host)\n if (hostValidationFailure == null) return\n throw new KnownHostsValidationError(\n `Refusing to persist known_hosts entry: host label ${describeHostValidationFailure(hostValidationFailure)}`\n )\n}\n\nfunction matchesKnownHostEntry(entry: KnownHostEntry, needle: string): boolean {\n return matchesKnownHostPatternList(entry.hostPatterns ?? [entry.host], needle)\n}\n\nfunction findMatchingEntries(\n entries: KnownHostEntry[],\n host: string,\n port: number\n): KnownHostEntry[] {\n const needle = formatHostNeedle(host, port)\n return entries.filter((entry) => matchesKnownHostEntry(entry, needle))\n}\n\nfunction lookupHostEntry(\n entries: KnownHostEntry[],\n host: string,\n port: number\n): KnownHostEntry | null {\n const match = findMatchingEntries(entries, host, port).find((entry) => entry.marker == null)\n return match ?? null\n}\n\nfunction findRevokedEntry(entries: KnownHostEntry[], key: Buffer): KnownHostEntry | undefined {\n return entries.find(\n (entry) =>\n entry.marker === \"@revoked\" &&\n entry.key.length === key.length &&\n timingSafeEqual(entry.key, key)\n )\n}\n\nfunction throwHostKeyMismatch(host: string, presentedKey: Buffer, existingKey?: Buffer): never {\n // R-0000210: both buffers can be untrusted (presented key from a remote\n // peer, existing key from a possibly-tampered known_hosts entry). Use the\n // non-throwing variant so a malformed wire format surfaces as a clean\n // HostKeyVerificationError instead of a RangeError.\n const presentedAlgo = describeAlgoForDiagnostics(presentedKey)\n const knownHostsDetails =\n existingKey == null\n ? \"remote host key does not match the key in known_hosts. \"\n : `remote host key (${presentedAlgo}) does not match the key in known_hosts (${describeAlgoForDiagnostics(existingKey)}). `\n throw new HostKeyVerificationError(\n `HOST KEY VERIFICATION FAILED for ${host}: ${knownHostsDetails}` +\n \"This could indicate a man-in-the-middle attack.\"\n )\n}\n\nfunction verifyHostKeyAgainstKnownEntries(parameters: {\n cachedKey: Buffer | null\n fileEntries: KnownHostEntry[]\n host: string\n key: Buffer\n}): boolean {\n const { cachedKey, fileEntries, host, key } = parameters\n const revokedKey = findRevokedEntry(fileEntries, key)\n if (revokedKey != null) {\n // R-0000210: the matched entry came from disk and may have a malformed\n // wire-format buffer; fall back to \"<unknown>\" rather than letting a\n // RangeError escape past HostKeyVerificationError.\n throw new HostKeyVerificationError(\n `HOST KEY VERIFICATION FAILED for ${host}: remote host key (${describeAlgoForDiagnostics(revokedKey.key)}) is marked as revoked in known_hosts.`\n )\n }\n // R-0000204: paratix does not implement `@cert-authority` validation.\n // Refuse rather than silently append a duplicate raw-key entry next to\n // the CA line via accept-new.\n if (fileEntries.some((entry) => entry.marker === \"@cert-authority\"))\n throw new HostKeyVerificationError(`${host}: ${CERT_AUTHORITY_MESSAGE}`)\n\n const rawHostKeyEntries = fileEntries.filter((entry) => entry.marker == null)\n const matchingEntry = rawHostKeyEntries.find(\n (entry) => entry.key.length === key.length && timingSafeEqual(entry.key, key)\n )\n if (matchingEntry != null) return true\n if (cachedKey?.length === key.length && timingSafeEqual(cachedKey, key)) {\n return true\n }\n\n const firstRawHostKey = rawHostKeyEntries.at(0)?.key\n if (firstRawHostKey != null) throwHostKeyMismatch(host, key, firstRawHostKey)\n if (cachedKey != null) throwHostKeyMismatch(host, key, cachedKey)\n return false\n}\n\nexport function lookupHostKey(\n entries: KnownHostEntry[],\n host: string,\n port: number\n): Buffer | null {\n return lookupHostEntry(entries, host, port)?.key ?? null\n}\n\n/**\n * Extract the algorithm name from an SSH public key in wire format.\n *\n * The SSH wire format starts with a `uint32` length prefix followed by the\n * algorithm name as an ASCII string.\n *\n * R-0000834: the extracted algorithm is validated against the same allowlist\n * used for pinned host keys ({@link PINNED_HOST_KEY_ALGORITHM_PATTERN}). A\n * remote peer could otherwise smuggle arbitrary bytes (including whitespace\n * or control characters) into the `known_hosts` line we persist via\n * {@link appendHostKey}, since the algorithm field would be rewritten verbatim\n * onto disk.\n *\n * @param keyBuffer - The raw public key buffer.\n * @returns The algorithm name (e.g. `\"ssh-ed25519\"`).\n */\nexport function extractAlgoFromKey(keyBuffer: Buffer): string {\n if (keyBuffer.length < UINT32_SIZE) {\n throw new Error(\"Invalid SSH key buffer: too short to contain algorithm length\")\n }\n const algoLength = keyBuffer.readUInt32BE(0)\n if (algoLength === 0 || UINT32_SIZE + algoLength > keyBuffer.length) {\n throw new Error(\"Invalid SSH key buffer: algorithm length exceeds buffer size\")\n }\n const algo = keyBuffer.subarray(UINT32_SIZE, UINT32_SIZE + algoLength).toString(\"ascii\")\n // R-0000834: refuse remote-supplied algorithm names that fail the\n // allowlist. Without this, a malicious peer could embed newlines or\n // whitespace into the field and corrupt the known_hosts line shape.\n if (!PINNED_HOST_KEY_ALGORITHM_PATTERN.test(algo)) {\n throw new KnownHostsValidationError(`Invalid SSH key buffer: unsupported algorithm '${algo}'`)\n }\n return algo\n}\n\n/**\n * R-0000210: non-throwing variant of {@link extractAlgoFromKey} for\n * untrusted input (corrupt revoked entries, presented host keys from a\n * malicious peer). Callers that only want the algorithm for diagnostics\n * use this and fall back to a placeholder so a malformed buffer surfaces\n * as a {@link HostKeyVerificationError} rather than a generic `RangeError`.\n *\n * @param keyBuffer - The raw public key buffer (possibly malformed).\n * @returns The algorithm name, or `\"<unknown>\"` when the buffer cannot be parsed.\n */\nfunction describeAlgoForDiagnostics(keyBuffer: Buffer): string {\n try {\n return extractAlgoFromKey(keyBuffer)\n } catch {\n return \"<unknown>\"\n }\n}\n\n/**\n * Compute the SHA256 fingerprint of an SSH public key in OpenSSH format.\n *\n * The result matches the fingerprint shown by `ssh-keygen -l`, e.g.\n * `SHA256:AbCdEf...`. Trailing `=` padding characters are stripped from the\n * base64 digest to conform to the OpenSSH fingerprint representation.\n *\n * @param key - The raw public key buffer (SSH wire format).\n * @returns The fingerprint string prefixed with `SHA256:`.\n */\nexport function computeFingerprint(key: Buffer): string {\n const hash = createHash(\"sha256\").update(key).digest(\"base64\")\n // Remove trailing '=' padding to match OpenSSH format\n return `SHA256:${hash.replaceAll(\"=\", \"\")}`\n}\n\n/**\n * Append a new host key entry to `~/.ssh/known_hosts`.\n *\n * Creates the `~/.ssh` directory (mode `0o700`) and the file itself if they\n * do not exist yet.\n *\n * R-0000151 / R-0000194: the append is serialized through\n * {@link withKnownHostsLock} so concurrent invocations cannot interleave\n * partial writes. The same lock also fences the read path inside\n * {@link buildHostVerifier}, so a verifier scheduled after this call always\n * sees the appended line in full.\n *\n * @param host - The hostname or IP.\n * @param port - The SSH port.\n * @param keyBuffer - The raw public key buffer.\n */\nexport async function appendHostKey(host: string, port: number, keyBuffer: Buffer): Promise<void> {\n assertSafeHostForKnownHosts(host)\n const hostLabel = formatHostNeedle(host, port)\n const algo = extractAlgoFromKey(keyBuffer)\n const base64Key = keyBuffer.toString(\"base64\")\n\n // R-0000834: defence-in-depth — even though extractAlgoFromKey enforces the\n // allowlist and base64 encoding never emits whitespace, refuse to persist a\n // line whose fields contain any whitespace, newline, or control byte. A\n // malformed hostLabel (e.g. an IPv6 literal smuggled through configuration)\n // must not corrupt the known_hosts line shape.\n if (/\\s/v.test(hostLabel)) {\n throw new KnownHostsValidationError(\n `Refusing to persist known_hosts entry: host label contains whitespace`\n )\n }\n if (/\\s/v.test(algo)) {\n throw new KnownHostsValidationError(\n `Refusing to persist known_hosts entry: algorithm contains whitespace`\n )\n }\n if (/\\s/v.test(base64Key)) {\n throw new KnownHostsValidationError(\n `Refusing to persist known_hosts entry: base64 key contains whitespace`\n )\n }\n\n const line = `${hostLabel} ${algo} ${base64Key}\\n`\n\n const sshDirectory = join(homedir(), \".ssh\")\n const filePath = join(sshDirectory, \"known_hosts\")\n\n await withKnownHostsLock(async () => {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n await mkdir(sshDirectory, { mode: 0o700, recursive: true })\n // R-0000838: load existing entries while holding the lock and skip the\n // append when an identical (hostPattern, algorithm, key) triple is\n // already present. Without this guard, repeated TOFU acceptances over\n // the lifetime of a process append duplicate lines to known_hosts which\n // bloat the file and dilute later trust audits.\n const existingEntries = await loadKnownHostEntries()\n const matchingRawHostEntries = existingEntries.filter(\n (entry) => entry.marker == null && matchesKnownHostEntry(entry, hostLabel)\n )\n const alreadyTrusted = matchingRawHostEntries.some(\n (entry) =>\n entry.algo === algo &&\n entry.key.length === keyBuffer.length &&\n timingSafeEqual(entry.key, keyBuffer)\n )\n if (alreadyTrusted) return\n const conflictingEntry = matchingRawHostEntries.find(\n (entry) => entry.key.length !== keyBuffer.length || !timingSafeEqual(entry.key, keyBuffer)\n )\n if (conflictingEntry != null) throwHostKeyMismatch(host, keyBuffer, conflictingEntry.key)\n // R-0000793: create the file with restrictive 0600 permissions. The\n // historical 0644 mode mirrored the OpenSSH default that lets other\n // local users read the file, but Paratix pins host keys on behalf of\n // automated runs that may store sensitive operator hosts (jump boxes,\n // bastions). Aligning with `~/.ssh/`'s 0700 keeps the trust store\n // accessible only to the running user.\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n await appendFile(filePath, line, { mode: 0o600 })\n })\n}\n\nfunction getFileSystemErrorCode(error: unknown): string | undefined {\n return typeof error === \"object\" && error !== null && \"code\" in error\n ? String(error.code)\n : undefined\n}\n\n/**\n * R-0000845: cap for the on-disk size of `~/.ssh/known_hosts` (16 MiB).\n * A well-maintained trust store rarely exceeds a few hundred KiB; the cap\n * stops a runaway or hostile file from consuming process memory inside\n * `parseKnownHosts`.\n */\nconst KIBIBYTE_BYTES = 1024\nconst MEBIBYTE_BYTES = KIBIBYTE_BYTES * KIBIBYTE_BYTES\nconst KNOWN_HOSTS_FILE_LIMIT_MIB = 16\nconst KNOWN_HOSTS_FILE_BYTE_LIMIT = KNOWN_HOSTS_FILE_LIMIT_MIB * MEBIBYTE_BYTES\n\n/**\n * Read and parse `~/.ssh/known_hosts`.\n *\n * A missing file is treated as an empty trust store. Other read failures fail\n * closed so `accept-new` cannot bypass an unreadable existing trust anchor.\n *\n * R-0000845: switched from `readFileSync` to async {@link readFile} and added\n * a stat-based size cap. The previous sync read held the event loop for the\n * entire I/O latency of `~/.ssh/known_hosts` — fine for the typical few-KiB\n * file, but unbounded on NFS-mounted homes or pathologically large trust\n * stores.\n *\n * @returns The parsed entries.\n */\nasync function loadKnownHostEntries(): Promise<KnownHostEntry[]> {\n const filePath = join(homedir(), \".ssh\", \"known_hosts\")\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const stats = await stat(filePath)\n if (stats.size > KNOWN_HOSTS_FILE_BYTE_LIMIT) {\n throw new HostKeyVerificationError(\n `Refusing to read known_hosts at ${filePath}: size ${stats.size} bytes exceeds the ${KNOWN_HOSTS_FILE_BYTE_LIMIT}-byte cap`\n )\n }\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n return parseKnownHosts(await readFile(filePath, \"utf8\"))\n } catch (error) {\n if (error instanceof HostKeyVerificationError) throw error\n if (getFileSystemErrorCode(error) === \"ENOENT\") {\n return []\n }\n throw new HostKeyVerificationError(\n `Could not read known_hosts at ${filePath}: ${String(error)}`\n )\n }\n}\n\nfunction formatAcceptedHostKeyWarning(host: string, key: Buffer): string {\n try {\n const algo = extractAlgoFromKey(key)\n const fingerprint = computeFingerprint(key)\n return (\n `WARNING: Permanently added '${host}' (${algo}) to the list of known hosts. ` +\n `Fingerprint: ${fingerprint}\\n`\n )\n } catch {\n return `WARNING: Permanently added '${host}' to the list of known hosts.\\n`\n }\n}\n\nfunction writeHostKeyPersistFallbackWarning(host: string, port: number, error: unknown): void {\n const keyscanArguments =\n port === DEFAULT_SSH_PORT ? shellQuote(host) : `-p ${port} ${shellQuote(host)}`\n process.stderr.write(\n `WARNING: Could not persist host key for ${host} — ` +\n `the key is cached in memory for this session. ` +\n `To persist it, ensure ~/.ssh/ is writable or run: ` +\n `ssh-keyscan ${keyscanArguments} >> ~/.ssh/known_hosts. ` +\n `${String(error)}\\n`\n )\n}\n\n/**\n * Accept an unknown host key, warn to stderr, and persist it to `~/.ssh/known_hosts`.\n *\n * The key is immediately stored in the in-memory cache so subsequent connections\n * within the same process succeed even if the disk write fails. If writing to\n * disk fails, a recovery hint with the equivalent `ssh-keyscan` command is\n * printed to stderr.\n *\n * @param location - The target host and SSH port to persist the key for.\n * @param key - The raw public key buffer presented by the remote host.\n * @param cache - The per-connection in-memory host key cache to update.\n * @returns A promise that resolves once the key has been written to disk (or the write error has been handled).\n */\nasync function acceptAndPersistHostKey(\n location: HostLocation,\n key: Buffer,\n cache: HostKeyCache\n): Promise<void> {\n const { host, port } = location\n const acceptedWarning = formatAcceptedHostKeyWarning(host, key)\n try {\n await appendHostKey(host, port, key)\n } catch (error: unknown) {\n if (error instanceof HostKeyVerificationError || error instanceof KnownHostsValidationError) {\n throw error\n }\n cache.set(formatHostNeedle(host, port), key)\n process.stderr.write(acceptedWarning)\n writeHostKeyPersistFallbackWarning(host, port, error)\n return\n }\n cache.set(formatHostNeedle(host, port), key)\n process.stderr.write(acceptedWarning)\n}\n\n/**\n * R-0000205: allowlist of SSH host-key algorithms paratix recognises in\n * pinned public keys. `ecdsa-sha2-*` covers the three OpenSSH curves\n * (nistp256, nistp384, nistp521); anything outside this set is almost\n * certainly a typo (`ed25519` vs `ssh-ed25519`) or an unsupported algorithm\n * and is rejected up front instead of failing later with an opaque mismatch.\n */\nconst PINNED_HOST_KEY_ALGORITHM_PATTERN =\n /^(?:ssh-ed25519|ssh-rsa|ecdsa-sha2-(?:nistp256|nistp384|nistp521))$/v\n\nfunction normalizePinnedPublicKey(publicKey: string): string {\n const parts = publicKey.trim().split(/\\s+/v)\n if (parts.length < 2) {\n throw new Error(\"Expected host public key must use the format '<algorithm> <base64>'\")\n }\n const [algorithm, key] = parts\n // R-0000205: validate algorithm against the allowlist so typos like\n // `ed25519` (missing `ssh-` prefix) fail with an actionable error instead\n // of a misleading \"remote key does not match\" later.\n if (!PINNED_HOST_KEY_ALGORITHM_PATTERN.test(algorithm)) {\n throw new Error(\n `Expected host public key uses unsupported algorithm '${algorithm}'. ` +\n \"Supported algorithms: ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521.\"\n )\n }\n // R-0000205: validate base64 with the same strict alphabet used for\n // known_hosts entries so silent truncation by `Buffer.from(value, \"base64\")`\n // cannot mask a tampered or copy-pasted key.\n if (!STRICT_BASE64_PATTERN.test(key)) {\n throw new Error(\n \"Expected host public key contains invalid base64 in the key field. \" +\n \"Use the exact value from `ssh-keygen -y` or the second field of an OpenSSH known_hosts line.\"\n )\n }\n return `${algorithm} ${key}`\n}\n\nexport function validateExpectedHostPublicKey(publicKey: string): null | string {\n try {\n normalizePinnedPublicKey(publicKey)\n return null\n } catch (error) {\n return error instanceof Error ? error.message : String(error)\n }\n}\n\nfunction formatPresentedPublicKey(key: Buffer): null | string {\n // R-0000210: the presented key comes from the remote peer (untrusted).\n // Return null on a malformed wire format so verifyPinnedHostKey can fail\n // closed with a HostKeyVerificationError instead of a RangeError.\n try {\n return `${extractAlgoFromKey(key)} ${key.toString(\"base64\")}`\n } catch {\n return null\n }\n}\n\nfunction hasPinnedHostTrustAnchor(options?: HostVerifierOptions): boolean {\n return options?.expectedHostFingerprint != null || options?.expectedHostPublicKey != null\n}\n\nfunction verifyPinnedHostKey(host: string, key: Buffer, options: HostVerifierOptions): void {\n const normalizedExpectedPublicKey =\n options.expectedHostPublicKey == null\n ? null\n : normalizePinnedPublicKey(options.expectedHostPublicKey)\n const expectedFingerprint = options.expectedHostFingerprint ?? null\n const presentedPublicKey = formatPresentedPublicKey(key)\n const presentedFingerprint = computeFingerprint(key)\n const publicKeyMatches =\n normalizedExpectedPublicKey == null ||\n (presentedPublicKey != null && normalizedExpectedPublicKey === presentedPublicKey)\n const fingerprintMatches =\n expectedFingerprint == null || expectedFingerprint === presentedFingerprint\n\n // R-0000210: treat null (malformed wire format) as \"does not match\".\n if (publicKeyMatches && fingerprintMatches) {\n return\n }\n\n throw new HostKeyVerificationError(\n `HOST KEY VERIFICATION FAILED for ${host}: the remote host key does not match the configured trust anchor.`\n )\n}\n\n/**\n * Build the `hostVerifier` callback for an ssh2 `ConnectConfig`.\n *\n * Behavior by mode:\n * - `\"no\"` — returns an empty object (no verification, ssh2 default).\n * - `\"accept-new\"` — accepts unknown keys and appends them to `~/.ssh/known_hosts`;\n * throws if a known key does not match.\n * - `\"yes\"` — throws for both unknown keys and mismatched keys.\n *\n * R-0000194: the synchronous read of `~/.ssh/known_hosts` is funneled through\n * the same in-process mutex as writes, so a verifier built right after a\n * concurrent `appendHostKey` always observes the freshly persisted line and\n * cannot race with a partial write.\n *\n * @param mode - The host key verification strategy.\n * @param location - The target host and SSH port.\n * @param options - Optional pinned trust anchors and per-connection cache override.\n * @returns An object with `hostVerifier` set (or empty for mode `\"no\"`).\n * @throws {Error} When a known host key does not match the presented key (all modes except `\"no\"`).\n * @throws {Error} When no known_hosts entry exists for the host and mode is `\"yes\"`.\n */\nexport async function buildHostVerifier(\n mode: \"accept-new\" | \"no\" | \"yes\",\n location: HostLocation,\n options: HostVerifierOptions = {}\n): Promise<HostVerifierResult> {\n const cache = options.cache ?? defaultHostKeyCache\n const { host, port } = location\n if (mode === \"no\" && !hasPinnedHostTrustAnchor(options)) return {}\n\n const entries = await withKnownHostsLock(loadKnownHostEntries)\n const fileEntries = findMatchingEntries(entries, host, port)\n const cachedKey = cache.get(formatHostNeedle(host, port)) ?? null\n let acceptedHostKey: Buffer | null = null\n\n const result: { hostVerifier: (key: Buffer) => boolean } & HostVerifierResult = {\n async commitAcceptedHostKey(): Promise<void> {\n if (acceptedHostKey != null) await acceptAndPersistHostKey(location, acceptedHostKey, cache)\n },\n hostVerifier(key: Buffer): boolean {\n if (\n mode !== \"no\" &&\n verifyHostKeyAgainstKnownEntries({ cachedKey, fileEntries, host, key })\n ) {\n if (hasPinnedHostTrustAnchor(options)) verifyPinnedHostKey(host, key, options)\n return true\n }\n if (hasPinnedHostTrustAnchor(options)) {\n verifyPinnedHostKey(host, key, options)\n return true\n }\n if (mode === \"yes\") {\n throw new HostKeyVerificationError(\n `Host key for ${host} not found in known_hosts. ` +\n 'Set strictHostKeyChecking to \"accept-new\" for explicit TOFU or configure ssh.expectedHostFingerprint / ssh.expectedHostPublicKey.'\n )\n }\n if (mode === \"no\") return true\n // mode === \"accept-new\": accept this key for this handshake, but only\n // commit TOFU trust after ssh2 reports the connection as ready.\n acceptedHostKey = Buffer.from(key)\n return true\n },\n }\n return result\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\"\n\nconst HASHED_HOST_PARTS = 4\n\ntype HostPatternMatchState = {\n needleIndex: number\n patternIndex: number\n starIndex: number\n starNeedleIndex: number\n}\n\nexport function matchesKnownHostPatternList(patterns: string[], needle: string): boolean {\n let matchedPositivePattern = false\n for (const rawPattern of patterns) {\n const negated = rawPattern.startsWith(\"!\")\n const pattern = negated ? rawPattern.slice(1) : rawPattern\n if (pattern.length === 0) continue\n const matched = matchesHashedHost(pattern, needle) || matchesPlainHostPattern(pattern, needle)\n if (!matched) continue\n if (negated) return false\n matchedPositivePattern = true\n }\n return matchedPositivePattern\n}\n\nfunction matchesPlainHostPattern(pattern: string, needle: string): boolean {\n if (pattern.startsWith(\"|1|\")) return false\n return matchesHostPattern(pattern.toLowerCase(), needle.toLowerCase())\n}\n\nfunction matchesHashedHost(pattern: string, needle: string): boolean {\n if (!pattern.startsWith(\"|1|\")) return false\n\n const parts = pattern.split(\"|\")\n if (parts.length !== HASHED_HOST_PARTS || parts[1] !== \"1\") return false\n\n const salt = Buffer.from(parts[2] ?? \"\", \"base64\")\n const expectedHash = Buffer.from(parts[3] ?? \"\", \"base64\")\n if (salt.length === 0 || expectedHash.length === 0) return false\n\n const actualHash = createHmac(\"sha1\", salt).update(needle).digest()\n return actualHash.length === expectedHash.length && timingSafeEqual(actualHash, expectedHash)\n}\n\nfunction matchesHostPattern(pattern: string, needle: string): boolean {\n let state = { needleIndex: 0, patternIndex: 0, starIndex: -1, starNeedleIndex: 0 }\n\n while (state.needleIndex < needle.length) {\n const nextState = advanceHostPatternMatch(pattern, needle, state)\n if (nextState === null) return false\n state = nextState\n }\n\n while (pattern[state.patternIndex] === \"*\") state.patternIndex += 1\n return state.patternIndex === pattern.length\n}\n\nfunction advanceHostPatternMatch(\n pattern: string,\n needle: string,\n state: HostPatternMatchState\n): HostPatternMatchState | null {\n const patternCharacter = pattern[state.patternIndex]\n if (patternCharacter === \"?\" || patternCharacter === needle[state.needleIndex]) {\n return { ...state, needleIndex: state.needleIndex + 1, patternIndex: state.patternIndex + 1 }\n }\n if (patternCharacter === \"*\") {\n return {\n ...state,\n patternIndex: state.patternIndex + 1,\n starIndex: state.patternIndex,\n starNeedleIndex: state.needleIndex,\n }\n }\n if (state.starIndex === -1) return null\n const starNeedleIndex = state.starNeedleIndex + 1\n return {\n ...state,\n needleIndex: starNeedleIndex,\n patternIndex: state.starIndex + 1,\n starNeedleIndex,\n }\n}\n","import type { SshConfig } from \"./types.js\"\n\nimport { validateExpectedHostPublicKey } from \"./knownHosts.js\"\n\nconst STRICT_HOST_KEY_ERROR = `Invalid property 'ssh.strictHostKeyChecking' (expected \"accept-new\", \"no\", or \"yes\")`\nconst MAX_TCP_PORT = 65_535\n\ntype NumberValidationOptions = {\n integer?: boolean\n positive?: boolean\n}\n\nfunction describeType(value: unknown): string {\n return value === null ? \"null\" : typeof value\n}\n\nfunction isHostKeyMode(value: string): boolean {\n return value === \"accept-new\" || value === \"no\" || value === \"yes\"\n}\n\nfunction isRecord(value: object): value is Record<string, unknown> {\n return Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null\n}\n\nfunction collectOptionalBooleanErrors(\n object: Record<string, unknown>,\n key: string,\n errors: string[]\n): void {\n if (!(key in object) || object[key] == null) return\n if (typeof object[key] !== \"boolean\") {\n errors.push(\n `Invalid property 'ssh.${key}' (expected boolean, got ${describeType(object[key])})`\n )\n }\n}\n\nfunction collectOptionalNumberErrors(parameters: {\n errors: string[]\n key: string\n object: Record<string, unknown>\n options?: NumberValidationOptions\n}): void {\n const { errors, key, object, options } = parameters\n if (!(key in object) || object[key] == null) return\n const value = object[key]\n const validatedNumber = validateNumberValue(value)\n if (validatedNumber == null) {\n errors.push(`Invalid property 'ssh.${key}' (expected number, got ${describeType(value)})`)\n return\n }\n if (options?.integer === true && !Number.isInteger(validatedNumber)) {\n errors.push(`Property 'ssh.${key}' must be an integer`)\n return\n }\n if (options?.positive === true && validatedNumber <= 0) {\n errors.push(`Property 'ssh.${key}' must be greater than 0`)\n }\n}\n\nfunction validateNumberValue(value: unknown): null | number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null\n}\n\nexport function isValidTcpPort(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 1 && value <= MAX_TCP_PORT\n}\n\nfunction collectOptionalStringErrors(\n object: Record<string, unknown>,\n key: string,\n errors: string[]\n): void {\n if (!(key in object) || object[key] == null) return\n if (typeof object[key] !== \"string\") {\n errors.push(`Invalid property 'ssh.${key}' (expected string, got ${describeType(object[key])})`)\n return\n }\n if (object[key].length === 0) {\n errors.push(`Property 'ssh.${key}' must not be an empty string`)\n }\n}\n\nfunction collectPortsErrors(ssh: Record<string, unknown>, errors: string[]): void {\n if (!(\"ports\" in ssh)) {\n errors.push(\"Missing property 'ssh.ports' (expected array)\")\n return\n }\n if (!Array.isArray(ssh.ports)) {\n errors.push(`Invalid property 'ssh.ports' (expected array, got ${describeType(ssh.ports)})`)\n return\n }\n if (ssh.ports.length === 0) {\n errors.push(\"Property 'ssh.ports' must not be empty\")\n return\n }\n for (const [index, port] of ssh.ports.entries()) {\n if (!isValidTcpPort(port)) {\n errors.push(`Property 'ssh.ports[${index}]' must be an integer between 1 and 65535`)\n }\n }\n}\n\nfunction collectRequiredUserErrors(ssh: Record<string, unknown>, errors: string[]): void {\n if (!(\"user\" in ssh)) {\n errors.push(\"Missing property 'ssh.user' (expected string)\")\n return\n }\n if (typeof ssh.user !== \"string\") {\n errors.push(`Invalid property 'ssh.user' (expected string, got ${describeType(ssh.user)})`)\n return\n }\n if (ssh.user.length === 0) {\n errors.push(\"Property 'ssh.user' must not be an empty string\")\n }\n}\n\nfunction collectStrictHostKeyCheckingErrors(ssh: Record<string, unknown>, errors: string[]): void {\n if (!(\"strictHostKeyChecking\" in ssh) || ssh.strictHostKeyChecking == null) return\n if (typeof ssh.strictHostKeyChecking !== \"string\" || !isHostKeyMode(ssh.strictHostKeyChecking)) {\n errors.push(STRICT_HOST_KEY_ERROR)\n }\n}\n\nfunction collectExpectedHostPublicKeyErrors(ssh: Record<string, unknown>, errors: string[]): void {\n if (typeof ssh.expectedHostPublicKey !== \"string\") return\n const error = validateExpectedHostPublicKey(ssh.expectedHostPublicKey)\n if (error != null) errors.push(`Invalid property 'ssh.expectedHostPublicKey': ${error}`)\n}\n\nfunction collectOptionalSshFieldErrors(ssh: Record<string, unknown>, errors: string[]): void {\n collectOptionalStringErrors(ssh, \"privateKey\", errors)\n collectOptionalStringErrors(ssh, \"sudoPassword\", errors)\n collectOptionalStringErrors(ssh, \"expectedHostFingerprint\", errors)\n collectOptionalStringErrors(ssh, \"expectedHostPublicKey\", errors)\n collectExpectedHostPublicKeyErrors(ssh, errors)\n collectOptionalBooleanErrors(ssh, \"agentForward\", errors)\n collectOptionalBooleanErrors(ssh, \"passwordFallback\", errors)\n collectOptionalNumberErrors({\n errors,\n key: \"reconnectTimeout\",\n object: ssh,\n options: { positive: true },\n })\n collectOptionalNumberErrors({\n errors,\n key: \"maxReconnectAttempts\",\n object: ssh,\n options: { integer: true, positive: true },\n })\n collectStrictHostKeyCheckingErrors(ssh, errors)\n}\n\nexport function collectSshConfigErrors(value: unknown): string[] {\n const errors: string[] = []\n if (value === null) {\n errors.push(\"Invalid property 'ssh' (expected object, got null)\")\n return errors\n }\n if (typeof value !== \"object\") {\n errors.push(`Invalid property 'ssh' (expected object, got ${describeType(value)})`)\n return errors\n }\n if (!isRecord(value)) {\n errors.push(\"Invalid property 'ssh' (expected object, got object)\")\n return errors\n }\n const ssh = value\n collectPortsErrors(ssh, errors)\n collectRequiredUserErrors(ssh, errors)\n collectOptionalSshFieldErrors(ssh, errors)\n return errors\n}\n\nfunction normalizeServerDefinitionSshError(error: string): string {\n const mappedErrors: Record<string, string> = {\n \"Missing property 'ssh.user' (expected string)\": \"ssh.user is required\",\n \"Property 'ssh.expectedHostFingerprint' must not be an empty string\":\n \"ssh.expectedHostFingerprint must not be an empty string\",\n \"Property 'ssh.expectedHostPublicKey' must not be an empty string\":\n \"ssh.expectedHostPublicKey must not be an empty string\",\n \"Property 'ssh.ports' must not be empty\": \"ssh.ports must not be empty\",\n \"Property 'ssh.privateKey' must not be an empty string\":\n \"ssh.privateKey must not be an empty string\",\n \"Property 'ssh.user' must not be an empty string\": \"ssh.user is required\",\n [STRICT_HOST_KEY_ERROR]: \"ssh.strictHostKeyChecking must be one of accept-new, no, yes\",\n }\n return mappedErrors[error] ?? error\n}\n\nexport function validateSshConfig(ssh: SshConfig): void {\n const errors = collectSshConfigErrors(ssh)\n if (errors.length === 0) return\n throw new Error(`ServerDefinition: ${normalizeServerDefinitionSshError(errors[0])}`)\n}\n","import type {\n Environment,\n EnvironmentMetaEntry,\n MetaEnvironmentValue,\n ModuleMetaEntry,\n SshdPortMetaEntry,\n SystemHostMetaEntry,\n SystemRebootMetaEntry,\n} from \"./types.js\"\n\nimport { createNullPrototypeEnvironment, ENVIRONMENT_FORBIDDEN_KEYS } from \"./environment.js\"\nimport { describeHostValidationFailure, validateHostLabel } from \"./hostValidation.js\"\nimport { isValidTcpPort } from \"./serverDefinitionValidation.js\"\n\nconst SYSTEM_HOST_KIND = \"system.host\"\nconst SYSTEM_REBOOT_KIND = \"system.reboot\"\ntype MetaValueType = \"boolean\" | \"number\" | \"string\"\n\nexport type BooleanEnvironmentMetaEntry = { valueType: \"boolean\" } & EnvironmentMetaEntry\nexport type LazyEnvironmentMetaEntry = EnvironmentMetaEntry\nexport type NumberEnvironmentMetaEntry = { valueType: \"number\" } & EnvironmentMetaEntry\nexport type StringEnvironmentMetaEntry = { valueType: \"string\" } & EnvironmentMetaEntry\n\nfunction hasValidMetaName(name: unknown): name is string {\n return typeof name === \"string\" && name.length > 0\n}\n\nfunction inferMetaValueType(\n value: MetaEnvironmentValue,\n explicitValueType?: MetaValueType\n): MetaValueType {\n if (explicitValueType != null) return explicitValueType\n if (typeof value === \"boolean\") return \"boolean\"\n if (typeof value === \"number\") return \"number\"\n return \"string\"\n}\n\nfunction isMetaValueType(value: string): value is MetaValueType {\n return value === \"boolean\" || value === \"number\" || value === \"string\"\n}\n\nfunction validateResolvedMetaValue(parameters: {\n actualType: string\n declaredValueType: MetaValueType\n enforceDeclaredValueType: boolean\n entryKey: string\n}): void {\n const { actualType, declaredValueType, enforceDeclaredValueType, entryKey } = parameters\n if (!isMetaValueType(actualType)) {\n throw new TypeError(\n `Env meta entry ${JSON.stringify(entryKey)} resolved to typeof ${actualType}, expected boolean, number, or string`\n )\n }\n if (enforceDeclaredValueType && actualType !== declaredValueType) {\n throw new TypeError(\n `Env meta entry ${JSON.stringify(entryKey)} resolved to typeof ${actualType}, expected ${declaredValueType}`\n )\n }\n}\n\nfunction normalizeMetaValueResolver(\n value: MetaEnvironmentValue\n): () => Promise<boolean | number | string> {\n if (typeof value === \"function\") {\n return async () => {\n await Promise.resolve()\n return value()\n }\n }\n return async () => {\n await Promise.resolve()\n return value\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null\n}\n\nfunction assertValidEnvironmentMetaEntry(candidate: Record<string, unknown>): void {\n if (!hasValidMetaName(candidate.name)) {\n throw new TypeError(\"Invalid env meta entry: name must be a non-empty string\")\n }\n\n if (typeof candidate.resolve !== \"function\") {\n throw new TypeError(\"Invalid env meta entry: resolve must be a function returning a Promise\")\n }\n\n if (\n candidate.valueType !== \"boolean\" &&\n candidate.valueType !== \"number\" &&\n candidate.valueType !== \"string\"\n ) {\n throw new TypeError(\"Invalid env meta entry: valueType must be boolean, number, or string\")\n }\n}\n\nfunction assertValidSshdPortMetaEntry(candidate: Record<string, unknown>): void {\n if (!isValidTcpPort(candidate.port)) {\n throw new TypeError(\"Invalid sshd.port meta entry: port must be an integer between 1 and 65535\")\n }\n}\n\nfunction assertValidSystemHostMetaEntry(candidate: Record<string, unknown>): void {\n const hostValidationFailure = validateHostLabel(candidate.host)\n if (hostValidationFailure != null) {\n throw new TypeError(\n `Invalid system.host meta entry: host ${describeHostValidationFailure(hostValidationFailure)}`\n )\n }\n}\n\nexport function environmentMeta(\n name: string,\n value: MetaEnvironmentValue,\n valueType?: MetaValueType\n): EnvironmentMetaEntry {\n if (!hasValidMetaName(name)) {\n throw new TypeError(\"Meta env entry name must be a non-empty string\")\n }\n return {\n kind: \"env\",\n name,\n resolve: normalizeMetaValueResolver(value),\n valueType: inferMetaValueType(value, valueType),\n valueTypeExplicit: valueType != null,\n }\n}\n\nexport function sshdPortMeta(port: number): SshdPortMetaEntry {\n if (!isValidTcpPort(port)) {\n throw new TypeError(\"Meta entry sshd.port requires an integer port between 1 and 65535\")\n }\n return { kind: \"sshd.port\", port }\n}\n\nexport function systemHostMeta(host: string): SystemHostMetaEntry {\n const hostValidationFailure = validateHostLabel(host)\n if (hostValidationFailure != null) {\n throw new TypeError(\n `Meta entry system.host invalid host: ${describeHostValidationFailure(hostValidationFailure)}`\n )\n }\n return { host, kind: SYSTEM_HOST_KIND }\n}\n\nexport function systemRebootMeta(): SystemRebootMetaEntry {\n return { kind: SYSTEM_REBOOT_KIND }\n}\n\nexport const meta = {\n env: environmentMeta,\n sshdPort: sshdPortMeta,\n systemHost: systemHostMeta,\n systemReboot: systemRebootMeta,\n} as const\n\nexport function isEnvironmentMetaEntry(entry: ModuleMetaEntry): entry is EnvironmentMetaEntry {\n return entry.kind === \"env\"\n}\n\nexport function isStringEnvironmentMetaEntry(\n entry: ModuleMetaEntry\n): entry is StringEnvironmentMetaEntry {\n return isEnvironmentMetaEntry(entry) && entry.valueType === \"string\"\n}\n\nexport function isNumberEnvironmentMetaEntry(\n entry: ModuleMetaEntry\n): entry is NumberEnvironmentMetaEntry {\n return isEnvironmentMetaEntry(entry) && entry.valueType === \"number\"\n}\n\nexport function isBooleanEnvironmentMetaEntry(\n entry: ModuleMetaEntry\n): entry is BooleanEnvironmentMetaEntry {\n return isEnvironmentMetaEntry(entry) && entry.valueType === \"boolean\"\n}\n\nexport function isLazyEnvironmentMetaEntry(\n entry: ModuleMetaEntry\n): entry is LazyEnvironmentMetaEntry {\n return isEnvironmentMetaEntry(entry)\n}\n\nexport function isSshdPortMetaEntry(entry: ModuleMetaEntry): entry is SshdPortMetaEntry {\n return entry.kind === \"sshd.port\"\n}\n\nexport function isSystemHostMetaEntry(entry: ModuleMetaEntry): entry is SystemHostMetaEntry {\n return entry.kind === SYSTEM_HOST_KIND\n}\n\nexport function isSystemRebootMetaEntry(entry: ModuleMetaEntry): entry is SystemRebootMetaEntry {\n return entry.kind === SYSTEM_REBOOT_KIND\n}\n\nexport function assertValidModuleMetaEntry(entry: unknown): asserts entry is ModuleMetaEntry {\n if (!isRecord(entry)) {\n throw new TypeError(`Invalid meta entry: expected object, got ${typeof entry}`)\n }\n\n switch (entry.kind) {\n case \"env\": {\n assertValidEnvironmentMetaEntry(entry)\n return\n }\n case \"sshd.port\": {\n assertValidSshdPortMetaEntry(entry)\n return\n }\n case SYSTEM_HOST_KIND: {\n assertValidSystemHostMetaEntry(entry)\n return\n }\n case SYSTEM_REBOOT_KIND: {\n return\n }\n default: {\n throw new TypeError(`Invalid meta entry kind: ${String(entry.kind)}`)\n }\n }\n}\n\nexport function assertValidModuleMetaEntries(entries: ModuleMetaEntry[] | undefined): void {\n if (entries == null) return\n for (const entry of entries) {\n assertValidModuleMetaEntry(entry)\n }\n}\n\n/**\n * R-0000745: meta env names follow the same allow-list that\n * `loadDotEnvironment` and `cli.collectEnvironment` apply for dotenv keys,\n * extended to permit dot-separated namespaces (`system.arch`,\n * `system.os.codename`, `sshd.port`) that the built-in modules use to group\n * related facts. The first segment must still start with a letter or\n * underscore, every dot must be followed by a non-empty segment, and no\n * other punctuation is accepted. This rejects values such as `\"foo bar\"`,\n * `\"123abc\"`, `\"bad-name\"`, or the empty string at the merge boundary\n * instead of letting them slip into the resolved environment.\n */\n// Validate the meta name segment-by-segment with explicit string operations\n// so the regex engine never sees a nested quantifier (`(\\w*)*`). The check\n// is linear in the name length and matches the documented pattern\n// `[A-Za-z_]\\w*(\\.[A-Za-z_]\\w*)*` without exposing the lint to any\n// backtracking ambiguity.\nconst META_ENV_SEGMENT_PATTERN = /^[A-Za-z_]\\w*$/v\n\nfunction isAllowedMetaEnvironmentName(name: string): boolean {\n if (name.length === 0) return false\n const segments = name.split(\".\")\n for (const segment of segments) {\n if (!META_ENV_SEGMENT_PATTERN.test(segment)) return false\n }\n return true\n}\n\nfunction assertAllowedEnvironmentMetaName(name: string): void {\n if (!isAllowedMetaEnvironmentName(name)) {\n throw new Error(\n `Invalid env meta name ${JSON.stringify(name)}: expected [A-Za-z_]\\\\w*(\\\\.[A-Za-z_]\\\\w*)*`\n )\n }\n if (ENVIRONMENT_FORBIDDEN_KEYS.has(name)) {\n throw new Error(\n `Forbidden env meta entry name: ${JSON.stringify(name)} (reserved JavaScript identifier)`\n )\n }\n}\n\nfunction createMemoizedEnvironmentResolver(\n entry: EnvironmentMetaEntry\n): () => Promise<boolean | number | string> {\n // R-0000206: memoize the resolved promise per entry so security-sensitive\n // providers (e.g. 1Password CLI) are not re-invoked for every consumer\n // of the same env key. Multiple modules reading the same key now share\n // a single round-trip. A rejection clears the cache so a later access\n // can retry once the underlying issue is resolved. Callers that need\n // fresh values on every access (one-time passwords) must register a\n // new meta entry for each access instead of reusing one.\n let cachedResolution: null | Promise<boolean | number | string> = null\n const declaredValueType = entry.valueType\n const enforceDeclaredValueType = entry.valueTypeExplicit !== false\n const entryKey = entry.name\n return async (): Promise<boolean | number | string> => {\n if (cachedResolution != null) return cachedResolution\n // R-0000264: validate the resolved value against the entry's declared\n // valueType so a provider that drifts (e.g. starts returning a number\n // for a \"string\"-typed key, or a null/object) fails fast at the\n // boundary instead of leaking an untyped value into resolveEnvironment\n // and downstream consumers. The error must not poison the cache so a\n // subsequent retry (after the provider is fixed) can succeed.\n const pending = entry\n .resolve()\n .then((resolved): boolean | number | string => {\n validateResolvedMetaValue({\n actualType: typeof resolved,\n declaredValueType,\n enforceDeclaredValueType,\n entryKey,\n })\n return resolved\n })\n .catch((error: unknown) => {\n cachedResolution = null\n throw error\n })\n cachedResolution = pending\n return pending\n }\n}\n\nexport async function mergeEnvironmentFromMeta(\n environment: Environment,\n entries: ModuleMetaEntry[] | undefined\n): Promise<Environment> {\n if (entries == null || entries.length === 0) {\n await Promise.resolve()\n return environment\n }\n\n // R-0000074: preserve the null-prototype guarantee that R-0000070\n // introduced in mergeEnvironment by routing through\n // createNullPrototypeEnvironment instead of `{ ...environment }`, and\n // explicitly reject reserved property names that could leak into\n // prototype semantics on a plain object.\n const nextEnvironment = Object.assign(createNullPrototypeEnvironment(), environment)\n for (const entry of entries) {\n if (!isEnvironmentMetaEntry(entry)) continue\n assertAllowedEnvironmentMetaName(entry.name)\n nextEnvironment[entry.name] = createMemoizedEnvironmentResolver(entry)\n }\n await Promise.resolve()\n return nextEnvironment\n}\n\nexport function environmentToMetaEntries(environment: Environment): ModuleMetaEntry[] {\n return Object.entries(environment).map(([name, value]) => environmentMeta(name, value))\n}\n\nexport function diffEnvironmentToMetaEntries(\n original: Environment,\n current: Environment\n): ModuleMetaEntry[] | undefined {\n const entries: ModuleMetaEntry[] = []\n for (const key of Object.keys(current)) {\n if (!(key in original) || current[key] !== original[key]) {\n entries.push(environmentMeta(key, current[key]))\n }\n }\n return entries.length === 0 ? undefined : entries\n}\n","import type { RecipeModule } from \"./recipe.js\"\nimport type { Environment, ModuleMetaEntry, ModuleStatus, SshConnection } from \"./types.js\"\n\nimport { shouldExecuteApplyDuringDryRun } from \"./dryRunDispatch.js\"\nimport { mergeEnvironmentFromMeta } from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n startModuleSpinner,\n withRecipeOutputScope,\n} from \"./output.js\"\n\ntype StepResult = {\n env: Environment\n meta?: ModuleMetaEntry[]\n shouldBreak: boolean\n status?: ModuleStatus\n stopRun?: true\n}\n\nfunction interruptedDryRunResult(parameters: {\n aggregatedMeta: ModuleMetaEntry[]\n aggregatedStatus: \"changed\" | \"ok\"\n currentEnvironment: Environment\n}): StepResult {\n return {\n env: parameters.currentEnvironment,\n meta: parameters.aggregatedMeta.length === 0 ? undefined : parameters.aggregatedMeta,\n shouldBreak: true,\n status: parameters.aggregatedStatus === \"changed\" ? \"changed\" : undefined,\n }\n}\n\nasync function executeDryRunBlockingModule(parameters: {\n childModule: RecipeModule[\"_modules\"][number]\n connection: null | SshConnection\n diff: boolean\n environment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n verbose: boolean\n}): Promise<StepResult> {\n const { childModule, connection, diff, environment, verbose } = parameters\n if (parameters.shutdownSignal() != null) return { env: environment, shouldBreak: true }\n startModuleSpinner(childModule.name)\n const result =\n childModule._applyDryRun == null\n ? await childModule.apply(connection, environment)\n : await childModule._applyDryRun(connection, environment, {\n shutdownSignal: parameters.shutdownSignal,\n })\n const nextEnvironment =\n result.meta == null ? environment : await mergeEnvironmentFromMeta(environment, result.meta)\n const diffOutput = diff ? result.diff : undefined\n printModuleResult(\n childModule.name,\n result.status,\n result._dryRunDetail ?? \"(dry-run)\",\n diffOutput\n )\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n return {\n env: nextEnvironment,\n meta: result.meta,\n shouldBreak: result.status === \"failed\" || result._stopRun === true,\n status: result.status,\n stopRun: result._stopRun,\n }\n}\n\nasync function executeDryRunChildModule(parameters: {\n childModule: RecipeModule[\"_modules\"][number]\n diff: boolean\n environment: Environment\n shutdownSignal: () => NodeJS.Signals | null\n ssh: null | SshConnection\n verbose: boolean\n}): Promise<StepResult> {\n const { childModule, diff, environment, ssh, verbose } = parameters\n if (parameters.shutdownSignal() != null) return { env: environment, shouldBreak: true }\n const connection = childModule.local === true ? null : ssh\n startModuleSpinner(childModule.name)\n const checkResult = await childModule.check(connection, environment)\n if (parameters.shutdownSignal() != null) return { env: environment, shouldBreak: true }\n if (checkResult !== \"ok\" && shouldExecuteApplyDuringDryRun(childModule, diff)) {\n return executeDryRunBlockingModule({\n childModule,\n connection,\n diff,\n environment,\n shutdownSignal: parameters.shutdownSignal,\n verbose,\n })\n }\n const status = checkResult === \"ok\" ? \"ok\" : \"changed\"\n const suffix = checkResult === \"ok\" ? undefined : \"(dry-run)\"\n printModuleResult(childModule.name, status, suffix)\n return { env: environment, shouldBreak: false, status }\n}\n\ntype DryRunRecipeAccumulator = {\n aggregatedMeta: ModuleMetaEntry[]\n aggregatedStatus: \"changed\" | \"ok\"\n currentEnvironment: Environment\n}\n\nfunction applyDryRunChildResult(\n accumulator: DryRunRecipeAccumulator,\n result: StepResult\n): DryRunRecipeAccumulator {\n return {\n aggregatedMeta:\n result.meta == null\n ? accumulator.aggregatedMeta\n : [...accumulator.aggregatedMeta, ...result.meta],\n aggregatedStatus: result.status === \"changed\" ? \"changed\" : accumulator.aggregatedStatus,\n currentEnvironment: result.env,\n }\n}\n\nasync function runDryRunChildLoop(parameters: {\n accumulator: DryRunRecipeAccumulator\n diff: boolean\n recipeModule: RecipeModule\n shutdownSignal: () => NodeJS.Signals | null\n ssh: null | SshConnection\n verbose: boolean\n}): Promise<DryRunRecipeAccumulator | StepResult> {\n let accumulator = parameters.accumulator\n for (const childModule of parameters.recipeModule._modules) {\n if (parameters.shutdownSignal() != null) {\n return interruptedDryRunResult({\n aggregatedMeta: accumulator.aggregatedMeta,\n aggregatedStatus: accumulator.aggregatedStatus,\n currentEnvironment: accumulator.currentEnvironment,\n })\n }\n // eslint-disable-next-line no-await-in-loop\n const result = await executeDryRunChildModule({\n childModule,\n diff: parameters.diff,\n environment: accumulator.currentEnvironment,\n shutdownSignal: parameters.shutdownSignal,\n ssh: parameters.ssh,\n verbose: parameters.verbose,\n })\n if (result.shouldBreak) return result\n accumulator = applyDryRunChildResult(accumulator, result)\n }\n return accumulator\n}\n\nexport async function dryRunRecipeModule(parameters: {\n environment: Environment\n options?: {\n diff?: boolean\n verbose?: boolean\n }\n recipeModule: RecipeModule\n shutdownSignal?: () => NodeJS.Signals | null\n ssh: null | SshConnection\n}): Promise<StepResult> {\n return withRecipeOutputScope(async () => {\n const { environment, recipeModule, ssh } = parameters\n printRecipeHeader(recipeModule.name)\n const shutdownSignal = parameters.shutdownSignal ?? (() => null)\n const loopResult = await runDryRunChildLoop({\n accumulator: {\n aggregatedMeta: [],\n aggregatedStatus: \"ok\",\n currentEnvironment: environment,\n },\n diff: parameters.options?.diff ?? false,\n recipeModule,\n shutdownSignal,\n ssh,\n verbose: parameters.options?.verbose ?? false,\n })\n if (\"shouldBreak\" in loopResult) return loopResult\n return {\n env: loopResult.currentEnvironment,\n meta: loopResult.aggregatedMeta.length === 0 ? undefined : loopResult.aggregatedMeta,\n shouldBreak: false,\n status: loopResult.aggregatedStatus,\n }\n })\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\"\n\n/**\n * Async-context-scoped holder for the runner's prompt/poll abort signal.\n *\n * The {@link import(\"./runner.js\").runPlaybook} entry installs the signal at\n * the start of a run via {@link withRunnerAbortSignal} so the entire playbook\n * lifecycle runs inside an {@link AsyncLocalStorage} scope that owns the\n * signal. Modules that perform interactive waits (e.g. the `pause` builtin)\n * or polling loops (e.g. `net.waitFor`) read the signal via\n * {@link getRunnerAbortSignal} so a SIGINT/SIGTERM observed mid-run can\n * unblock the wait promptly instead of running to its configured timeout.\n *\n * R-0000743: the holder is now async-context-scoped instead of module-global\n * so two concurrent `runPlaybook` invocations sharing the same Node process\n * never observe each other's signals. The previous module-global slot let a\n * parallel run overwrite the in-flight signal of an earlier run; with\n * {@link AsyncLocalStorage} every async chain rooted in `withRunnerAbortSignal`\n * sees its own value and other chains stay isolated.\n *\n * R-0000027 introduced the `pause` use case; R-0000052 generalized the helper\n * for `net.waitFor` so its polling loop aborts on shutdown.\n */\nconst runnerAbortSignalStorage = new AsyncLocalStorage<AbortSignal | undefined>()\n\n/**\n * Runs `body` while {@link getRunnerAbortSignal} resolves to `signal` for the\n * duration of the entire async sub-tree rooted in this call.\n *\n * Intended for the runner's lifecycle only — playbooks must never call this\n * directly.\n *\n * @param signal - The abort signal whose `abort` event cancels active waits\n * and polling loops in modules that observe it. Pass `undefined` to scope\n * a body that has no abort signal (e.g. tests that need to clear an outer\n * scope without disturbing it).\n * @param body - Async work that observes the scoped signal.\n * @returns The value resolved by `body`.\n */\nexport async function withRunnerAbortSignal<T>(\n signal: AbortSignal | undefined,\n body: () => Promise<T>\n): Promise<T> {\n return runnerAbortSignalStorage.run(signal, body)\n}\n\n/**\n * Imperatively register or clear the abort signal observable through\n * {@link getRunnerAbortSignal} for the current async context and every\n * task that branches off it.\n *\n * R-0000743: this helper is the imperative escape hatch over the new\n * {@link AsyncLocalStorage}-backed implementation. Tests that exercise\n * abort-aware modules (`recipe.check`, `op.apply`, `net.waitFor`, …) can\n * still call this to seed the scope without wrapping their bodies in a\n * dedicated `withRunnerAbortSignal` block. Production code in the runner\n * uses {@link withRunnerAbortSignal} instead so the entire playbook run\n * is bounded by a single async-local scope.\n *\n * Pass `undefined` to clear the registration in the current async context.\n *\n * @param signal - The abort signal to install, or `undefined` to clear.\n */\nexport function setRunnerAbortSignal(signal: AbortSignal | undefined): void {\n // R-0000743: `enterWith` updates the ALS store for the current async\n // chain and every descendant task without requiring a wrapper callback.\n // This preserves the imperative `setRunnerAbortSignal(...)` API used by\n // existing tests while ensuring sibling async chains (e.g. a parallel\n // `runPlaybook` running on its own `withRunnerAbortSignal` scope) keep\n // observing their own scoped value.\n runnerAbortSignalStorage.enterWith(signal)\n}\n\n/**\n * Retrieve the abort signal scoped to the current async context, if any.\n *\n * @returns The active abort signal, or `undefined` when no run is in flight\n * in the current async chain.\n */\nexport function getRunnerAbortSignal(): AbortSignal | undefined {\n return runnerAbortSignalStorage.getStore()\n}\n","const SIGNAL_EXIT_BASE = 128\nconst SIGTERM_NUMBER = 15\nconst SIGINT_NUMBER = 2\n\n/**\n * Returns the conventional exit code for a termination signal.\n * Follows the POSIX convention of 128 + signal number.\n *\n * @param signal - The received signal (`SIGTERM` or `SIGINT`).\n * @returns The exit code to use when the process is terminated by `signal`.\n */\nexport function signalExitCode(signal: NodeJS.Signals): number {\n return SIGNAL_EXIT_BASE + (signal === \"SIGTERM\" ? SIGTERM_NUMBER : SIGINT_NUMBER)\n}\n\n/**\n * Sets `process.exitCode` based on the run outcome.\n * A received shutdown signal takes precedence over module failures.\n *\n * @param shutdownSignal - The signal that interrupted the run, or `null` if the\n * run completed normally.\n * @param stats - Accumulated run statistics used to detect module failures.\n * @param stats.failed - Number of modules that failed.\n */\nexport function resolveExitCode(\n shutdownSignal: NodeJS.Signals | null,\n stats: { failed: number }\n): void {\n if (shutdownSignal != null) {\n process.exitCode = signalExitCode(shutdownSignal)\n } else if (stats.failed > 0) {\n process.exitCode = 1\n } else {\n process.exitCode = 0\n }\n}\n","import type { ServerDefinition } from \"./types.js\"\n\nimport { describeHostValidationFailure, validateHostLabel } from \"./hostValidation.js\"\nimport { validateSshConfig } from \"./serverDefinitionValidation.js\"\n\nfunction isModuleLike(value: unknown): boolean {\n if (value === null || typeof value !== \"object\") return false\n return (\n \"apply\" in value &&\n \"check\" in value &&\n \"name\" in value &&\n typeof value.name === \"string\" &&\n value.name.length > 0 &&\n typeof value.check === \"function\" &&\n typeof value.apply === \"function\"\n )\n}\n\nfunction validateModuleList(\n modules: unknown,\n property: \"run\" | \"signals\",\n options?: { requireNonEmpty?: boolean }\n): void {\n if (!Array.isArray(modules)) {\n throw new TypeError(`ServerDefinition: ${property} must be an array of modules`)\n }\n if (options?.requireNonEmpty === true && modules.length === 0) {\n throw new Error(\"ServerDefinition: run must contain at least one module\")\n }\n for (const [index, module] of modules.entries()) {\n if (!isModuleLike(module)) {\n throw new Error(\n `ServerDefinition: ${property}[${index}] must be a module with name, check, and apply`\n )\n }\n }\n}\n\nexport function validateServerDefinition(\n config: ServerDefinition,\n options?: { allowEmptyRun?: boolean }\n): void {\n const hostValidationFailure = validateHostLabel(config.host)\n if (hostValidationFailure === \"empty\") {\n throw new Error(\"ServerDefinition: host is required\")\n }\n if (hostValidationFailure != null) {\n throw new Error(\n `ServerDefinition: host ${describeHostValidationFailure(hostValidationFailure)}`\n )\n }\n if (config.name.length === 0) {\n throw new Error(\"ServerDefinition: name is required\")\n }\n validateSshConfig(config.ssh)\n validateModuleList(config.run, \"run\", { requireNonEmpty: options?.allowEmptyRun !== true })\n if (config.signals !== undefined) validateModuleList(config.signals, \"signals\")\n}\n\n/**\n * Define a server and validate its configuration at construction time.\n *\n * This is a thin identity function whose only purpose is to provide\n * type-safe validation with descriptive error messages before the runner\n * ever attempts an SSH connection.\n *\n * @param config - The server definition to validate and return.\n * @returns The validated server definition.\n * @throws {Error} When any required field is missing or empty.\n *\n * @example\n * export default server({\n * name: \"web-01\",\n * host: \"10.0.0.1\",\n * ssh: { user: \"root\", ports: [22], privateKey: \"~/.ssh/id_ed25519\" }, // \"~\" is expanded\n * run: [apt.installed(\"nginx\")],\n * });\n */\nexport function server(config: ServerDefinition): ServerDefinition {\n validateServerDefinition(config)\n return config\n}\n","/**\n * Swappable Signal-Bus, der die SIGINT/SIGTERM-Behandlung von runner.ts\n * von process entkoppelt, damit Tests deterministisch feuern können.\n *\n * Production: getSignalBus() liefert einen Default-Bus, der direkt an\n * process.on / process.removeListener / process.listenerCount delegiert.\n *\n * Tests: setSignalBus(createTestSignalBus()) im beforeEach, resetSignalBus()\n * im afterEach. Test-Bus exponiert emit(signal), um Handler synchron zu feuern.\n */\n\nexport type SignalName = \"SIGINT\" | \"SIGTERM\"\nexport type SignalHandler = (signal: SignalName) => void\n\nexport type SignalBus = {\n listenerCount: (signal: SignalName) => number\n off: (signal: SignalName, handler: SignalHandler) => void\n on: (signal: SignalName, handler: SignalHandler) => void\n}\n\nexport type TestSignalBus = {\n emit: (signal: SignalName) => void\n} & SignalBus\n\nconst defaultProcessSignalBus: SignalBus = {\n listenerCount: (signal) => process.listenerCount(signal),\n off(signal, handler) {\n process.removeListener(signal, handler)\n },\n on(signal, handler) {\n process.on(signal, handler)\n },\n}\n\n/**\n * Wir halten die aktive Bus-Referenz in einem Symbol-Slot auf globalThis,\n * damit Tests, die `vi.resetModules()` benutzen, weiterhin denselben\n * Test-Bus sehen wie der frisch re-importierte runner.ts. Ohne diesen\n * Trick würde jede neue Modul-Instanz ihren eigenen `let activeBus`-Slot\n * mit dem Default-Bus initialisieren und die `setSignalBus`-Registrierung\n * der vorherigen Instanz verlieren.\n */\nconst ACTIVE_BUS_KEY = Symbol.for(\"paratix.signalBus.activeBus\")\n\nfunction isSignalBus(value: unknown): value is SignalBus {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { listenerCount?: unknown }).listenerCount === \"function\" &&\n typeof (value as { off?: unknown }).off === \"function\" &&\n typeof (value as { on?: unknown }).on === \"function\"\n )\n}\n\nfunction readActiveBus(): SignalBus | undefined {\n const value: unknown = Reflect.get(globalThis, ACTIVE_BUS_KEY)\n return isSignalBus(value) ? value : undefined\n}\n\nfunction writeActiveBus(bus: SignalBus): void {\n Reflect.set(globalThis, ACTIVE_BUS_KEY, bus)\n}\n\nexport function getSignalBus(): SignalBus {\n return readActiveBus() ?? defaultProcessSignalBus\n}\n\nexport function setSignalBus(bus: SignalBus): void {\n writeActiveBus(bus)\n}\n\nexport function resetSignalBus(): void {\n writeActiveBus(defaultProcessSignalBus)\n}\n\nexport function createTestSignalBus(): TestSignalBus {\n const handlers: Record<SignalName, Set<SignalHandler>> = {\n SIGINT: new Set(),\n SIGTERM: new Set(),\n }\n return {\n emit(signal) {\n // Snapshot vor Dispatch, damit off() während eines Handlers keine\n // weiteren Handler aus der laufenden Iteration entfernen kann.\n const snapshot = new Set(handlers[signal])\n for (const handler of snapshot) handler(signal)\n },\n listenerCount(signal) {\n return handlers[signal].size\n },\n off(signal, handler) {\n handlers[signal].delete(handler)\n },\n on(signal, handler) {\n handlers[signal].add(handler)\n },\n }\n}\n","import type {\n Environment,\n Module,\n ModuleStatus,\n OrchestrationStep,\n SshConnection,\n} from \"./types.js\"\n\nimport { assertValidModuleMetaEntries, mergeEnvironmentFromMeta } from \"./meta.js\"\nimport { printCommandFailure, printModuleResult, startModuleSpinner } from \"./output.js\"\n\nexport type SignalHooks = {\n onSignalFinished?: (status: ModuleStatus) => void\n onSignalStarted?: () => void\n}\n\nexport type SignalRunStatus = \"changed\" | \"failed\"\n\ntype SignalRunParameters = {\n environment: Environment\n hooks?: SignalHooks\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signals: Module[]\n ssh: null | SshConnection\n verbose?: boolean\n}\n\nfunction handleSignalResult(parameters: {\n hooks?: SignalHooks\n result: Awaited<ReturnType<Module[\"apply\"]>>\n signalName: string\n verbose: boolean\n}): SignalRunStatus {\n const { hooks, result, signalName, verbose } = parameters\n printModuleResult(`signal: ${signalName}`, result.status)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n hooks?.onSignalFinished?.(result.status)\n return result.status === \"failed\" ? \"failed\" : \"changed\"\n}\n\nfunction handleSignalFailure(parameters: {\n error: unknown\n hooks?: SignalHooks\n signalName: string\n verbose: boolean\n}): SignalRunStatus {\n const { error, hooks, signalName, verbose } = parameters\n printModuleResult(`signal: ${signalName}`, \"failed\")\n printCommandFailure(error, verbose)\n hooks?.onSignalFinished?.(\"failed\")\n return \"failed\"\n}\n\nasync function applySignalMeta(parameters: {\n currentEnvironment: Environment\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n result: Awaited<ReturnType<Module[\"apply\"]>>\n}): Promise<Environment> {\n assertValidModuleMetaEntries(parameters.result.meta)\n const nextEnvironment =\n parameters.result.status === \"failed\"\n ? parameters.currentEnvironment\n : await mergeEnvironmentFromMeta(parameters.currentEnvironment, parameters.result.meta)\n await parameters.onSignalStep?.({\n env: nextEnvironment,\n meta: parameters.result.meta,\n status: parameters.result.status,\n })\n return nextEnvironment\n}\n\nasync function runOneSignal(parameters: {\n currentEnvironment: Environment\n hooks?: SignalHooks\n onSignalStep?: (step: OrchestrationStep) => Promise<void>\n shutdownSignal?: () => NodeJS.Signals | null\n signal: Module\n ssh: null | SshConnection\n verbose: boolean\n}): Promise<{ nextEnvironment: Environment; status: SignalRunStatus }> {\n const connection = parameters.signal.local === true ? null : parameters.ssh\n startModuleSpinner(`signal: ${parameters.signal.name}`)\n const result =\n parameters.shutdownSignal == null\n ? await parameters.signal.apply(connection, parameters.currentEnvironment)\n : await parameters.signal.apply(connection, parameters.currentEnvironment, {\n shutdownSignal: parameters.shutdownSignal,\n })\n const nextEnvironment = await applySignalMeta({\n currentEnvironment: parameters.currentEnvironment,\n onSignalStep: parameters.onSignalStep,\n result,\n })\n return {\n nextEnvironment,\n status: handleSignalResult({\n hooks: parameters.hooks,\n result,\n signalName: parameters.signal.name,\n verbose: parameters.verbose,\n }),\n }\n}\n\nexport async function runSignalModules(parameters: SignalRunParameters): Promise<SignalRunStatus> {\n const getShutdownSignal = parameters.shutdownSignal ?? (() => null)\n const verbose = parameters.verbose ?? false\n let currentEnvironment = parameters.environment\n let status: SignalRunStatus = \"changed\"\n\n for (const signal of parameters.signals) {\n if (getShutdownSignal() != null) break\n parameters.hooks?.onSignalStarted?.()\n try {\n // eslint-disable-next-line no-await-in-loop\n const signalStep = await runOneSignal({\n currentEnvironment,\n hooks: parameters.hooks,\n onSignalStep: parameters.onSignalStep,\n shutdownSignal: parameters.shutdownSignal,\n signal,\n ssh: parameters.ssh,\n verbose,\n })\n currentEnvironment = signalStep.nextEnvironment\n if (signalStep.status === \"failed\") status = \"failed\"\n } catch (error) {\n status = handleSignalFailure({\n error,\n hooks: parameters.hooks,\n signalName: signal.name,\n verbose,\n })\n }\n }\n\n return status\n}\n","/* eslint-disable max-lines -- SSH transport methods keep callback wiring local */\nimport type { Stats } from \"node:fs\"\n\nimport { createHash, timingSafeEqual } from \"node:crypto\"\nimport { createReadStream } from \"node:fs\"\nimport { readFile, stat } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join, posix } from \"node:path\"\nimport { pipeline } from \"node:stream/promises\"\nimport { Client, type ClientChannel } from \"ssh2\"\n\nimport type { ExecOptions, ExecResult, SshConfig, SshConnection } from \"./types.js\"\n\nimport {\n buildHostVerifier,\n createHostKeyCache,\n extractAlgoFromKey,\n type HostKeyCache,\n HostKeyVerificationError,\n type HostVerifierResult,\n} from \"./knownHosts.js\"\nimport {\n getRegisteredSecrets,\n registerSecret,\n unregisterSecret,\n withRegisteredSecrets,\n} from \"./secretSink.js\"\nimport { SFTP_TIMEOUT, sftpDownload, sftpUpload, sftpUploadContent } from \"./sftp.js\"\nimport {\n attachSshClientTeardownErrorSink,\n CAPTURE_TRUNCATION_MARKER,\n cleanupFailedSshClient,\n collectStreamOutput,\n CommandError,\n DEFAULT_MAX_OUTPUT_BYTES,\n maskPreparedSecrets,\n maskSecrets,\n normalizeSshCloseCode,\n prepareSecrets,\n type SecretSource,\n shellQuote,\n tryConnectOnPort,\n validateMode,\n} from \"./sshHelpers.js\"\nimport { promptTerminal } from \"./terminal.js\"\n\nexport { shellQuote, validateMktempPath, validateMode }\n\nasync function statLocalFile(path: string): Promise<Stats> {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- localPath is an explicit caller-provided upload source that must be stat'ed before transfer\n return stat(path)\n}\n\n/**\n * Stream the local upload source through SHA-256 so the post-finalize\n * verification can compare hashes instead of byte counts (R-0000599). Hashing\n * via `createReadStream` keeps memory usage flat regardless of the file size\n * — `uploadFile` may transfer arbitrarily large artifacts.\n *\n * @param path - Absolute or relative path to the local upload source.\n * @returns Lowercase hex SHA-256 digest of the file contents.\n */\nasync function computeLocalFileSha256(path: string): Promise<string> {\n const hash = createHash(\"sha256\")\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- localPath is the caller-provided upload source that must be hashed before transfer\n const readStream = createReadStream(path)\n await pipeline(readStream, hash)\n return hash.digest(\"hex\")\n}\n\n/**\n * Reject any `directory` argument that contains characters mktemp output\n * validation cannot reason about safely. R-0000202: an unchecked directory\n * with embedded newlines, backslashes, double slashes, or a trailing slash\n * either smuggles control characters into the matched path or builds an\n * invalid expectedPrefix that defeats the startsWith check downstream.\n *\n * @param directory - The candidate target directory for `mktemp`.\n * @throws {Error} When the directory contains forbidden characters.\n */\nfunction assertValidMktempDirectory(directory: string): void {\n const hasInvalidCharacter =\n directory.length === 0 ||\n directory.includes(\"\\n\") ||\n directory.includes(\"\\r\") ||\n directory.includes(\"\\\\\") ||\n directory.includes(\"//\") ||\n (directory !== \"/\" && directory.endsWith(\"/\"))\n if (hasInvalidCharacter) {\n throw new Error(`Unexpected mktemp directory: ${directory}`)\n }\n}\n\n/**\n * Validate that a path returned by `mktemp` matches the expected paratix pattern.\n *\n * @param directory - The target directory in which the temp file must be created.\n * @param path - The raw `mktemp` output to validate.\n * @param prefix - The expected Paratix temp-file prefix.\n * @returns The validated path.\n * @throws {Error} When the path or directory does not match the expected pattern.\n */\nfunction validateMktempPath(directory: string, path: string, prefix: string): string {\n assertValidMktempDirectory(directory)\n const normalizedDirectory = directory === \"/\" ? \"\" : directory\n const expectedPrefix = `${normalizedDirectory}/${prefix}.`\n // R-0000155: reject CR explicitly alongside LF. A remote host that emits\n // Windows-style line endings (or a misconfigured shell that injects a\n // stray CR) could otherwise smuggle control characters into the path\n // verbatim and break downstream argument parsing.\n if (\n !path.startsWith(expectedPrefix) ||\n path.includes(\"\\n\") ||\n path.includes(\"\\r\") ||\n path.endsWith(\"/\")\n ) {\n throw new Error(`Unexpected mktemp output: ${path}`)\n }\n return path\n}\n\nfunction expandHomePath(path: string): string {\n if (path === \"~\") return homedir()\n if (path.startsWith(\"~/\")) return join(homedir(), path.slice(2))\n return path\n}\n\nfunction resolveWriteFileMode(\n remotePath: string,\n options: { mode?: string } | null | undefined\n): string {\n if (options?.mode == null) {\n throw new Error(\n `[ssh.writeFile: ${remotePath}] missing options.mode; pass { mode: \"0644\" } or another explicit file mode`\n )\n }\n\n try {\n validateMode(options.mode)\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(\n `[ssh.writeFile: ${remotePath}] invalid options.mode \"${options.mode}\": ${reason}`,\n { cause: error }\n )\n }\n\n return options.mode\n}\n\nconst COMMAND_TIMEOUT = 120_000\nconst DEFAULT_MAX_RECONNECT_ATTEMPTS = 10\nconst DEFAULT_RECONNECT_TIMEOUT = 120_000\n// SHA-256 of an empty byte sequence. Used by `verifyRemoteWriteFile` to detect\n// a remote file that was finalized as 0 bytes (e.g. disk full) — when the\n// expected content hash is anything other than this constant and the remote\n// hash matches it, we know the destination is empty even without rerunning a\n// separate `stat` call. The literal must match `sha256(\"\")` exactly.\nconst EMPTY_FILE_SHA256 = \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"\n// R-0000209: how long to wait for `client.end()` to complete before\n// forcibly destroying the underlying socket.\nconst DISCONNECT_DESTROY_FALLBACK_MS = 5000\nconst JITTER_BASE = 0.75\nconst JITTER_RANGE = 0.5\nconst RECONNECT_BASE_DELAY = 1000\nconst RECONNECT_MAX_DELAY = 30_000\nconst RAW_OUTPUT_ERROR_SNIPPET_LENGTH = 500\n\ntype AuthMethod = \"agent\" | \"password\" | \"privateKey\" | null\ntype PromptOptions = { abortSignal?: AbortSignal }\ntype SudoCommandMode = \"none\" | \"noninteractive\" | \"password\"\ntype SudoCommandResult = { command: string; mode: SudoCommandMode; needsPassword: boolean }\ntype ConnectOptions = { reconnectDeadline?: number } & PromptOptions\ntype TryConnectOnPortsOptions = {\n agent?: string\n password?: string\n privateKey?: Buffer | string\n reconnectDeadline?: number\n}\ntype HostKeyAttempt = {\n commit: () => void\n hostVerifier: (key: Buffer) => boolean\n}\n\ntype ClientLifecycleListeners = {\n close: () => void\n error: (error: Error) => void\n}\n\ntype SshRuntimeState = {\n host: string\n ports: number[]\n}\n\nfunction getAbortReason(signal: AbortSignal): Error {\n return signal.reason instanceof Error ? signal.reason : new Error(\"SSH operation aborted\")\n}\n\nfunction truncateRawOutputErrorSnippet(text: string): string {\n let count = 0\n let sliceEnd = 0\n for (const char of text) {\n if (count >= RAW_OUTPUT_ERROR_SNIPPET_LENGTH) {\n return `${text.slice(0, sliceEnd)}…(truncated)`\n }\n sliceEnd += char.length\n count++\n }\n return text\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error))\n}\n\n/**\n * Signals that `verifyRemoteWriteFile` could not evaluate the remote\n * verification command (non-zero exit code, unparseable hash output,\n * transport hiccup). Surfacing the failure as a dedicated error lets the\n * caller distinguish it from a real content mismatch: a transient\n * verification failure must propagate so the run can retry, never trigger\n * the Shell-Fallback that would overwrite a file that may well be finalized\n * correctly on disk. R-0000522: the verification now hashes the remote\n * file via `sha256sum` instead of comparing sizes, closing the TOCTOU\n * window where an attacker could swap the file content without changing\n * its byte length. The class name is preserved for backward-compatibility\n * with existing callers that import the symbol.\n */\nexport class RemoteStatTransientError extends Error {\n public constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = \"RemoteStatTransientError\"\n }\n}\n\nfunction closeClientChannel(channel: ClientChannel | null): void {\n if (channel == null) return\n channel.close()\n}\n\nfunction hasReconnectDeadlineExpired(reconnectDeadline?: number): boolean {\n return reconnectDeadline != null && reconnectDeadline <= Date.now()\n}\n\nfunction getRemainingReconnectTimeout(reconnectDeadline?: number): number | undefined {\n return reconnectDeadline == null ? undefined : reconnectDeadline - Date.now()\n}\n\nasync function sleepWithAbort(delay: number, abortSignal?: AbortSignal): Promise<void> {\n // R-0000142: still honour the abort status when delay is non-positive.\n // The reconnect loop reaches `delay = 0` once the deadline has expired and\n // would otherwise spin through additional connect attempts before noticing\n // a queued abort. Always probe the abort state up front so the next\n // `await sleepWithAbort(...)` propagates the abort reason immediately.\n if (abortSignal?.aborted === true) throw getAbortReason(abortSignal)\n if (delay <= 0) return\n if (abortSignal == null) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, delay)\n })\n return\n }\n\n await new Promise<void>((resolve, reject) => {\n const handleAbort = (): void => {\n clearTimeout(timer)\n cleanup()\n reject(getAbortReason(abortSignal))\n }\n const cleanup = (): void => {\n abortSignal.removeEventListener(\"abort\", handleAbort)\n }\n const timer = setTimeout(() => {\n cleanup()\n resolve()\n }, delay)\n\n abortSignal.addEventListener(\"abort\", handleAbort, { once: true })\n })\n}\n\nexport class SshConnectionImpl implements SshConnection {\n private agentSocket: null | string = null\n private authMethod: AuthMethod = null\n /**\n * Cached sudo password stored as a Buffer so it can be actively zeroed\n * after use via `buffer.fill(0)`.\n *\n * **Limitations:** Buffer zeroing in JavaScript/V8 only reduces the window\n * for potential memory leaks — it cannot eliminate them entirely. The GC may\n * create internal copies. The masking pipeline only materializes a string\n * from this buffer on actual output/error paths. This is a best-effort\n * mitigation, not a guarantee.\n */\n private cachedSudoPassword: Buffer | null = null\n private client: Client | null = null\n private readonly clientLifecycleListeners = new WeakMap<Client, ClientLifecycleListeners>()\n private readonly config: SshConfig\n private connectedPort = 0\n /**\n * AbortController whose signal is fed to every SFTP transfer started over\n * the current transport. R-0000255: when `disconnectTransport()` runs (e.g.\n * the SIGINT path in runner.ts), this controller is aborted so any\n * in-flight SFTP upload/download rejects in <1 s instead of waiting for the\n * default 120 s SFTP timeout. A fresh controller is installed on the next\n * `disconnectTransport()` call so future transfers can subscribe again.\n */\n private connectionAbortController: AbortController = new AbortController()\n /**\n * Tracks whether the remote host's sudo timestamp has been primed so that\n * subsequent `sudo -n` calls can succeed without prompting. Set to `true`\n * once `cacheAndValidateSudoPassword` has authenticated via `sudo -S` and\n * cleared on disconnect / clearCachedPassword.\n *\n * R-0000152: this lets `sudoCommand` route caller-provided input through\n * `sudo -n bash -c` even on hosts that require a real sudo password — the\n * password no longer has to share the single SSH stdin stream with the\n * payload, so the previous fail-closed branch becomes a working path.\n */\n private credentialCachePrimed = false\n /**\n * R-0000479: per-instance in-memory cache for host keys accepted via\n * accept-new TOFU. Scoping the map to the connection prevents two parallel\n * SshConnectionImpl instances pointed at the same `[host]:port` from\n * overwriting each other's pinned keys. The cache survives reconnects of\n * the same instance, preserving the original process-lifetime caching\n * behavior expected by the reconnect path.\n */\n private readonly hostKeyCache: HostKeyCache = createHostKeyCache()\n /**\n * Tracks whether the remote host accepts passwordless sudo for the configured\n * user. Set to `true` after a successful `sudo -n true` probe (see\n * `hasPasswordlessSudo`). Used by `sudoCommand()` to safely route stdin\n * payloads via `sudo -n bash -c` only when no real password challenge would\n * appear; when a sudo password is required, `sudo -S` cannot share a single\n * stdin channel with caller-provided input and the call must fail-closed.\n */\n private passwordlessSudo = false\n private readonly pendingRejects = new Set<(reason: Error) => void>()\n private pinnedHostKey: Buffer | null = null\n private promptAbortSignal: AbortSignal | undefined\n private readonly runtime: SshRuntimeState\n /**\n * Cached error from a previous sudo probe failure. Once a probe has failed\n * (sudo not installed, wrong password, etc.) we re-throw this error on\n * subsequent privileged exec calls instead of repeating the probe and\n * re-prompting the user (R-0000145). A successful `cacheAndValidateSudoPassword`\n * clears the cache.\n */\n private sudoProbeFailedReason: Error | null = null\n private sudoProbePromise: null | Promise<void> = null\n private sudoReady = false\n private verifiedHostKey: Buffer | null = null\n\n public constructor(host: string, config: SshConfig) {\n this.runtime = {\n host,\n ports: [...config.ports],\n }\n this.config = {\n ...config,\n ports: [...config.ports],\n }\n if (\n config.sudoPassword != null &&\n (config.sudoPassword.includes(\"\\n\") || config.sudoPassword.includes(\"\\r\"))\n ) {\n throw new Error(\"Sudo password must not contain newline characters\")\n }\n this.cachedSudoPassword = config.sudoPassword == null ? null : Buffer.from(config.sudoPassword)\n // R-0000785: register the seeded sudo password in the process-wide secret\n // sink so the rotation logic in `cacheAndValidateSudoPassword` can release\n // it cleanly when the operator re-prompts. Without this seed registration\n // the rotation path could not balance its `unregisterSecret(previous)`\n // call when the constructor-supplied password is replaced.\n if (config.sudoPassword != null) {\n registerSecret(config.sudoPassword)\n }\n }\n\n public addPort(port: number): boolean {\n if (this.runtime.ports.includes(port)) return false\n this.runtime.ports.push(port)\n return true\n }\n\n /**\n * Establish the SSH connection using the configured credentials.\n *\n * Authentication strategy (in order):\n * 1. If `privateKey` is set: connect with the key, optionally falling back to\n * password authentication when `passwordFallback` is enabled.\n * 2. If `privateKey` is omitted: connect via the SSH agent identified by\n * `SSH_AUTH_SOCK`, optionally falling back to password authentication\n * when `passwordFallback` is enabled. Throws if the environment variable\n * is not set.\n *\n * @param options - Optional prompt behavior for interactive password fallback.\n * @throws {Error} When no port in `config.ports` accepts the connection.\n */\n public async connect(options?: ConnectOptions): Promise<void> {\n // R-0000090: only overwrite the cached prompt signal when the caller\n // actually passed one. `reconnect()` calls `connect()` without options;\n // unconditionally writing `undefined` would silently discard the signal\n // installed by an earlier `connect()` / `probeSudo()` call and break the\n // graceful-shutdown path during reconnect attempts.\n if (options?.abortSignal !== undefined) {\n this.promptAbortSignal = options.abortSignal\n }\n if (this.config.privateKey == null) {\n await this.connectViaAgent(options)\n return\n }\n await this.connectViaPrivateKey(options)\n }\n\n public disconnect(): void {\n this.clearCachedPassword()\n // R-0000145 / R-0000258: disconnectTransport now resets the sudo-related\n // state (sudoProbeFailedReason, sudoReady, credentialCachePrimed,\n // passwordlessSudo) so reconnect()/updateHost paths inherit the same\n // fresh-probe semantics that public disconnect() needs.\n // R-0000669: disconnectTransport also rotates connectionAbortController,\n // which now propagates into performConnectAttemptOnPort. A SIGINT-driven\n // disconnect() while tryConnectOnPort is still running therefore aborts\n // the in-flight connect via the abort-signal path instead of leaving an\n // orphaned client that disconnectTransport already nulled.\n this.disconnectTransport()\n }\n\n public async downloadFile(remotePath: string, localPath: string): Promise<void> {\n const client = this.ensureClient()\n let sourcePath = remotePath\n try {\n if (this.config.user !== \"root\") {\n // R-0000565: pass `/tmp` via `-p` and the template via `--` so the\n // prefix cannot be parsed as a `mktemp` option.\n sourcePath = await this.createRemoteTempPath(\n \"mktemp -p /tmp -- paratix-download.XXXXXX\",\n \"paratix-download\"\n )\n await this.exec(`cat ${shellQuote(remotePath)} > ${shellQuote(sourcePath)}`, {\n silent: true,\n })\n }\n await sftpDownload(\n client,\n sourcePath,\n localPath,\n SFTP_TIMEOUT,\n this.connectionAbortController.signal,\n // R-0000689: bridge the same prepared-secret variants that\n // `exec`/`writeFile` already mask through so a tampered remote path\n // cannot leak a registered secret into the SFTP error message.\n prepareSecrets(this.buildSecrets())\n )\n } finally {\n if (sourcePath !== remotePath) {\n try {\n await this.cleanupRemoteTempFile(sourcePath)\n } catch (cleanupError) {\n process.stderr.write(\n `Warning: failed to remove temp file ${sourcePath}: ${maskSecrets(String(cleanupError), this.buildSecrets())}\\n`\n )\n }\n }\n }\n }\n\n public async exec(command: string, options: ExecOptions = {}): Promise<ExecResult> {\n await this.ensureSudoReady()\n return this.execPrepared(command, options)\n }\n\n public async exists(remotePath: string): Promise<boolean> {\n return this.test(`[ -e ${shellQuote(remotePath)} ]`)\n }\n\n /**\n * R-0000694: synchronous hard-stop used by the second-SIGINT path in\n * `runner.ts`. The regular `disconnect()` is queued via a microtask so\n * ssh2 stream internals have a coherent tick to drain; the second SIGINT\n * arrives before that microtask runs and is followed immediately by\n * `process.exit`, which leaves ssh2 cleanup in the middle of an\n * outstanding stream operation. Calling `client.destroy()` synchronously\n * tears the underlying socket down inside the same tick so no in-flight\n * ssh2 callback survives the imminent exit.\n */\n public forceDestroy(): void {\n if (this.client == null) return\n const closing = this.client\n this.client = null\n try {\n closing.destroy()\n } catch {\n // destroy() may throw if the socket has already been torn down. The\n // hard-exit path cannot do anything useful with that error.\n }\n }\n\n public getConnectionInfo(): ReturnType<SshConnection[\"getConnectionInfo\"]> {\n return {\n agentSocket: this.authMethod === \"agent\" ? (this.agentSocket ?? undefined) : undefined,\n authMethod: this.authMethod ?? undefined,\n configuredPorts: [...this.config.ports],\n host: this.runtime.host,\n port: this.connectedPort,\n privateKeyPath:\n this.authMethod === \"privateKey\" && this.config.privateKey != null\n ? expandHomePath(this.config.privateKey)\n : undefined,\n user: this.config.user,\n verifiedHostPublicKey:\n this.verifiedHostKey == null\n ? undefined\n : `${extractAlgoFromKey(this.verifiedHostKey)} ${this.verifiedHostKey.toString(\"base64\")}`,\n }\n }\n\n public async lines(command: string): Promise<string[]> {\n const out = await this.output(command)\n return out === \"\" ? [] : out.split(\"\\n\")\n }\n\n public async output(command: string): Promise<string> {\n const result = await this.exec(command, { silent: true })\n return result.stdout.trim()\n }\n\n /**\n * Probe whether passwordless sudo is available. If not, prompt the user\n * for a password and cache it for the remainder of the run.\n *\n * @param options - Optional prompt behavior for the interactive sudo password prompt.\n */\n public async probeSudo(options?: PromptOptions): Promise<void> {\n this.promptAbortSignal = options?.abortSignal ?? this.promptAbortSignal\n if (this.isSudoReadyWithoutProbe()) {\n this.sudoReady = true\n return\n }\n await this.ensureSudoInstalled()\n if (await this.hasPasswordlessSudo()) {\n this.sudoReady = true\n return\n }\n await this.promptAndCacheSudoPassword()\n }\n\n public async readFile(remotePath: string): Promise<string> {\n const result = await this.exec(`cat ${shellQuote(remotePath)}`, { silent: true })\n // R-0000668: the default 1 MiB output cap silently appends the capture\n // truncation marker to result.stdout. Returning that to callers corrupts\n // readFile contents and breaks guardedWriteFile's originalContent\n // precondition — surface a clear error instead so the caller sees the\n // real failure and can raise maxOutputBytes if the file legitimately\n // exceeds the cap.\n if (result.stdout.endsWith(CAPTURE_TRUNCATION_MARKER)) {\n throw new Error(\n `[ssh.readFile: ${remotePath}] remote file exceeds the captured-output cap of ${DEFAULT_MAX_OUTPUT_BYTES} bytes; refusing to return truncated contents`\n )\n }\n return result.stdout\n }\n\n public async reconnect(): Promise<void> {\n const timeout = this.config.reconnectTimeout ?? DEFAULT_RECONNECT_TIMEOUT\n const maxAttempts = this.config.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS\n const deadline = Date.now() + timeout\n let attempt = 0\n\n while (Date.now() < deadline && attempt < maxAttempts) {\n try {\n this.disconnectTransport()\n // eslint-disable-next-line no-await-in-loop\n await this.connect({ reconnectDeadline: deadline })\n return\n } catch (error) {\n if (\n error instanceof HostKeyVerificationError ||\n (error instanceof Error && error.message === \"SSH connection closed\")\n )\n throw error\n const jitter =\n Math.min(RECONNECT_BASE_DELAY * 2 ** attempt, RECONNECT_MAX_DELAY) *\n (JITTER_BASE + Math.random() * JITTER_RANGE)\n const delay = Math.min(jitter, Math.max(0, deadline - Date.now()))\n // eslint-disable-next-line no-await-in-loop\n await sleepWithAbort(delay, this.promptAbortSignal)\n attempt++\n }\n }\n throw new Error(\n `Failed to reconnect to ${this.runtime.host} after ${attempt} attempts (timeout: ${timeout}ms)`\n )\n }\n\n public removePort(port: number): void {\n this.runtime.ports = this.runtime.ports.filter((candidate) => candidate !== port)\n }\n\n public async sha256(remotePath: string): Promise<null | string> {\n const exists = await this.test(`[ -f ${shellQuote(remotePath)} ]`)\n if (!exists) return null\n // R-0000668: `output()` strips trailing whitespace which would remove the\n // newline that precedes the truncation marker, but the marker text itself\n // survives. Run a raw exec to detect truncation directly on the captured\n // stream before any trimming corrupts the digest comparison.\n const result = await this.exec(`sha256sum ${shellQuote(remotePath)}`, { silent: true })\n if (result.stdout.endsWith(CAPTURE_TRUNCATION_MARKER)) {\n throw new Error(\n `[ssh.sha256: ${remotePath}] sha256sum output exceeds the captured-output cap of ${DEFAULT_MAX_OUTPUT_BYTES} bytes; refusing to return a digest derived from truncated output`\n )\n }\n return result.stdout.trim().split(/\\s+/v)[0] ?? null\n }\n\n public async test(command: string): Promise<boolean> {\n const result = await this.exec(command, { ignoreExitCode: true, silent: true })\n return result.code === 0\n }\n\n public updateHost(host: string): void {\n this.runtime.host = host\n }\n\n public async uploadFile(\n localPath: string,\n remotePath: string,\n options?: { mode?: string }\n ): Promise<void> {\n const client = this.ensureClient()\n const localFileStats = await statLocalFile(localPath)\n const localFileSize = localFileStats.size\n // R-0000599: pre-compute the SHA-256 of the local upload source so the\n // post-finalize verification can compare hashes instead of byte counts —\n // the same threat model that drove `writeFile` to a SHA-256 post-finalize\n // check (R-0000522) applies here. Streaming via `createReadStream` keeps\n // memory usage flat regardless of the uploaded file's size.\n const expectedHash = await computeLocalFileSha256(localPath)\n const temporaryPath = await this.createRemoteWritableTempPath(remotePath, \"paratix-upload\")\n const temporaryMode = options?.mode ?? \"0600\"\n try {\n await sftpUpload(\n client,\n localPath,\n temporaryPath,\n SFTP_TIMEOUT,\n this.connectionAbortController.signal,\n // R-0000689: mask registered secrets that could appear in the\n // remote temp path before they reach an operator-visible error.\n prepareSecrets(this.buildSecrets())\n )\n await this.setRemoteTempMode(temporaryPath, temporaryMode)\n // R-0000150: verify the size on the staged temp file BEFORE the\n // privileged finalize (`mv -T`). After the move, an attacker with\n // write access to the destination directory could swap the final\n // file and our `stat` would report a size for an attacker-controlled\n // inode rather than the file we actually wrote. Asserting on the\n // temp path eliminates that TOCTOU window.\n // R-0000266: assertRemoteFileSize routes the stat call through raw\n // exec for non-root users so an expired sudo credential cache between\n // upload and finalize cannot mask a real size mismatch with a\n // sudo-auth error.\n await this.assertRemoteFileSize(temporaryPath, localFileSize)\n await this.finalizeRemoteTempFile(temporaryPath, remotePath, temporaryMode)\n // R-0000599: re-hash the finalized destination so a same-length TOCTOU\n // swap between the staged temp file and the privileged `mv -T` cannot\n // slip past. Mirrors the post-finalize SHA-256 check `writeFile`\n // performs via `ensureRemoteWriteFile`; the shell-fallback rewrite\n // there is specific to in-memory content and does not apply to a\n // streamed local upload source.\n await this.ensureRemoteUploadFile({\n expectedHash,\n expectedSize: localFileSize,\n remotePath,\n })\n } finally {\n try {\n await this.cleanupRemoteTempFile(temporaryPath)\n } catch (cleanupError) {\n process.stderr.write(\n `Warning: failed to remove temp file ${temporaryPath}: ${maskSecrets(String(cleanupError), this.buildSecrets())}\\n`\n )\n }\n }\n }\n\n /**\n * Write a string to a remote file atomically via write-to-temp + mv.\n *\n * The content is streamed via SFTP to a remote temporary file, then moved to\n * the final destination with `mv`. This ensures the target file is never\n * left in a half-written state.\n *\n * @param remotePath - Destination path on the remote host.\n * @param content - The string content to write.\n * @param options - Settings for the remote write.\n * @param options.mode - File mode to set via `chmod` on the temp file before moving (e.g. `\"0644\"`).\n */\n public async writeFile(\n remotePath: string,\n content: string,\n options: { mode: string }\n ): Promise<void> {\n const client = this.ensureClient()\n const remoteTemporary = await this.createRemoteWritableTempPath(remotePath, \"paratix-write\")\n const temporaryMode = resolveWriteFileMode(remotePath, options)\n const expectedSize = Buffer.byteLength(content, \"utf8\")\n // R-0000522: pre-compute the SHA-256 of the local content so the\n // post-finalize verification can compare hashes instead of byte counts.\n // The content is already fully in memory (writeFile takes a `string`),\n // so a one-shot `update()` is sufficient — no need for a streaming\n // hash.\n const expectedHash = createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\")\n try {\n await sftpUploadContent(\n client,\n content,\n remoteTemporary,\n SFTP_TIMEOUT,\n this.connectionAbortController.signal,\n // R-0000689: same masking bridge as `uploadFile`; the remote\n // temp path is derived from `remotePath` which may be templated\n // with values registered in the secret sink.\n prepareSecrets(this.buildSecrets())\n )\n await this.setRemoteTempMode(remoteTemporary, temporaryMode)\n // R-0000150: the pre-finalize size check on the staged temp file is a\n // cheap smoke-test that catches obvious upload failures (0-byte writes,\n // truncated transfers) before we ever move the file into place. The\n // post-finalize verification below tightens this to a full SHA-256\n // comparison so a same-length TOCTOU swap of the finalized inode\n // cannot slip past.\n await this.assertRemoteFileSize(remoteTemporary, expectedSize, \"writeFile\")\n await this.finalizeRemoteTempFile(remoteTemporary, remotePath, temporaryMode)\n await this.ensureRemoteWriteFile({\n content,\n expectedHash,\n expectedSize,\n mode: temporaryMode,\n remotePath,\n })\n } finally {\n await this.cleanupWriteFileTemporaryPath(remoteTemporary)\n }\n }\n\n private async agentSocketExists(agent: string): Promise<boolean> {\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- agent is validated from SSH_AUTH_SOCK env var\n await stat(agent)\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Reject any destination directory whose path contains a symbolic link\n * component. Resolving via `realpath -m` lets the check tolerate trailing\n * components that do not yet exist while still detecting symlinks earlier\n * in the path.\n *\n * @param directory - The destination directory derived from `posix.dirname`.\n * @param remotePath - Original remote path (used for the diagnostic message).\n * @throws {Error} when at least one component of `directory` is a symlink.\n */\n private async assertDirnameHasNoSymlinkComponent(\n directory: string,\n remotePath: string\n ): Promise<void> {\n if (directory === \"\" || directory === \"/\") return\n // R-0000693: invoke `realpath` via `command -p` so the lookup runs\n // against the POSIX default PATH (`getconf PATH`). The previous\n // unqualified `realpath` call inherited whatever PATH the connected\n // shell exposed, which on a manipulated remote (e.g. a wrapper script\n // earlier in PATH) could short-circuit the symlink resolution and let\n // a planted symlink survive the guard. `command -p` is a POSIX builtin\n // available in bash/sh on every supported target, so the call resolves\n // to a system-shipped `realpath` regardless of how the user's PATH is\n // shaped.\n const result = await this.output(`command -p realpath -m -- ${shellQuote(directory)}`)\n const resolved = result.trim()\n if (resolved !== directory) {\n throw new Error(\n `[ssh.mktemp: ${remotePath}] destination directory ${directory} resolves to ${resolved}; refusing to mktemp because at least one path component is a symbolic link`\n )\n }\n }\n\n private async assertRemoteFileSize(\n remotePath: string,\n expectedSize: number,\n operation: \"uploadFile\" | \"writeFile\" = \"uploadFile\"\n ): Promise<void> {\n // R-0000266: the upload temp path is owned by the connecting user (mktemp\n // staged it under /tmp without sudo). Reading the size through `output`\n // would funnel the call through `ensureSudoReady` and could fail with a\n // sudo-auth error after the cached credentials expired. Use the raw exec\n // path for non-root users so the size check stays a pure stat call and\n // surfaces a real size mismatch instead of a sudo prompt failure.\n const statCommand = `stat -c '%s' ${shellQuote(remotePath)}`\n const rawSize =\n this.config.user === \"root\"\n ? await this.output(statCommand)\n : await this.outputWithoutSudo(statCommand)\n const actualSize = Number(rawSize.trim())\n\n if (!Number.isFinite(actualSize)) {\n throw new TypeError(\n `[ssh.${operation}: ${remotePath}] could not determine remote file size after upload`\n )\n }\n\n if (actualSize === 0 && expectedSize > 0) {\n const diskInfo = await this.checkRemoteDiskSpace(remotePath)\n if (diskInfo != null && diskInfo.availableBytes < expectedSize) {\n throw new Error(\n `[ssh.${operation}: ${remotePath}] disk full – ${diskInfo.availableBytes} bytes available on ${diskInfo.mountpoint}; the file was written as 0 bytes because there is no space left on the device`\n )\n }\n }\n\n if (actualSize !== expectedSize) {\n throw new Error(\n `[ssh.${operation}: ${remotePath}] remote file size mismatch after upload; expected ${expectedSize} bytes, got ${actualSize}`\n )\n }\n }\n\n /**\n * R-0000669: build the abort signal that {@link tryConnectOnPort} subscribes\n * to. Combines the prompt-level abort signal (which fires from the SIGINT\n * handler) with the connection-level abort signal (which fires from\n * {@link disconnect}/{@link disconnectTransport} via\n * {@link rotateConnectionAbortController}). The combination ensures that\n * any caller-initiated disconnect aborts an in-flight connect attempt,\n * even when the prompt signal was not also aborted (e.g. a programmatic\n * disconnect from a custom error handler).\n *\n * @returns A signal that aborts on the first of the inputs to abort, or\n * undefined when there is no signal to subscribe to.\n */\n private buildConnectAbortSignal(): AbortSignal {\n const signals: AbortSignal[] = [this.connectionAbortController.signal]\n if (this.promptAbortSignal != null) signals.push(this.promptAbortSignal)\n return signals.length === 1 ? signals[0] : AbortSignal.any(signals)\n }\n\n private buildEnvPrefix(environment?: Record<string, string>): string {\n if (environment == null) return \"\"\n for (const key of Object.keys(environment)) {\n if (!/^[A-Za-z_]\\w*$/v.test(key)) {\n throw new Error(`Invalid environment variable name: ${key}`)\n }\n }\n const pairs = Object.entries(environment).map(([k, v]) => `${k}=${shellQuote(v)}`)\n return `${pairs.join(\" \")} `\n }\n\n private buildSecrets(extra?: string[]): SecretSource[] {\n const cachedPasswordSecret =\n this.cachedSudoPassword == null ? [] : [() => this.cachedSudoPassword?.toString(\"utf8\") ?? \"\"]\n // R-0000092: include the process-wide secret sink (op tokens, signed\n // download URLs, user password hashes, ...) so every consumer of\n // `buildSecrets` — including the cleanup-warning path — masks the same\n // material that `printCommandFailure` would mask. Snapshot the registered\n // values at call time so each one is treated as an independent secret\n // variant by the masking pipeline.\n const registeredSecrets: SecretSource[] = getRegisteredSecrets()\n return [...cachedPasswordSecret, ...registeredSecrets, ...(extra ?? [])]\n }\n\n private async cacheAndValidateSudoPassword(password: string): Promise<void> {\n if (password.includes(\"\\n\") || password.includes(\"\\r\")) {\n throw new Error(\"Sudo password must not contain newline characters\")\n }\n // R-0000785: when the cached password rotates (a previous probe primed the\n // sink with an older value, or the caller re-runs `probeSudo` with a fresh\n // prompt), release the old sink registration before overwriting the\n // buffer. Otherwise the orphaned counter keeps masking the now-irrelevant\n // value while the freshly cached password never enters the global sink at\n // all, leaving its plaintext exposed in subsequent diagnostics.\n const previousPassword = this.cachedSudoPassword?.toString(\"utf8\") ?? null\n this.cachedSudoPassword = Buffer.from(password)\n registerSecret(password)\n // The comparisons below are not credential validations — they decide\n // which sink registration to release after a rotation. Constant-time\n // comparison would not change the security properties here, so the\n // ESLint timing-attack heuristic is silenced for the whole branch.\n if (previousPassword != null && previousPassword !== password) {\n unregisterSecret(previousPassword)\n // eslint-disable-next-line security/detect-possible-timing-attacks -- bookkeeping comparison, see note above\n } else if (previousPassword === password) {\n // The caller re-cached the same value (e.g. retry after a transient\n // failure). Drop the now-redundant registration so the counter stays\n // balanced when `clearCachedPassword` releases it later.\n unregisterSecret(password)\n }\n // R-0000146: register the entered password in the process-wide secret\n // sink for the duration of the probe. Without this, a thrown\n // diagnostic—including the wrapping `cause` chain printed by\n // `printCauseChain` in cli.ts via `errorToString(cause)`—could leak\n // the plain-text password through paths that mask only the global\n // sink (not the locally-passed `[password]` array). The registration\n // is released as soon as the probe resolves; on success the caller\n // typically registers the password elsewhere via the buffered cached\n // copy used by buildSecrets.\n try {\n await withRegisteredSecrets([password], async () => {\n await this.execPrepared(\"true\", { silent: true, timeout: 10_000 })\n })\n this.sudoReady = true\n // R-0000152: a successful `sudo -S` authentication primes the remote\n // host's sudo timestamp so subsequent `sudo -n` calls can proceed\n // without re-prompting. Track this state so `sudoCommand` can route\n // caller-provided stdin via `sudo -n` even on password-protected hosts.\n this.credentialCachePrimed = true\n } catch (error) {\n const masked = maskSecrets(String(error), [password])\n this.clearCachedPassword()\n throw new Error(`Sudo authentication failed: ${masked}`, { cause: error })\n }\n }\n\n private async checkRemoteDiskSpace(\n remotePath: string\n ): Promise<{ availableBytes: number; mountpoint: string } | null> {\n const DF_MIN_COLUMNS = 6\n const DF_AVAILABLE_INDEX = 3\n const DF_MOUNTPOINT_INDEX = 5\n const KB_TO_BYTES = 1024\n try {\n const directory = remotePath.includes(\"/\")\n ? remotePath.slice(0, remotePath.lastIndexOf(\"/\")) || \"/\"\n : \".\"\n const dfOutput = await this.output(`df -P ${shellQuote(directory)}`)\n const lines = dfOutput.trim().split(\"\\n\")\n if (lines.length < 2) return null\n const columns = lines[1].split(/\\s+/v)\n if (columns.length < DF_MIN_COLUMNS) return null\n const availableKb = Number(columns[DF_AVAILABLE_INDEX])\n if (!Number.isFinite(availableKb)) return null\n return { availableBytes: availableKb * KB_TO_BYTES, mountpoint: columns[DF_MOUNTPOINT_INDEX] }\n } catch {\n return null\n }\n }\n\n private async cleanupPrivilegedRemoteTempFile(remotePath: string): Promise<void> {\n // R-0000565: pass `--` so the mktemp-allocated path cannot be parsed as\n // an `rm` option after a future refactor that loosens the prefix.\n await this.exec(`rm -f -- ${shellQuote(remotePath)}`, {\n ignoreExitCode: true,\n silent: true,\n })\n }\n\n private async cleanupRemoteTempFile(remotePath: string): Promise<void> {\n // R-0000690: a concurrent disconnect (e.g. SIGINT) may null out\n // `this.client` between the upload path completing and the `finally`\n // cleanup running. Returning early when the transport is already gone\n // avoids racing with `ensureClient()` and prevents `cleanupRemoteTempFile`\n // from emitting a misleading \"Warning: failed to remove temp file\"\n // line for what is really an in-flight shutdown. The original\n // diagnostic from the failing upload / writeFile is preserved because\n // we never enter `this.exec`.\n if (this.client == null) return\n // R-0000196: best-effort cleanup must not propagate errors — a transient\n // SSH failure in a `finally` block would otherwise overwrite the\n // original diagnostic with a misleading rm-failure trace. Mirror the\n // ignoreExitCode pattern used by cleanupPrivilegedRemoteTempFile and\n // emit a masked warning to stderr instead of throwing.\n //\n // R-0000565: pass `--` so the mktemp-allocated path cannot be parsed as\n // an `rm` option after a future refactor that loosens the prefix.\n const command = `rm -f -- ${shellQuote(remotePath)}`\n try {\n if (this.config.user === \"root\") {\n await this.exec(command, { ignoreExitCode: true, silent: true })\n return\n }\n await this.execWithoutSudo(command)\n } catch (cleanupError) {\n process.stderr.write(\n `Warning: failed to remove temp file ${remotePath}: ${maskSecrets(String(cleanupError), this.buildSecrets())}\\n`\n )\n }\n }\n\n private async cleanupWriteFileTemporaryPath(remoteTemporary: string): Promise<void> {\n // R-0000690: short-circuit when the underlying client was nulled by\n // a concurrent disconnect. Mirrors the guard in `cleanupRemoteTempFile`\n // so the writeFile cleanup path stays silent during an in-flight\n // shutdown rather than racing the disconnect logic.\n if (this.client == null) return\n try {\n await this.cleanupRemoteTempFile(remoteTemporary)\n } catch (cleanupError) {\n process.stderr.write(\n `Warning: failed to remove temp file ${remoteTemporary}: ${maskSecrets(String(cleanupError), this.buildSecrets())}\\n`\n )\n }\n }\n\n private clearCachedPassword(): void {\n if (this.cachedSudoPassword != null) {\n // R-0000785: release the sink registration before zeroing the buffer so\n // a rotation that calls `cacheAndValidateSudoPassword` again later (or\n // a final shutdown) cannot leave a dangling reference-counted entry in\n // the process-wide secret sink.\n const passwordValue = this.cachedSudoPassword.toString(\"utf8\")\n this.cachedSudoPassword.fill(0)\n this.cachedSudoPassword = null\n unregisterSecret(passwordValue)\n }\n // R-0000152: a cleared password invalidates our local view of the\n // remote sudo cred cache too — even if the remote timestamp lingers,\n // we have no way to refresh it without prompting again.\n this.credentialCachePrimed = false\n }\n\n private async commitAcceptedHostKey(verifier: HostVerifierResult): Promise<void> {\n if (verifier.commitAcceptedHostKey != null) {\n await verifier.commitAcceptedHostKey()\n return\n }\n if (verifier.pendingPersist != null) await verifier.pendingPersist\n }\n\n private async commitAcceptedHostKeyAndRegisterClient(parameters: {\n client: Client\n port: number\n transitionErrorSink: { assertNoError: () => void }\n verifier: HostVerifierResult\n }): Promise<void> {\n const { client, port, transitionErrorSink, verifier } = parameters\n await this.commitAcceptedHostKey(verifier)\n transitionErrorSink.assertNoError()\n this.registerConnectedClient(client, port)\n }\n\n private async connectViaAgent(options?: ConnectOptions): Promise<void> {\n const agent = process.env.SSH_AUTH_SOCK\n if (agent == null || agent.length === 0) {\n await this.connectWithoutAgentSocket(options)\n return\n }\n if (!(await this.agentSocketExists(agent))) {\n if (await this.tryPasswordFallback(options)) return\n throw new Error(`SSH_AUTH_SOCK points to non-existent path: ${agent}`)\n }\n if (await this.tryConnectOnPorts({ agent, reconnectDeadline: options?.reconnectDeadline })) {\n this.agentSocket = agent\n this.authMethod = \"agent\"\n return\n }\n if (await this.tryPasswordFallback(options, agent)) return\n throw new Error(\n `Could not connect to ${this.runtime.host} via SSH agent on ports ${this.runtime.ports.join(\", \")}`\n )\n }\n\n private async connectViaPrivateKey(options?: ConnectOptions): Promise<void> {\n const privateKeyPath = this.config.privateKey\n if (privateKeyPath == null) {\n throw new Error(\"connectViaPrivateKey requires config.privateKey\")\n }\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const privateKey = await readFile(expandHomePath(privateKeyPath))\n // R-0000521: do not zero the buffer in the success path. ssh2 keeps an\n // internal reference to the same Buffer object, so a re-key or reconnect\n // would access a zeroed buffer. The GC reclaims the buffer once all\n // references are dropped. Only scrub the bytes on the error path for\n // defensive security hygiene.\n try {\n if (\n await this.tryConnectOnPorts({\n privateKey,\n reconnectDeadline: options?.reconnectDeadline,\n })\n ) {\n this.authMethod = \"privateKey\"\n return\n }\n if (await this.tryPrivateKeyPasswordFallback(privateKey, options)) return\n privateKey.fill(0)\n throw new Error(\n `Failed to connect to ${this.runtime.host} on ports: ${this.runtime.ports.join(\", \")}`\n )\n } catch (error) {\n privateKey.fill(0)\n throw error\n }\n }\n\n /**\n * Handle the no-agent / no-private-key configuration. When `SSH_AUTH_SOCK`\n * is unset and `passwordFallback` is enabled, attempt the password path and\n * surface a diagnostic that names both root causes (missing agent + failed\n * password auth) instead of a generic \"Failed to connect on ports\" message.\n *\n * @param options - Prompt options forwarded from `connect()`.\n */\n private async connectWithoutAgentSocket(options?: ConnectOptions): Promise<void> {\n if (!this.config.passwordFallback) {\n throw new Error(\"No privateKey configured and SSH_AUTH_SOCK is not set\")\n }\n if (await this.tryPasswordFallback(options)) return\n throw new Error(\n `No SSH agent (SSH_AUTH_SOCK is not set) and password authentication failed for ${this.config.user}@${this.runtime.host}`\n )\n }\n\n /**\n * Create a host-key verification attempt that only commits accepted trust\n * state after the SSH handshake succeeds.\n *\n * @param original - The original verifier from `buildHostVerifier`, if any.\n * @returns A verifier and commit callback for host-key pinning.\n */\n private createHostKeyAttempt(original?: (key: Buffer) => boolean): HostKeyAttempt {\n let acceptedPinnedHostKey: Buffer | null = null\n let acceptedVerifiedHostKey: Buffer | null = null\n\n return {\n commit: (): void => {\n if (acceptedVerifiedHostKey != null) this.verifiedHostKey ??= acceptedVerifiedHostKey\n if (acceptedPinnedHostKey != null) this.pinnedHostKey ??= acceptedPinnedHostKey\n },\n hostVerifier: (key: Buffer): boolean => {\n if (\n this.pinnedHostKey != null &&\n (this.pinnedHostKey.length !== key.length || !timingSafeEqual(this.pinnedHostKey, key))\n ) {\n this.clearCachedPassword()\n throw new HostKeyVerificationError(\n `HOST KEY CHANGED on reconnect to ${this.runtime.host}: ` +\n \"the remote host key does not match the key from the initial connection. \" +\n \"This could indicate a man-in-the-middle attack.\"\n )\n }\n if (original != null) {\n const accepted = original(key)\n if (!accepted) return false\n acceptedVerifiedHostKey ??= Buffer.from(key)\n }\n acceptedPinnedHostKey ??= Buffer.from(key)\n return true\n },\n }\n }\n\n private async createRemotePrivilegedTempPathInDestination(\n remotePath: string,\n prefix: string\n ): Promise<string> {\n const directory = posix.dirname(remotePath)\n // R-0000141: refuse to mktemp into a directory whose resolved path differs\n // from the literal one — that means at least one component of the\n // destination is a symlink and an attacker could redirect the privileged\n // temp file (and the subsequent `mv -T`) into a location they control.\n await this.assertDirnameHasNoSymlinkComponent(directory, remotePath)\n // R-0000565: pass the directory via `-p` and separate the template with\n // `--` so a future refactor that loosens the prefix cannot let an\n // attacker-controlled value be interpreted as a `mktemp` option.\n const template = `${prefix}.XXXXXX`\n const path = await this.output(`mktemp -p ${shellQuote(directory)} -- ${shellQuote(template)}`)\n return validateMktempPath(directory, path, prefix)\n }\n\n private async createRemoteTempPath(command: string, prefix: string): Promise<string> {\n const path =\n this.config.user === \"root\"\n ? await this.output(command)\n : await this.outputWithoutSudo(command)\n return validateMktempPath(\"/tmp\", path, prefix)\n }\n\n private async createRemoteTempPathInDestination(\n remotePath: string,\n prefix: string\n ): Promise<string> {\n const directory = posix.dirname(remotePath)\n // R-0000141: same dirname-symlink protection as for the privileged path.\n await this.assertDirnameHasNoSymlinkComponent(directory, remotePath)\n // R-0000565: pass the directory via `-p` and separate the template with\n // `--` so the prefix cannot be parsed as a `mktemp` option after a future\n // refactor that loosens the prefix validation.\n const template = `${prefix}.XXXXXX`\n const command = `mktemp -p ${shellQuote(directory)} -- ${shellQuote(template)}`\n const path =\n this.config.user === \"root\"\n ? await this.output(command)\n : await this.outputWithoutSudo(command)\n return validateMktempPath(directory, path, prefix)\n }\n\n private async createRemoteWritableTempPath(remotePath: string, prefix: string): Promise<string> {\n if (this.config.user === \"root\") {\n return this.createRemoteTempPathInDestination(remotePath, prefix)\n }\n // R-0000565: pass `/tmp` via `-p` and the template via `--` so the prefix\n // cannot be parsed as a `mktemp` option after a future refactor that\n // loosens the prefix validation.\n const template = `${prefix}.XXXXXX`\n return this.createRemoteTempPath(`mktemp -p /tmp -- ${shellQuote(template)}`, prefix)\n }\n\n private createSettledCallbacks<T>(\n resolve: (value: T) => void,\n reject: (reason: Error) => void\n ): {\n isSettled: () => boolean\n wrappedReject: (reason: Error) => void\n wrappedResolve: (value: T) => void\n } {\n let settled = false\n const wrappedReject = (reason: Error): void => {\n if (settled) return\n settled = true\n this.pendingRejects.delete(wrappedReject)\n reject(reason)\n }\n const wrappedResolve = (value: T): void => {\n if (settled) return\n settled = true\n this.pendingRejects.delete(wrappedReject)\n resolve(value)\n }\n this.pendingRejects.add(wrappedReject)\n return { isSettled: () => settled, wrappedReject, wrappedResolve }\n }\n\n /**\n * Detach the lifecycle listeners installed by registerConnectedClient.\n *\n * R-0000254: `registerConnectedClient` stores `client.once(\"close\", () =>\n * rejectPending(...))` where `rejectPending` is a closure over\n * `this.pendingRejects` (no snapshot). Without removing these listeners on\n * disconnect, a delayed `close`/`error` event from the OLD client would run\n * the closure against the NEW connection's pendingRejects after a reconnect\n * and falsely reject freshly queued operations with\n * \"SSH connection closed unexpectedly\".\n *\n * @param closing - The ssh2 client whose lifecycle listeners should be removed.\n */\n private detachClientLifecycleListeners(closing: Client): void {\n const listeners = this.clientLifecycleListeners.get(closing)\n if (listeners == null) return\n try {\n closing.removeListener(\"close\", listeners.close)\n closing.removeListener(\"error\", listeners.error)\n this.clientLifecycleListeners.delete(closing)\n } catch {\n // removeListener must never propagate from the disconnect path.\n }\n }\n\n private disconnectTransport(): void {\n this.rotateConnectionAbortController()\n if (this.client) {\n const closing = this.client\n this.client = null\n this.tearDownClient(closing)\n }\n this.resetTransportDerivedState()\n const error = new Error(\"SSH connection closed\")\n for (const rejectFunction of this.pendingRejects) {\n rejectFunction(error)\n }\n this.pendingRejects.clear()\n }\n\n private endStreamInput(stream: ClientChannel, input?: Exclude<ExecOptions[\"input\"], null>): void {\n try {\n if (input === undefined) {\n stream.end()\n } else {\n stream.end(input)\n }\n } catch {\n // Defense in depth: same as writeSudoPassword above.\n }\n }\n\n private ensureClient(): Client {\n if (!this.client) throw new Error(\"SSH not connected\")\n return this.client\n }\n\n /**\n * R-0000599: post-finalize verification for `uploadFile`. Compares the\n * SHA-256 of the finalized remote file against the streaming digest\n * computed from `localPath`. Mirrors the verdict handling of\n * {@link ensureRemoteWriteFile} (\"matches\" / \"empty\" / \"hash-mismatch\")\n * but does not invoke the shell-fallback rewrite — that fallback is\n * specific to in-memory `writeFile` content and cannot be reproduced for\n * a streamed local upload source. A transient verification failure\n * propagates as {@link RemoteStatTransientError} so the caller can retry\n * instead of acting on a non-verdict result.\n *\n * @param options - Verification inputs.\n * @param options.expectedHash - Lowercase hex SHA-256 digest of the local file.\n * @param options.expectedSize - Byte length of the local file (used for disk-full diagnostics).\n * @param options.remotePath - The finalized destination path on the remote host.\n * @throws {Error} When the remote file is empty (disk-full) or its hash does not match.\n */\n private async ensureRemoteUploadFile(options: {\n expectedHash: string\n expectedSize: number\n remotePath: string\n }): Promise<void> {\n const verification = await this.verifyRemoteWriteFile(options.remotePath, options.expectedHash)\n if (verification === \"matches\") return\n if (verification === \"empty\") {\n const diskInfo = await this.checkRemoteDiskSpace(options.remotePath)\n if (diskInfo != null && diskInfo.availableBytes < options.expectedSize) {\n throw new Error(\n `[ssh.uploadFile: ${options.remotePath}] disk full – ${diskInfo.availableBytes} bytes available on ${diskInfo.mountpoint}; the file was written as 0 bytes because there is no space left on the device`\n )\n }\n throw new Error(\n `[ssh.uploadFile: ${options.remotePath}] remote file is empty after upload/finalize; refusing successful upload result`\n )\n }\n throw new Error(\n `[ssh.uploadFile: ${options.remotePath}] remote file hash mismatch after upload/finalize; expected ${options.expectedSize} bytes with SHA-256 ${options.expectedHash}`\n )\n }\n\n private async ensureRemoteWriteFile(options: {\n content: string\n expectedHash: string\n expectedSize: number\n mode: string\n remotePath: string\n }): Promise<void> {\n // R-0000476: `verifyRemoteWriteFile` reports the content verdict\n // (\"matches\" / \"empty\" / \"hash-mismatch\") and throws\n // `RemoteStatTransientError` when the underlying verification command\n // cannot be evaluated. The Shell-Fallback only fires for an explicit\n // \"needs rewrite\" verdict — transient verification failures propagate so\n // callers can retry instead of overwriting a remote file that may\n // already be finalized correctly.\n // R-0000522: the verification compares SHA-256 hashes instead of byte\n // counts so an attacker with write access to the destination directory\n // cannot TOCTOU-swap the file content past the verify call.\n const verification = await this.verifyRemoteWriteFile(options.remotePath, options.expectedHash)\n if (verification === \"matches\") return\n\n await this.rewriteRemoteFileViaShell(options.remotePath, options.content, options.mode)\n const fallbackVerification = await this.verifyRemoteWriteFile(\n options.remotePath,\n options.expectedHash\n )\n if (fallbackVerification === \"matches\") return\n if (fallbackVerification === \"empty\") {\n const diskInfo = await this.checkRemoteDiskSpace(options.remotePath)\n if (diskInfo != null && diskInfo.availableBytes < options.expectedSize) {\n throw new Error(\n `[ssh.writeFile: ${options.remotePath}] disk full – ${diskInfo.availableBytes} bytes available on ${diskInfo.mountpoint}; the file was written as 0 bytes because there is no space left on the device`\n )\n }\n throw new Error(\n `[ssh.writeFile: ${options.remotePath}] remote file is empty after upload/finalize and shell fallback; refusing successful write result`\n )\n }\n throw new Error(\n `[ssh.writeFile: ${options.remotePath}] remote file hash mismatch after upload/finalize and shell fallback; expected ${options.expectedSize} bytes with SHA-256 ${options.expectedHash}`\n )\n }\n\n /** Verify that `sudo` is available on the remote host. */\n private async ensureSudoInstalled(): Promise<void> {\n const result = await this.execRaw(\"command -v sudo\")\n if (result.exitCode !== 0) {\n throw new Error(\"sudo is not installed on the remote host\")\n }\n }\n\n private async ensureSudoReady(): Promise<void> {\n if (this.config.user === \"root\" || this.sudoReady) return\n if (this.cachedSudoPassword != null) {\n this.sudoReady = true\n return\n }\n // R-0000145: once a probe has failed, fail fast on every subsequent call\n // instead of re-running the probe (which would re-prompt the user and\n // could produce an endless prompt loop on hosts without working sudo).\n if (this.sudoProbeFailedReason != null) {\n throw this.sudoProbeFailedReason\n }\n if (this.sudoProbePromise != null) {\n await this.sudoProbePromise\n return\n }\n this.sudoProbePromise = this.probeSudo({ abortSignal: this.promptAbortSignal }).finally(() => {\n this.sudoProbePromise = null\n })\n try {\n await this.sudoProbePromise\n } catch (error) {\n // Cache the failure so further `exec` calls do not re-trigger a prompt.\n this.sudoProbeFailedReason = error instanceof Error ? error : new Error(String(error))\n throw error\n }\n }\n\n private async execPrepared(command: string, options: ExecOptions = {}): Promise<ExecResult> {\n const client = this.ensureClient()\n const environmentPrefix = this.buildEnvPrefix(options.env)\n const sudo = this.sudoCommand(command, environmentPrefix, options.input != null)\n const secrets = prepareSecrets(this.buildSecrets(options.secrets))\n try {\n return await new Promise((resolve, reject) => {\n const { isSettled, wrappedReject, wrappedResolve } =\n this.createSettledCallbacks<ExecResult>(resolve, reject)\n const timeout = options.timeout ?? COMMAND_TIMEOUT\n let activeStream: ClientChannel | null = null\n const timer = setTimeout(() => {\n activeStream?.close()\n wrappedReject(\n new Error(\n `Command timed out after ${timeout}ms: ${maskPreparedSecrets(command, secrets)}`\n )\n )\n }, timeout)\n try {\n client.exec(sudo.command, (error: Error | undefined, stream: ClientChannel) => {\n if (error) {\n clearTimeout(timer)\n wrappedReject(error)\n return\n }\n // If the timer already fired (or the promise was otherwise settled) before\n // ssh2 invoked this callback, we must not attach listeners that can never\n // resolve the already-rejected promise. Close the stream immediately so\n // ssh2 releases the channel and discards any buffered data.\n if (isSettled()) {\n stream.close()\n return\n }\n activeStream = stream\n collectStreamOutput({\n command,\n options,\n reject: wrappedReject,\n resolve: wrappedResolve,\n secrets,\n stream,\n timer,\n })\n this.writeStreamInput(stream, sudo.needsPassword, options.input)\n })\n } catch (error) {\n clearTimeout(timer)\n closeClientChannel(activeStream)\n wrappedReject(toError(error))\n }\n })\n } catch (error) {\n if (sudo.mode === \"noninteractive\" && this.isNoninteractiveSudoAuthError(error)) {\n this.invalidateSudoReadiness()\n }\n throw error\n }\n }\n\n /**\n * Execute a command directly over the SSH transport without sudo wrapping.\n * Used only by {@link probeSudo} to check whether `sudo` is installed.\n *\n * @param command - The raw shell command to run.\n * @returns The exit code and captured stdout.\n */\n private async execRaw(command: string): Promise<{ exitCode: number; stdout: string }> {\n const client = this.ensureClient()\n return new Promise((resolve, reject) => {\n const { isSettled, wrappedReject, wrappedResolve } = this.createSettledCallbacks<{\n exitCode: number\n stdout: string\n }>(resolve, reject)\n let activeStream: ClientChannel | null = null\n const timer = setTimeout(() => {\n activeStream?.close()\n // R-0000667: route command interpolation through the same secret sink\n // execPrepared uses so future callers with secret-bearing commands\n // cannot leak through verbose error rendering.\n const secrets = prepareSecrets(this.buildSecrets())\n wrappedReject(\n new Error(\n `Command timed out after ${COMMAND_TIMEOUT}ms: ${maskPreparedSecrets(command, secrets)}`\n )\n )\n }, COMMAND_TIMEOUT)\n try {\n client.exec(command, (error: Error | undefined, stream: ClientChannel) => {\n if (error) {\n clearTimeout(timer)\n wrappedReject(error)\n return\n }\n // If the timer already fired (or the promise was otherwise settled) before\n // ssh2 invoked this callback, we must not attach listeners that can never\n // resolve the already-rejected promise. Close the stream immediately so\n // ssh2 releases the channel and discards any buffered data.\n if (isSettled()) {\n stream.close()\n return\n }\n activeStream = stream\n const chunks: Buffer[] = []\n let stdoutBytes = 0\n stream.on(\"data\", (chunk: Buffer) => {\n if (stdoutBytes > DEFAULT_MAX_OUTPUT_BYTES) return\n stdoutBytes += chunk.length\n if (stdoutBytes > DEFAULT_MAX_OUTPUT_BYTES) {\n const remainingBytes = Math.max(\n 0,\n DEFAULT_MAX_OUTPUT_BYTES - (stdoutBytes - chunk.length)\n )\n if (remainingBytes > 0) {\n chunks.push(chunk.subarray(0, remainingBytes))\n }\n const capturedStdout = Buffer.concat(chunks).toString(\"utf8\")\n const secrets = prepareSecrets(this.buildSecrets())\n activeStream?.close()\n wrappedReject(\n new Error(\n `Command stdout exceeded ${DEFAULT_MAX_OUTPUT_BYTES} bytes: ${maskPreparedSecrets(\n command,\n secrets\n )}\\nstdout: ${truncateRawOutputErrorSnippet(\n maskPreparedSecrets(capturedStdout, secrets)\n )}`\n )\n )\n return\n }\n chunks.push(chunk)\n })\n stream.on(\"close\", (code: null | number | undefined, signal?: null | string) => {\n clearTimeout(timer)\n if (signal != null && signal !== \"\") {\n // R-0000667: mask command before interpolating it into the\n // Error.message; mirrors the secret sink used in execPrepared.\n const secrets = prepareSecrets(this.buildSecrets())\n wrappedReject(\n new Error(\n `Command failed with signal ${signal}: ${maskPreparedSecrets(command, secrets)}`\n )\n )\n return\n }\n wrappedResolve({\n exitCode: normalizeSshCloseCode(code),\n stdout: Buffer.concat(chunks).toString(\"utf8\"),\n })\n })\n // R-0000089: attach error listeners on both the stream and its stderr\n // channel. ssh2 emits `error` (e.g. EPIPE during the sudo probe path)\n // synchronously and an unhandled `error` on a ClientChannel crashes\n // the process. Pattern mirrors `collectStreamOutput` in sshHelpers.ts.\n stream.on(\"error\", (error: Error) => {\n clearTimeout(timer)\n wrappedReject(error)\n })\n stream.stderr.on(\"data\", () => {\n // discard stderr\n })\n stream.stderr.on(\"error\", (error: Error) => {\n clearTimeout(timer)\n wrappedReject(error)\n })\n })\n } catch (error) {\n clearTimeout(timer)\n closeClientChannel(activeStream)\n wrappedReject(toError(error))\n }\n })\n }\n\n private async execWithoutSudo(command: string): Promise<void> {\n const result = await this.execRaw(command)\n if (result.exitCode !== 0) {\n // R-0000667: mask the command before interpolating it into the\n // Error.message; mirrors the secret sink used in execPrepared.\n const secrets = prepareSecrets(this.buildSecrets())\n throw new Error(\n `Command failed (exit code ${result.exitCode}): ${maskPreparedSecrets(command, secrets)}`\n )\n }\n }\n\n private async finalizeRemoteTempFile(\n temporaryPath: string,\n remotePath: string,\n mode: string\n ): Promise<void> {\n validateMode(mode)\n const targetGuard = `[ ! -d ${shellQuote(remotePath)} ] && [ ! -L ${shellQuote(remotePath)} ]`\n if (this.config.user === \"root\") {\n await this.exec(\n `${targetGuard} && mv -T -- ${shellQuote(temporaryPath)} ${shellQuote(remotePath)}`,\n {\n silent: true,\n }\n )\n return\n }\n\n const directory = posix.dirname(remotePath)\n await this.assertDirnameHasNoSymlinkComponent(directory, remotePath)\n const basename = posix.basename(remotePath)\n // R-0000565: pass the directory via `-p` and separate the template with\n // `--` so the prefix cannot be parsed as a `mktemp` option after a future\n // refactor that loosens the basename validation. Same `--` for the\n // `rm -f` cleanup so `$target_temp` cannot be parsed as an `rm` option.\n const finalTemplate = `.${basename}.paratix.XXXXXX`\n // R-0000517: enable strict shell error handling. Without `set -eu` a\n // failed `mktemp` would leave `$target_temp` empty, the subsequent\n // `mv`/`chmod`/`chown` would silently misbehave, and `trap - EXIT` would\n // produce exit code 0 — letting `uploadFile`/`writeFile` believe the\n // finalize succeeded even though the destination was never written.\n // Guarding `target_temp` immediately after `mktemp` makes any failure\n // surface as a non-zero exit code that `ssh.exec` translates into a\n // `CommandError`.\n const finalizeScript = `set -eu\nif ! ${targetGuard}; then\n printf '%s\\n' 'target path must not be a directory or symlink' >&2\n exit 1\nfi\ntarget_owner=$(stat -c '%u:%g' ${shellQuote(remotePath)} 2>/dev/null || stat -c '%u:%g' ${shellQuote(directory)} 2>/dev/null || printf '0:0')\ntarget_temp=''\ncleanup() {\n if [ -n \"$target_temp\" ]; then\n rm -f -- \"$target_temp\"\n fi\n}\ntrap cleanup EXIT\ntarget_temp=$(mktemp -p ${shellQuote(directory)} -- ${shellQuote(finalTemplate)})\n[ -n \"$target_temp\" ] || exit 1\nmv -T -- ${shellQuote(temporaryPath)} \"$target_temp\"\nchmod ${shellQuote(mode)} \"$target_temp\"\nchown \"$target_owner\" \"$target_temp\"\nmv -T -- \"$target_temp\" ${shellQuote(remotePath)}\ntrap - EXIT\n`\n await this.exec(finalizeScript, { silent: true })\n }\n\n private handleTryConnectError(parameters: {\n client: Client\n error: unknown\n registered: boolean\n tryConnectResolved: boolean\n }): void {\n const { client, error, registered, tryConnectResolved } = parameters\n // Only release the client when nothing else has taken responsibility:\n // tryConnectOnPort cleans up internally on rejection, and a registered\n // client is owned by `this` and must not be force-closed mid-iteration.\n if (tryConnectResolved && !registered) cleanupFailedSshClient(client)\n // R-0000256: rethrow HostKeyVerificationError before any abort handling\n // so the caller sees the verification failure verbatim. When the abort\n // fired simultaneously with a post-handshake commit failure, surface the\n // commit-failure cause via a new Error preserving the original message\n // instead of falling through to the abort path.\n if (error instanceof HostKeyVerificationError) throw error\n if (tryConnectResolved && this.promptAbortSignal?.aborted === true) {\n throw error instanceof Error\n ? new Error(error.message, { cause: error })\n : new Error(String(error))\n }\n if (this.promptAbortSignal?.aborted === true) throw getAbortReason(this.promptAbortSignal)\n // Otherwise fall through to try the next port.\n }\n\n /**\n * Probe whether passwordless sudo is available without provoking an\n * interactive prompt in the SSH channel.\n *\n * R-0000138: previously this method funneled through `execPrepared` →\n * `sudoCommand`, which—when no sudo password was cached—routed the probe\n * through `sudo bash -c …` (without `-n`). On hosts requiring an actual\n * sudo password this hung on a blocking prompt until the 10s watchdog\n * fired and additionally produced `auth.log` failure entries. The probe\n * now runs a dedicated `sudo -n true` via `execRaw`, so a host without\n * passwordless sudo fails fast with a non-zero exit code in one RTT.\n *\n * @returns `true` when `sudo -n true` exits with code 0; `false` otherwise.\n */\n private async hasPasswordlessSudo(): Promise<boolean> {\n try {\n const result = await this.execRaw(\"sudo -n true\")\n if (result.exitCode === 0) {\n this.passwordlessSudo = true\n return true\n }\n return false\n } catch {\n return false\n }\n }\n\n private installConnectedClientTransitionErrorSink(client: Client): {\n assertNoError: () => void\n dispose: () => void\n } {\n const transitionState: { error?: Error } = {}\n const handleTransitionError = (error: Error): void => {\n transitionState.error ??= error\n }\n client.on(\"error\", handleTransitionError)\n return {\n assertNoError() {\n if (transitionState.error != null) throw transitionState.error\n },\n dispose() {\n client.removeListener(\"error\", handleTransitionError)\n },\n }\n }\n\n private invalidateSudoReadiness(): void {\n this.sudoReady = false\n this.credentialCachePrimed = false\n this.passwordlessSudo = false\n }\n\n private isNoninteractiveSudoAuthError(error: unknown): boolean {\n if (!(error instanceof CommandError)) return false\n return /sudo: (?:a password is required|a terminal is required|no tty present)/iv.test(\n error.fullStderr\n )\n }\n\n private isSudoReadyWithoutProbe(): boolean {\n return this.config.user === \"root\" || this.cachedSudoPassword != null\n }\n\n private async outputWithoutSudo(command: string): Promise<string> {\n const result = await this.execRaw(command)\n if (result.exitCode !== 0) {\n // R-0000667: mask the command before interpolating it into the\n // Error.message; mirrors the secret sink used in execPrepared.\n const secrets = prepareSecrets(this.buildSecrets())\n throw new Error(\n `Command failed (exit code ${result.exitCode}): ${maskPreparedSecrets(command, secrets)}`\n )\n }\n return result.stdout.trim()\n }\n\n private async performConnectAttemptOnPort(parameters: {\n client: Client\n options: TryConnectOnPortsOptions\n port: number\n state: { registered: boolean; tryConnectResolved: boolean }\n }): Promise<void> {\n const { client, options, port, state } = parameters\n const verifier = await buildHostVerifier(\n this.config.strictHostKeyChecking ?? \"yes\",\n { host: this.runtime.host, port },\n {\n cache: this.hostKeyCache,\n expectedHostFingerprint: this.config.expectedHostFingerprint,\n expectedHostPublicKey: this.config.expectedHostPublicKey,\n }\n )\n const hostKeyAttempt = this.createHostKeyAttempt(verifier.hostVerifier)\n // R-0000669: combine the prompt-level abort signal (SIGINT-driven prompt\n // cancellation) with the connection-level abort signal so an in-flight\n // tryConnectOnPort is aborted whenever disconnect() runs — including\n // programmatic disconnect paths that did not also abort promptAbortSignal.\n // Without this, disconnectTransport nulled the client while the connect\n // continued and emitted a `ready` event on a no-longer-tracked client.\n const connectAbortSignal = this.buildConnectAbortSignal()\n const transitionErrorSink = this.installConnectedClientTransitionErrorSink(client)\n try {\n await tryConnectOnPort({\n abortSignal: connectAbortSignal,\n agent: options.agent,\n agentForward: this.config.agentForward,\n client,\n host: this.runtime.host,\n hostVerifier: hostKeyAttempt.hostVerifier,\n password: options.password,\n port,\n privateKey: options.privateKey,\n readyTimeout: getRemainingReconnectTimeout(options.reconnectDeadline),\n username: this.config.user,\n })\n state.tryConnectResolved = true\n transitionErrorSink.assertNoError()\n hostKeyAttempt.commit()\n await this.commitAcceptedHostKeyAndRegisterClient({\n client,\n port,\n transitionErrorSink,\n verifier,\n })\n state.registered = true\n } finally {\n transitionErrorSink.dispose()\n }\n }\n\n private async promptAndCacheSudoPassword(): Promise<void> {\n const password = await promptTerminal(\n `[sudo] password for ${this.config.user}@${this.runtime.host}: `,\n true,\n { abortSignal: this.promptAbortSignal }\n )\n await this.cacheAndValidateSudoPassword(password)\n }\n\n /**\n * R-0000581: read the SHA-256 output for the post-finalize verify step\n * without going through `sudo` when possible. For non-root users the\n * finalize step chowns the destination to the parent-directory owner, so\n * the unprivileged user usually has read access; bypassing sudo avoids the\n * worst-case command timeout if the sudo channel hangs. The privileged\n * `output()` fallback runs only when the unprivileged attempt fails — for\n * example because the destination directory denies traverse access to the\n * connected user.\n *\n * @param hashCommand - The `sha256sum -- …` command to execute.\n * @returns Raw stdout of the successful `sha256sum` invocation.\n */\n private async readSha256OutputForVerify(hashCommand: string): Promise<string> {\n if (this.config.user === \"root\") {\n return this.output(hashCommand)\n }\n try {\n return await this.outputWithoutSudo(hashCommand)\n } catch {\n // Fall back to the privileged path when the unprivileged read fails\n // (typically a permission error). `this.output` adds the same sudo\n // wrapper used elsewhere so the caller still gets a stable result.\n return this.output(hashCommand)\n }\n }\n\n private registerConnectedClient(client: Client, port: number): void {\n const rejectPending = (error: Error): void => {\n for (const rejectFunction of this.pendingRejects) {\n rejectFunction(error)\n }\n this.pendingRejects.clear()\n }\n const closeListener = (): void => {\n rejectPending(new Error(\"SSH connection closed unexpectedly\"))\n }\n const errorListener = (error: Error): void => {\n rejectPending(error)\n }\n // R-0000143: ssh2 emits both `close` and `error` for a single\n // disconnect event (e.g. error escalation followed by close). Use\n // `once` for the close handler so the cleanup logic and stderr\n // logging fire exactly once per lifecycle, avoiding duplicated lines\n // for the operator. `rejectPending` itself is idempotent (it clears\n // the set after calling), but the handler is also responsible for\n // diagnostics that should not be repeated.\n client.once(\"close\", closeListener)\n client.on(\"error\", errorListener)\n this.clientLifecycleListeners.set(client, { close: closeListener, error: errorListener })\n this.client = client\n this.connectedPort = port\n }\n\n /**\n * Tear down the SSH transport without touching the cached sudo password.\n *\n * R-0000209: `client.end()` initiates a graceful disconnect, which can\n * hang on TCP half-open until the OS keepalive expires (default ~2 hours).\n * Schedule a `client.destroy()` fallback so test runners and reconnect\n * loops do not leak sockets when the peer never replies.\n */\n /**\n * Reset connection-derived state that must not survive a transport teardown.\n *\n * - R-0000236 clears the identity fields (connectedPort, authMethod,\n * agentSocket) so getConnectionInfo() does not return stale values that\n * would mislead reconnect-rollback logic.\n * - R-0000258 clears the sudo-related state (sudoProbeFailedReason,\n * sudoReady, credentialCachePrimed, passwordlessSudo) so reconnect()/\n * updateHost paths start with a fresh sudo reality and do not re-throw a\n * cached probe failure from the previous host.\n */\n private resetTransportDerivedState(): void {\n this.connectedPort = 0\n this.authMethod = null\n this.agentSocket = null\n this.sudoProbeFailedReason = null\n this.sudoReady = false\n this.credentialCachePrimed = false\n this.passwordlessSudo = false\n }\n\n private async rewriteRemoteFileViaShell(\n remotePath: string,\n content: string,\n mode: string\n ): Promise<void> {\n const remoteTemporary = await this.createRemotePrivilegedTempPathInDestination(\n remotePath,\n \"paratix-write\"\n )\n // R-0000093: stream the encoded payload over stdin instead of passing it\n // as a shell argument. The previous `printf '%s' '<encodedContent>'`\n // pipeline placed the entire base64 blob on the argv list, where it was\n // capped by the kernel `ARG_MAX` limit and a real-world write of a few\n // hundred KB would fail with E2BIG. Reading from stdin removes the cap\n // and matches the sudo-stdin pattern used elsewhere in this class.\n const encodedContent = Buffer.from(content, \"utf8\").toString(\"base64\")\n\n try {\n await this.exec(`base64 -d > ${shellQuote(remoteTemporary)}`, {\n input: encodedContent,\n silent: true,\n })\n await this.exec(`chmod ${shellQuote(mode)} ${shellQuote(remoteTemporary)}`, { silent: true })\n await this.finalizeRemoteTempFile(remoteTemporary, remotePath, mode)\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n throw new Error(`[ssh.writeFile: ${remotePath}] shell fallback write failed: ${reason}`, {\n cause: error,\n })\n } finally {\n try {\n await this.cleanupPrivilegedRemoteTempFile(remoteTemporary)\n } catch (cleanupError) {\n process.stderr.write(\n `Warning: failed to remove temp file ${remoteTemporary}: ${maskSecrets(String(cleanupError), this.buildSecrets())}\\n`\n )\n }\n }\n }\n\n /**\n * R-0000255: fire the SFTP-coupled abort signal so any in-flight SFTP\n * operations stop waiting on their dedicated channels immediately. The\n * new AbortController replaces the old one unconditionally so subsequent\n * connect()s expose a fresh, non-aborted signal that future SFTP operations\n * can subscribe to.\n */\n private rotateConnectionAbortController(): void {\n const previousConnectionAbort = this.connectionAbortController\n this.connectionAbortController = new AbortController()\n try {\n previousConnectionAbort.abort(new Error(\"ssh disconnect\"))\n } catch {\n // AbortController.abort never throws on modern runtimes; defense in\n // depth only.\n }\n }\n\n private async setRemoteTempMode(remotePath: string, mode: string): Promise<void> {\n validateMode(mode)\n const command = `chmod ${shellQuote(mode)} ${shellQuote(remotePath)}`\n if (this.config.user === \"root\") {\n await this.exec(command, { silent: true })\n return\n }\n await this.execWithoutSudo(command)\n }\n\n /**\n * Build the sudo-wrapped command string for execution.\n *\n * @param command - The raw command to execute.\n * @param environmentPrefix - Optional env var prefix string.\n * @param hasInput - Whether the command receives caller-provided stdin.\n * @returns An object with the final command and whether a password must be written to stdin.\n */\n private sudoCommand(\n command: string,\n environmentPrefix = \"\",\n hasInput = false\n ): SudoCommandResult {\n if (this.config.user === \"root\") {\n return { command: `${environmentPrefix}${command}`, mode: \"none\", needsPassword: false }\n }\n const quoted = shellQuote(`${environmentPrefix}${command}`)\n if (this.cachedSudoPassword != null && hasInput) {\n // R-0000127: a single SSH channel only exposes one stdin stream. When\n // sudo would prompt for a password (`sudo -S`), the password and the\n // caller-provided input cannot share that stream — sudo would consume\n // the input as the password and then fail with `sudo: a password is\n // required`. We can only safely route the input via `sudo -n bash -c`\n // if either a previous probe confirmed passwordless sudo, or a prior\n // successful `sudo -S` authentication has primed the remote sudo\n // timestamp (R-0000152). Otherwise fail-closed with an actionable error\n // instead of silently producing a `sudo -n` command that breaks at\n // runtime.\n if (!this.passwordlessSudo && !this.credentialCachePrimed) {\n throw new Error(\n \"exec with input is not supported when sudo requires a password: \" +\n \"configure passwordless sudo for the connecting user or remove the input payload\"\n )\n }\n return { command: `sudo -n bash -c ${quoted}`, mode: \"noninteractive\", needsPassword: false }\n }\n if (this.cachedSudoPassword != null) {\n return {\n command: `SUDO_PROMPT='' sudo -S bash -c ${quoted}`,\n mode: \"password\",\n needsPassword: true,\n }\n }\n return { command: `sudo -n bash -c ${quoted}`, mode: \"noninteractive\", needsPassword: false }\n }\n\n private tearDownClient(closing: Client): void {\n // R-0000582: attach the teardown error sink BEFORE detaching the lifecycle\n // listeners so the client never goes without an `error` listener even for\n // a single tick. Otherwise an `error` event emitted between the detach\n // and the new sink registration would crash the process via Node's\n // \"unhandled error\" path.\n attachSshClientTeardownErrorSink(closing)\n this.detachClientLifecycleListeners(closing)\n try {\n closing.end()\n } catch {\n // end() may throw when the underlying socket has already been destroyed\n }\n const fallback = setTimeout(() => {\n try {\n closing.destroy()\n } catch {\n // destroy() must never propagate from a best-effort fallback\n }\n }, DISCONNECT_DESTROY_FALLBACK_MS)\n // Do not keep the event loop alive solely for the destroy fallback —\n // when the program is otherwise idle, it can exit and the GC will\n // reclaim the socket.\n fallback.unref()\n // R-0000524: when `end()` completes a clean shutdown before the fallback\n // fires, cancel the pending `destroy()` so it does not run on an already\n // closed socket.\n closing.once(\"close\", () => {\n clearTimeout(fallback)\n })\n }\n\n /**\n * Iterate over `config.ports` and attempt a connection on each one.\n *\n * @param options - Auth parameters and optional reconnect deadline for bounded per-port attempts.\n * @returns `true` if a port connected successfully, `false` if all ports failed.\n */\n // R-0000139: iterate on a snapshot of `runtime.ports` so that concurrent\n // `addPort`/`removePort` calls (e.g. from `handlePortChange` rollback in\n // runner.ts) cannot mutate the array mid-iteration and cause skipped or\n // re-visited entries.\n private async tryConnectOnPorts(options: TryConnectOnPortsOptions = {}): Promise<boolean> {\n const ports: number[] = [...this.runtime.ports]\n for (const port of ports) {\n if (hasReconnectDeadlineExpired(options.reconnectDeadline)) return false\n // R-0000039 / R-0000198: keep the Client reference outside the try-block so\n // the catch path can close it explicitly when ownership did not transfer.\n const client = new Client()\n const state = { registered: false, tryConnectResolved: false }\n try {\n // eslint-disable-next-line no-await-in-loop -- buildHostVerifier serializes the known_hosts read; sequential per-port is intentional.\n await this.performConnectAttemptOnPort({ client, options, port, state })\n return true\n } catch (error) {\n this.handleTryConnectError({ client, error, ...state })\n }\n }\n return false\n }\n\n private async tryPasswordFallback(options?: ConnectOptions, agent?: string): Promise<boolean> {\n if (!this.config.passwordFallback) return false\n const password = await promptTerminal(\n `Password for ${this.config.user}@${this.runtime.host}: `,\n true,\n options\n )\n // R-0000095: register the interactively entered SSH login password in\n // the process-wide secret sink for the duration of the connect attempt.\n // Without this, a thrown error from `tryConnectOnPorts` (e.g. an ssh2\n // protocol failure that includes the credential in its trace) would\n // surface the plaintext password to stderr via `printCommandFailure` /\n // `printVerboseGenericError`. Mirroring the sudo-password handling, the\n // sink registration is released as soon as the connect attempt resolves.\n return withRegisteredSecrets([password], async () => {\n if (\n !(await this.tryConnectOnPorts({\n agent,\n password,\n reconnectDeadline: options?.reconnectDeadline,\n }))\n )\n return false\n this.authMethod = \"password\"\n return true\n })\n }\n\n /**\n * Prompt for a password and retry the connect using both the loaded\n * private key and the prompt response. R-0000095: the prompt response is\n * registered in the process-wide secret sink for the duration of the\n * connect attempt so any thrown diagnostic masks the credential.\n *\n * @param privateKey - The loaded private key buffer to combine with the prompt response.\n * @param options - Prompt options (e.g. abort signal) forwarded from `connect()`.\n * @returns `true` when the password attempt succeeded, `false` when the fallback was disabled or all ports refused the credential.\n */\n private async tryPrivateKeyPasswordFallback(\n privateKey: Buffer,\n options?: ConnectOptions\n ): Promise<boolean> {\n if (!this.config.passwordFallback) return false\n const password = await promptTerminal(\n `Password for ${this.config.user}@${this.runtime.host}: `,\n true,\n options\n )\n const accepted = await withRegisteredSecrets([password], async () =>\n this.tryConnectOnPorts({\n password,\n privateKey,\n reconnectDeadline: options?.reconnectDeadline,\n })\n )\n if (!accepted) return false\n this.authMethod = \"password\"\n return true\n }\n\n private async verifyRemoteWriteFile(\n remotePath: string,\n expectedHash: string\n ): Promise<\"empty\" | \"hash-mismatch\" | \"matches\"> {\n // R-0000522: verify the post-finalize remote file via SHA-256 instead of\n // a plain byte-count comparison. A size-only check left a TOCTOU window\n // where an attacker with write access to the destination directory could\n // swap the finalized inode with a different file of the same length and\n // our verification would still report \"matches\". Hashing the actual byte\n // contents closes that window because SHA-256 is collision-resistant.\n // R-0000581: for non-root sessions try the unprivileged `sha256sum` path\n // first. The finalize step chowns the destination to the parent-directory\n // owner, so the connected user can usually read the file directly. This\n // avoids running `sudo bash -c …` for the verify step, which would burn\n // the full per-command timeout when a sudo prompt hangs (worst-case twice\n // for upload + verify). Only fall back to the privileged `output()` path\n // when the unprivileged read fails with a permission error.\n const hashCommand = `sha256sum -- ${shellQuote(remotePath)}`\n let rawHash: string\n try {\n rawHash = await this.readSha256OutputForVerify(hashCommand)\n } catch (error) {\n // R-0000476: a transient verification failure (non-zero exit code,\n // channel error, sudo hiccup) must not look like a content mismatch.\n // Surfacing it as a dedicated transient error stops the caller from\n // entering the Shell-Fallback and overwriting a remote file that may\n // well be finalized correctly on disk.\n throw new RemoteStatTransientError(\n `[ssh.writeFile: ${remotePath}] could not hash remote file after upload/finalize`,\n { cause: error }\n )\n }\n // R-0000686: mirror the truncation detection from R-0000668 (sha256 /\n // readFile) for the verify path. When the captured stdout is suffixed\n // with `CAPTURE_TRUNCATION_MARKER`, the underlying exec hit the 1 MiB\n // output cap and the trailing 64-hex digest may have been chopped off.\n // Treat that as a transient verification failure so the caller can\n // re-run the verify rather than mis-classifying a truncated reply as\n // a content mismatch (which would then walk into the Shell-Fallback\n // overwrite path).\n if (rawHash.endsWith(CAPTURE_TRUNCATION_MARKER)) {\n throw new RemoteStatTransientError(\n `[ssh.writeFile: ${remotePath}] sha256sum output exceeds the captured-output cap of ${DEFAULT_MAX_OUTPUT_BYTES} bytes; refusing to derive a verdict from truncated output`\n )\n }\n // `sha256sum -- <file>` prints `<64-hex> <filename>` on success. Split\n // on whitespace and take the first token so a stray newline or filename\n // that contains whitespace cannot confuse the parser.\n const actualHash = rawHash.trim().split(/\\s+/v)[0]?.toLowerCase() ?? \"\"\n\n if (!/^[0-9a-f]{64}$/v.test(actualHash)) {\n throw new RemoteStatTransientError(\n `[ssh.writeFile: ${remotePath}] could not determine remote file hash after upload/finalize`\n )\n }\n\n const expected = expectedHash.toLowerCase()\n if (actualHash === expected) return \"matches\"\n // Disk-full indicator: the remote file hashes to the canonical empty\n // SHA-256 even though we expected non-empty content. Caller uses this\n // verdict to surface a precise \"disk full\" diagnostic when df agrees.\n if (actualHash === EMPTY_FILE_SHA256 && expected !== EMPTY_FILE_SHA256) return \"empty\"\n return \"hash-mismatch\"\n }\n\n /**\n * Forward sudo password / `options.input` to a stream while tolerating\n * EPIPE errors that may arrive between the `isSettled()` check and the\n * actual write call (R-0000054). The stream is owned by ssh2 and may\n * have been closed by the timeout path; emitting an `error` event on a\n * closed channel without a listener would crash the process.\n *\n * @param stream - The ssh2 channel that just became available.\n * @param needsPassword - Whether the command needs a sudo password.\n * @param input - Optional stdin payload from the caller.\n */\n private writeStreamInput(\n stream: ClientChannel,\n needsPassword: boolean,\n input: ExecOptions[\"input\"]\n ): void {\n // R-0000054: attach an EPIPE-safe error handler before any subsequent\n // write to the stream. The settle path (timeout / remote close)\n // already owns the rejection reason; later stream errors are dropped.\n stream.once(\"error\", () => {\n // Defensive no-op.\n })\n // R-0000140: ssh2 emits stderr `error` events (e.g. EPIPE during the\n // sudo password write) on the stderr channel separately from the main\n // stream. `collectStreamOutput` only attaches its own stderr listener\n // *after* `writeStreamInput` returns, so a synchronous stderr EPIPE\n // emitted while the password is being written would have no listener\n // and the channel could hang silently until the 120s watchdog fires.\n // Install a defensive stderr listener so the event is always consumed.\n stream.stderr.once(\"error\", () => {\n // Defensive no-op — collectStreamOutput owns the actual rejection path.\n })\n const writesPassword = needsPassword && this.cachedSudoPassword != null\n if (writesPassword) {\n try {\n this.writeSudoPassword(stream)\n } catch {\n // Defense in depth: a synchronous EPIPE during write must not\n // propagate — the timer / remote-close path owns the rejection.\n }\n }\n if (input != null) {\n this.endStreamInput(stream, input)\n return\n }\n if (writesPassword) {\n this.endStreamInput(stream)\n }\n }\n\n /**\n * Write the cached sudo password followed by a newline to the given stream.\n *\n * @param stream - The SSH channel to write the password to.\n */\n private writeSudoPassword(stream: ClientChannel): void {\n stream.write(this.cachedSudoPassword)\n stream.write(\"\\n\")\n }\n}\n","/* eslint-disable max-lines -- SFTP transfer helpers keep stream lifecycle wiring local */\nimport type { Writable } from \"node:stream\"\nimport type { Client, SFTPWrapper } from \"ssh2\"\n\nimport { randomUUID } from \"node:crypto\"\nimport { createReadStream, createWriteStream } from \"node:fs\"\nimport { rename, unlink } from \"node:fs/promises\"\nimport { dirname, join } from \"node:path\"\nimport { Readable } from \"node:stream\"\n\nimport type { PreparedSecrets } from \"./sshHelpers.js\"\n\nimport { maskPreparedSecrets } from \"./sshHelpers.js\"\n\n/** Default timeout for SFTP transfers in milliseconds (2 minutes). */\nexport const SFTP_TIMEOUT = 120_000\n\n/**\n * R-0000689: helper that masks a remote-path interpolation against the\n * prepared secret variants. When `secrets` is `undefined` the input is\n * returned unchanged so existing callers that do not know about the\n * masking pipeline continue to receive the raw text.\n *\n * @param value - The string about to be embedded in an error message.\n * @param secrets - Optional prepared secrets used to redact the text.\n * @returns The masked variant or the original value when no secrets are set.\n */\nfunction maskInterpolatedValue(value: string, secrets: PreparedSecrets | undefined): string {\n if (secrets === undefined) return value\n return maskPreparedSecrets(value, secrets)\n}\n\ntype TransferSettlement = {\n rejectOnce: (reason: Error) => void\n resolveOnce: () => void\n}\n\ntype TransferStreams = {\n readStream: Readable\n writeStream: Writable\n}\n\nfunction normalizeTransferError(error: unknown, message: string): Error {\n return error instanceof Error ? error : new Error(`${message}: ${String(error)}`)\n}\n\nfunction noopAbortCleanup(): void {\n // No subscription installed.\n}\n\n/**\n * R-0000600: defensive noop installed in place of the real `error` listeners\n * once a transfer has settled. Detaching the original handlers is what\n * allows the closures over readStream/writeStream/sftp to be released, but a\n * late `error` event emitted after settle (e.g. when both streams error\n * back-to-back and the second emit races the cleanup) would otherwise\n * become an unhandled `error` and crash the process. The noop swallows that\n * straggler emit while still letting the original closures be GC'd. Hoisted\n * to module scope so the same shared reference can be attached and later\n * removed across every wireStreams invocation without capturing any\n * per-call state.\n */\nfunction noopStreamError(): void {\n /* swallow late stream errors after the transfer has settled */\n}\n\n/**\n * R-0000687: register a one-shot `error` listener on the SFTP wrapper before\n * we call `sftp.end()`. Without this sink, ssh2 may emit `error` on the\n * wrapper after the surrounding Promise has already settled (e.g. when the\n * underlying channel reports a teardown error post-close). Node would treat\n * that emit as an unhandled `error` and crash the process. The once-listener\n * absorbs that straggler emit without keeping the SFTPWrapper alive. The\n * runtime guard accommodates lightweight test doubles that do not implement\n * the full EventEmitter surface.\n *\n * @param sftp - The SFTP wrapper to silence on late errors.\n */\nfunction silenceLateSftpErrors(sftp: SFTPWrapper): void {\n const maybeOnce = (sftp as { once?: unknown }).once\n if (typeof maybeOnce !== \"function\") return\n sftp.once(\"error\", () => {\n /* swallow late SFTP wrapper errors after the transfer has settled */\n })\n}\n\n/**\n * R-0000255: short-circuit the openSftp wait when the SSH connection is torn\n * down externally (e.g. the SIGINT path in runner.ts). Without this, the\n * session-open promise would idle until the default timeout fires even\n * though the underlying transport is already gone. Returns a cleanup\n * function the caller invokes once the open path settles, plus an `aborted`\n * flag indicating that the abort already fired and the caller must return.\n *\n * @param connectionAbortSignal - Optional connection-level abort signal.\n * @param onAbort - Called synchronously when the abort fires before settle.\n * @returns Cleanup helper and aborted flag.\n */\nfunction subscribeToConnectionAbort(\n connectionAbortSignal: AbortSignal | undefined,\n onAbort: () => void\n): { aborted: boolean; cleanup: () => void } {\n if (connectionAbortSignal == null) {\n return { aborted: false, cleanup: noopAbortCleanup }\n }\n if (connectionAbortSignal.aborted) {\n onAbort()\n return { aborted: true, cleanup: noopAbortCleanup }\n }\n const handleAbort = (): void => {\n onAbort()\n }\n connectionAbortSignal.addEventListener(\"abort\", handleAbort, { once: true })\n const cleanup = (): void => {\n connectionAbortSignal.removeEventListener(\"abort\", handleAbort)\n }\n return { aborted: false, cleanup }\n}\n\nfunction openSftp(options: {\n client: Client\n connectionAbortSignal?: AbortSignal\n onOpen: (sftp: SFTPWrapper) => void\n reject: (reason: Error) => void\n timeout: number\n timeoutMessage: string\n}): void {\n const { client, connectionAbortSignal, onOpen, reject, timeout, timeoutMessage } = options\n let settled = false\n // Holder so the timer / sftp callback can call into a still-mutable cleanup\n // reference once `subscribeToConnectionAbort` returns the real one below.\n const abortHandle: { cleanup: () => void } = { cleanup: noopAbortCleanup }\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n abortHandle.cleanup()\n client.end()\n reject(new Error(timeoutMessage))\n }, timeout)\n const subscription = subscribeToConnectionAbort(connectionAbortSignal, () => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n reject(new Error(\"SFTP session aborted: ssh disconnect\"))\n })\n abortHandle.cleanup = subscription.cleanup\n if (subscription.aborted) return\n try {\n client.sftp((error: Error | undefined, sftp: SFTPWrapper | undefined) => {\n if (settled) {\n if (sftp !== undefined) {\n silenceLateSftpErrors(sftp)\n sftp.end()\n }\n return\n }\n clearTimeout(timer)\n abortHandle.cleanup()\n settled = true\n if (error) {\n reject(error)\n return\n }\n if (sftp === undefined) {\n reject(new Error(\"Failed to open SFTP session: missing SFTP session\"))\n return\n }\n onOpen(sftp)\n })\n } catch (openError) {\n clearTimeout(timer)\n abortHandle.cleanup()\n settled = true\n reject(normalizeTransferError(openError, \"Failed to open SFTP session\"))\n }\n}\n\nfunction createTransferSettlement(options: {\n clearTimer: () => void\n reject: (reason: Error) => void\n resolve: () => void\n sftp: SFTPWrapper\n}): TransferSettlement {\n let settled = false\n\n return {\n rejectOnce(reason: Error) {\n options.clearTimer()\n if (settled) return\n settled = true\n silenceLateSftpErrors(options.sftp)\n options.sftp.end()\n options.reject(reason)\n },\n resolveOnce() {\n options.clearTimer()\n if (settled) return\n settled = true\n silenceLateSftpErrors(options.sftp)\n options.sftp.end()\n options.resolve()\n },\n }\n}\n\nfunction openDownloadStreams(\n sftp: SFTPWrapper,\n remotePath: string,\n temporaryPath: string\n): TransferStreams {\n let readStream: Readable | undefined\n try {\n readStream = sftp.createReadStream(remotePath)\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n const writeStream = createWriteStream(temporaryPath, { mode: 0o600 })\n return { readStream, writeStream }\n } catch (streamError) {\n if (readStream !== undefined && typeof readStream.destroy === \"function\") {\n readStream.destroy()\n }\n throw normalizeTransferError(streamError, \"Failed to create SFTP download streams\")\n }\n}\n\nfunction openUploadStreams(\n sftp: SFTPWrapper,\n localPath: string,\n remotePath: string\n): TransferStreams {\n let readStream: Readable | undefined\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n readStream = createReadStream(localPath)\n const writeStream = sftp.createWriteStream(remotePath, { mode: 0o600 })\n return { readStream, writeStream }\n } catch (streamError) {\n if (readStream !== undefined && typeof readStream.destroy === \"function\") {\n readStream.destroy()\n }\n throw normalizeTransferError(streamError, \"Failed to create SFTP upload streams\")\n }\n}\n\nfunction openContentUploadStreams(\n sftp: SFTPWrapper,\n content: string,\n remotePath: string\n): TransferStreams {\n let readStream: Readable | undefined\n try {\n readStream = Readable.from([Buffer.from(content, \"utf8\")])\n const writeStream = sftp.createWriteStream(remotePath, { mode: 0o600 })\n return { readStream, writeStream }\n } catch (streamError) {\n if (readStream !== undefined && typeof readStream.destroy === \"function\") {\n readStream.destroy()\n }\n throw normalizeTransferError(streamError, \"Failed to create SFTP content upload streams\")\n }\n}\n\n/**\n * Wire up stream event handlers with a timeout guard, then pipe.\n *\n * @param options - Stream piping options including timeout configuration.\n * @param options.completionEvents - Stream events that mark a successful transfer.\n * @param options.connectionAbortSignal - Optional connection-level abort signal that destroys the streams when fired (R-0000255).\n * @param options.prematureCloseMessage - Error message for a close before a successful transfer.\n * @param options.readStream - The source stream to read from.\n * @param options.reject - Promise reject callback.\n * @param options.resolve - Promise resolve callback.\n * @param options.sftp - The SFTP wrapper to close on completion.\n * @param options.timeout - Maximum time in ms before the transfer is aborted.\n * @param options.timeoutMessage - Error message to use when the transfer times out.\n * @param options.writeStream - The destination stream to write to.\n */\ntype StreamListeners = {\n onClose: () => void\n onFinish: () => void\n onReadError: (readError: Error) => void\n onWriteError: (writeError: Error) => void\n}\n\n/**\n * R-0000600: build the set of named stream listeners used by\n * {@link wireStreams}. Extracted so the listener functions stay close\n * together and `wireStreams` can simply attach and later `off(...)` them as\n * a unit. The `getSettlement` indirection lets the listeners be created\n * before the settlement holder is populated — the listeners are only\n * invoked after `pipe()` starts the transfer, which always happens after\n * the settlement has been assigned.\n *\n * @param parameters - Stream wiring inputs shared with `wireStreams`.\n * @param parameters.getSettlement - Late binding for the `TransferSettlement`\n * that the listeners forward `resolveOnce` / `rejectOnce` calls to.\n * @param parameters.prematureCloseMessage - Error message used by the close\n * listener when a premature close is observed. `undefined` disables it.\n * @param parameters.readStream - The source stream to subscribe to for `error` events.\n * @param parameters.writeStream - The destination stream to subscribe to for `error`\n * and completion events.\n * @returns Named listener functions keyed by the event they handle.\n */\nfunction buildStreamListeners(parameters: {\n getSettlement: () => TransferSettlement\n prematureCloseMessage: string | undefined\n readStream: Readable\n writeStream: Writable\n}): StreamListeners {\n const { getSettlement, prematureCloseMessage, readStream, writeStream } = parameters\n return {\n onClose(): void {\n if (prematureCloseMessage === undefined) return\n readStream.destroy()\n if (typeof writeStream.destroy === \"function\") writeStream.destroy()\n getSettlement().rejectOnce(new Error(prematureCloseMessage))\n },\n onFinish(): void {\n getSettlement().resolveOnce()\n },\n onReadError(readError: Error): void {\n if (typeof readStream.destroy === \"function\") readStream.destroy()\n if (typeof writeStream.destroy === \"function\") writeStream.destroy()\n getSettlement().rejectOnce(readError)\n },\n onWriteError(writeError: Error): void {\n readStream.destroy()\n if (typeof writeStream.destroy === \"function\") writeStream.destroy()\n getSettlement().rejectOnce(writeError)\n },\n }\n}\n\n/**\n * R-0000600: detach the stream listeners attached by {@link attachStreamListeners}\n * and replace the `error` handlers with the shared {@link noopStreamError} noop\n * so a late stream error fired after settle does not crash the process.\n *\n * @param parameters - Stream and listener references mirroring the attach call.\n * @param parameters.completionEvents - Events that mark a successful transfer\n * and were registered with `listeners.onFinish`.\n * @param parameters.listeners - Listener bundle returned by {@link buildStreamListeners}.\n * @param parameters.prematureCloseMessage - Same value supplied to the attach\n * call; controls whether `onClose` was registered.\n * @param parameters.readStream - Source stream the read-side listeners were attached to.\n * @param parameters.writeStream - Destination stream the write-side listeners were attached to.\n */\nfunction detachStreamListeners(parameters: {\n completionEvents: Array<\"close\" | \"finish\">\n listeners: StreamListeners\n prematureCloseMessage: string | undefined\n readStream: Readable\n writeStream: Writable\n}): void {\n const { completionEvents, listeners, prematureCloseMessage, readStream, writeStream } = parameters\n for (const completionEvent of completionEvents) {\n writeStream.off(completionEvent, listeners.onFinish)\n }\n if (prematureCloseMessage !== undefined) {\n writeStream.off(\"close\", listeners.onClose)\n }\n writeStream.off(\"error\", listeners.onWriteError)\n readStream.off(\"error\", listeners.onReadError)\n // R-0000841: register the post-settlement noop with `{ once: true }`.\n // The previous persistent listener stayed attached for the lifetime of\n // the underlying streams, which kept the streams (and their internal\n // buffers) reachable long after the transfer had finished. Stream errors\n // in this window are rare and only need to be absorbed once; further\n // straggler emits would already pass through Node's normal unhandled\n // error machinery after the stream has been destroyed and forgotten.\n writeStream.once(\"error\", noopStreamError)\n readStream.once(\"error\", noopStreamError)\n}\n\n/**\n * R-0000600: attach the stream listeners returned by {@link buildStreamListeners}\n * onto the read/write streams.\n *\n * @param parameters - Stream and listener references.\n * @param parameters.completionEvents - Stream events that mark a successful transfer.\n * @param parameters.listeners - Listener bundle returned by {@link buildStreamListeners}.\n * @param parameters.prematureCloseMessage - When defined, `onClose` is wired to\n * the write stream's `\"close\"` event.\n * @param parameters.readStream - Source stream that receives the read-side `error` listener.\n * @param parameters.writeStream - Destination stream that receives the write-side listeners.\n */\nfunction attachStreamListeners(parameters: {\n completionEvents: Array<\"close\" | \"finish\">\n listeners: StreamListeners\n prematureCloseMessage: string | undefined\n readStream: Readable\n writeStream: Writable\n}): void {\n const { completionEvents, listeners, prematureCloseMessage, readStream, writeStream } = parameters\n for (const completionEvent of completionEvents) {\n writeStream.on(completionEvent, listeners.onFinish)\n }\n if (prematureCloseMessage !== undefined) {\n writeStream.on(\"close\", listeners.onClose)\n }\n writeStream.on(\"error\", listeners.onWriteError)\n readStream.on(\"error\", listeners.onReadError)\n}\n\nfunction wireStreams(options: {\n completionEvents?: Array<\"close\" | \"finish\">\n connectionAbortSignal?: AbortSignal\n prematureCloseMessage?: string\n readStream: Readable\n reject: (reason: Error) => void\n resolve: () => void\n sftp: SFTPWrapper\n timeout: number\n timeoutMessage: string\n writeStream: Writable\n}): void {\n const {\n completionEvents = [\"finish\"],\n connectionAbortSignal,\n prematureCloseMessage,\n readStream,\n reject,\n resolve,\n sftp,\n timeout,\n timeoutMessage,\n writeStream,\n } = options\n\n const timer = setTimeout(() => {\n readStream.destroy()\n writeStream.destroy()\n settlement.rejectOnce(new Error(timeoutMessage))\n }, timeout)\n // R-0000600: keep the stream listeners as named functions so the settle\n // path can detach them via `off(...)`. The previous inline-arrow form\n // retained closure references on both readStream and writeStream long\n // after the transfer had settled — for a long-lived SSH session holding\n // many transient SFTP streams this leaked memory proportional to the\n // number of completed transfers. The listeners read the settlement via a\n // getter so the cyclic reference between `settlement.clearTimer` (which\n // calls `off(...)` with these listeners) and the listeners themselves\n // (which call `settlement.rejectOnce`) resolves without a `let`.\n const listeners = buildStreamListeners({\n getSettlement: () => settlement,\n prematureCloseMessage,\n readStream,\n writeStream,\n })\n\n // R-0000688 / R-0000255: declare `handleConnectionAbort` as a hoisted\n // function declaration BEFORE `createTransferSettlement`. The previous\n // `const` form lived after the settlement and produced a temporal-dead-zone\n // hazard: `clearTimer` (passed into the settlement) references\n // `handleConnectionAbort`, so an early teardown — e.g. the timer firing\n // before the `const` initializer ran — would hit a ReferenceError. A\n // function declaration is hoisted to the top of the enclosing function so\n // the reference is always defined when `clearTimer` runs. When the\n // underlying SSH transport is torn down (e.g. the SIGINT handler in\n // runner.ts) any in-flight SFTP transfer would otherwise sit idle until\n // the default 120 s timeout fires, because client.sftp() does NOT emit a\n // stream error on its own when the parent connection closes. Couple the\n // transfer to a connection-level abort signal so an external disconnect\n // destroys the streams immediately.\n function handleConnectionAbort(): void {\n readStream.destroy()\n if (typeof writeStream.destroy === \"function\") writeStream.destroy()\n settlement.rejectOnce(new Error(\"SFTP transfer aborted: ssh disconnect\"))\n }\n\n const settlement = createTransferSettlement({\n clearTimer() {\n clearTimeout(timer)\n // R-0000600: drop the listeners that were attached below so the\n // closures over readStream/writeStream/sftp can be garbage-collected\n // immediately after the transfer settles. A defensive\n // `noopStreamError` replaces the real error listeners so a late\n // `error` event still has a handler and does not crash the process.\n detachStreamListeners({\n completionEvents,\n listeners,\n prematureCloseMessage,\n readStream,\n writeStream,\n })\n if (connectionAbortSignal != null) {\n connectionAbortSignal.removeEventListener(\"abort\", handleConnectionAbort)\n }\n },\n reject,\n resolve,\n sftp,\n })\n\n attachStreamListeners({\n completionEvents,\n listeners,\n prematureCloseMessage,\n readStream,\n writeStream,\n })\n\n // R-0000584: handle the connection-abort signal AFTER the read/write\n // listeners are attached but BEFORE `pipe` starts the transfer. When the\n // signal is already aborted on entry we used to schedule the handler via\n // `queueMicrotask`, but a synchronous `pipe` finish could call\n // `resolveOnce` before that microtask ran and the abort would be swallowed.\n // Invoking `handleConnectionAbort` synchronously here guarantees the abort\n // is observed first, regardless of how quickly `pipe` settles.\n if (connectionAbortSignal != null) {\n if (connectionAbortSignal.aborted) {\n handleConnectionAbort()\n } else {\n connectionAbortSignal.addEventListener(\"abort\", handleConnectionAbort, { once: true })\n }\n }\n\n readStream.pipe(writeStream)\n}\n\n/**\n * Transfer a remote file to a local path via SFTP.\n *\n * @param client - The connected ssh2 client.\n * @param remotePath - Source path on the remote host.\n * @param localPath - Destination path on the local filesystem.\n * @param timeout - Maximum time in ms before the transfer is aborted.\n * @param connectionAbortSignal - Optional abort signal that fires when the\n * underlying SSH transport is torn down. Triggers immediate stream\n * destruction (R-0000255) so a SIGINT-driven `ssh.disconnect()` does not\n * leave the transfer waiting for the default 120 s timeout.\n * @param secrets - R-0000689: optional prepared secrets routed through\n * `maskPreparedSecrets` for every error reason that interpolates the\n * remote path. Without this bridge an attacker-shaped remote path could\n * leak credentials embedded in path templates into operator logs.\n */\n// eslint-disable-next-line max-params -- timeout / abort / secrets parameters extend the existing signature; cleanup/finalize logic is intentionally kept together\nexport async function sftpDownload(\n client: Client,\n remotePath: string,\n localPath: string,\n timeout = SFTP_TIMEOUT,\n connectionAbortSignal?: AbortSignal,\n secrets?: PreparedSecrets\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const temporaryPath = join(dirname(localPath), `.paratix-download-${randomUUID()}.tmp`)\n const maskedRemotePath = maskInterpolatedValue(remotePath, secrets)\n let shouldCleanupTemporaryFile = false\n\n const rejectWithCleanup = (reason: Error): void => {\n if (shouldCleanupTemporaryFile) {\n // R-0000666: detach the temp-file unlink from the reject path so the\n // rejection reason surfaces without waiting for the network filesystem\n // (NFS/SMB) to ack the unlink. The previous sync unlinkSync blocked\n // the event loop and accumulated under parallel sftpDownload failures.\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n unlink(temporaryPath).catch(() => {\n // Best effort cleanup: preserve the original transfer error.\n })\n }\n reject(reason)\n }\n\n openSftp({\n client,\n connectionAbortSignal,\n onOpen(sftp) {\n let streams: TransferStreams\n try {\n streams = openDownloadStreams(sftp, remotePath, temporaryPath)\n shouldCleanupTemporaryFile = true\n } catch (streamError) {\n silenceLateSftpErrors(sftp)\n sftp.end()\n rejectWithCleanup(normalizeTransferError(streamError, \"Failed to create SFTP download\"))\n return\n }\n\n wireStreams({\n completionEvents: [\"finish\"],\n connectionAbortSignal,\n readStream: streams.readStream,\n reject: rejectWithCleanup,\n resolve() {\n // R-0000148: use async rename so the event loop is not blocked on\n // network filesystems or large files. After a successful rename,\n // disable the cleanup flag so a late stray rejection cannot\n // unlink the freshly renamed final file.\n // eslint-disable-next-line security/detect-non-literal-fs-filename\n rename(temporaryPath, localPath).then(\n () => {\n shouldCleanupTemporaryFile = false\n resolve()\n },\n (finalizeError: unknown) => {\n rejectWithCleanup(\n finalizeError instanceof Error\n ? finalizeError\n : new Error(`Failed to finalize SFTP download: ${String(finalizeError)}`)\n )\n }\n )\n },\n sftp,\n timeout,\n timeoutMessage: `SFTP download timed out after ${timeout}ms: ${maskedRemotePath}`,\n writeStream: streams.writeStream,\n })\n },\n reject: rejectWithCleanup,\n timeout,\n timeoutMessage: `SFTP download session timed out after ${timeout}ms: ${maskedRemotePath}`,\n })\n })\n}\n\n/**\n * Transfer a local file to a remote path via SFTP.\n *\n * @param client - The connected ssh2 client.\n * @param localPath - Source path on the local filesystem.\n * @param remotePath - Destination path on the remote host.\n * @param timeout - Maximum time in ms before the transfer is aborted.\n * @param connectionAbortSignal - Optional abort signal that fires when the\n * underlying SSH transport is torn down (R-0000255).\n * @param secrets - R-0000689: optional prepared secrets routed through\n * `maskPreparedSecrets` for every error reason that interpolates the\n * remote path.\n */\n// eslint-disable-next-line max-params -- timeout / abort / secrets parameters extend the existing signature\nexport async function sftpUpload(\n client: Client,\n localPath: string,\n remotePath: string,\n timeout = SFTP_TIMEOUT,\n connectionAbortSignal?: AbortSignal,\n secrets?: PreparedSecrets\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const maskedRemotePath = maskInterpolatedValue(remotePath, secrets)\n openSftp({\n client,\n connectionAbortSignal,\n onOpen(sftp) {\n let streams: TransferStreams\n try {\n streams = openUploadStreams(sftp, localPath, remotePath)\n } catch (streamError) {\n silenceLateSftpErrors(sftp)\n sftp.end()\n reject(normalizeTransferError(streamError, \"Failed to create SFTP upload\"))\n return\n }\n\n wireStreams({\n completionEvents: [\"finish\"],\n connectionAbortSignal,\n prematureCloseMessage: `SFTP upload closed before finish: ${maskedRemotePath}`,\n readStream: streams.readStream,\n reject,\n resolve,\n sftp,\n timeout,\n timeoutMessage: `SFTP upload timed out after ${timeout}ms: ${maskedRemotePath}`,\n writeStream: streams.writeStream,\n })\n },\n reject,\n timeout,\n timeoutMessage: `SFTP upload session timed out after ${timeout}ms: ${maskedRemotePath}`,\n })\n })\n}\n\n/**\n * Transfer string content directly to a remote path via SFTP.\n *\n * @param client - The connected ssh2 client.\n * @param content - UTF-8 string content to transfer.\n * @param remotePath - Destination path on the remote host.\n * @param timeout - Maximum time in ms before the transfer is aborted.\n * @param connectionAbortSignal - Optional abort signal that fires when the\n * underlying SSH transport is torn down (R-0000255).\n * @param secrets - R-0000689: optional prepared secrets routed through\n * `maskPreparedSecrets` for every error reason that interpolates the\n * remote path.\n */\n// eslint-disable-next-line max-params -- timeout / abort / secrets parameters mirror sftpUpload\nexport async function sftpUploadContent(\n client: Client,\n content: string,\n remotePath: string,\n timeout = SFTP_TIMEOUT,\n connectionAbortSignal?: AbortSignal,\n secrets?: PreparedSecrets\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const maskedRemotePath = maskInterpolatedValue(remotePath, secrets)\n openSftp({\n client,\n connectionAbortSignal,\n onOpen(sftp) {\n let streams: TransferStreams\n try {\n streams = openContentUploadStreams(sftp, content, remotePath)\n } catch (streamError) {\n silenceLateSftpErrors(sftp)\n sftp.end()\n reject(normalizeTransferError(streamError, \"Failed to create SFTP content upload\"))\n return\n }\n\n wireStreams({\n completionEvents: [\"finish\"],\n connectionAbortSignal,\n prematureCloseMessage: `SFTP content upload closed before finish: ${maskedRemotePath}`,\n readStream: streams.readStream,\n reject,\n resolve,\n sftp,\n timeout,\n timeoutMessage: `SFTP content upload timed out after ${timeout}ms: ${maskedRemotePath}`,\n writeStream: streams.writeStream,\n })\n },\n reject,\n timeout,\n timeoutMessage: `SFTP content upload session timed out after ${timeout}ms: ${maskedRemotePath}`,\n })\n })\n}\n","import { createInterface } from \"node:readline\"\nimport { Writable } from \"node:stream\"\n\nfunction normalizePromptAbortReason(reason: unknown): Error {\n return reason instanceof Error ? reason : new Error(String(reason))\n}\n\n// Hidden prompts need a TTY-shaped output so readline keeps terminal-mode\n// behavior, but all redraw/echo chunks must be discarded to avoid leaking\n// typed secrets. The prompt text itself is written directly to the target\n// stream by promptTerminal before rl.question is invoked, so _write\n// suppresses every chunk readline would emit. Chunk-based detection of the\n// prompt text is unreliable because readline may split the prompt across\n// multiple _write invocations, in which case a substring check would\n// suppress the chunk that actually carries the prompt.\nclass HiddenPromptOutput extends Writable {\n public readonly columns: number | undefined\n public readonly rows: number | undefined\n\n public constructor(private readonly target: NodeJS.WriteStream) {\n super()\n this.columns = target.columns\n this.rows = target.rows\n Object.defineProperty(this, \"isTTY\", { value: target.isTTY })\n }\n\n public override _write(\n _chunk: Buffer | string,\n _encoding: BufferEncoding,\n callback: (error?: Error | null) => void\n ): void {\n callback()\n }\n\n public clearLine(...parameters: Parameters<NodeJS.WriteStream[\"clearLine\"]>): boolean {\n return this.target.clearLine(...parameters)\n }\n\n public clearScreenDown(\n ...parameters: Parameters<NodeJS.WriteStream[\"clearScreenDown\"]>\n ): boolean {\n return this.target.clearScreenDown(...parameters)\n }\n\n public cursorTo(...parameters: Parameters<NodeJS.WriteStream[\"cursorTo\"]>): boolean {\n return this.target.cursorTo(...parameters)\n }\n\n public getColorDepth(...parameters: Parameters<NodeJS.WriteStream[\"getColorDepth\"]>): number {\n return this.target.getColorDepth(...parameters)\n }\n\n public hasColors(...parameters: Parameters<NodeJS.WriteStream[\"hasColors\"]>): boolean {\n return this.target.hasColors(...parameters)\n }\n\n public moveCursor(...parameters: Parameters<NodeJS.WriteStream[\"moveCursor\"]>): boolean {\n return this.target.moveCursor(...parameters)\n }\n}\n\nfunction createHiddenPromptOutput(target: NodeJS.WriteStream): Writable {\n return new HiddenPromptOutput(target)\n}\n\nfunction createPromptAbortHandler(parameters: {\n abortSignal?: AbortSignal\n cleanup: () => void\n hidden: boolean\n reject: (reason?: unknown) => void\n rl: ReturnType<typeof createInterface>\n setSettled: () => void\n settled: () => boolean\n}): () => void {\n return () => {\n if (parameters.settled()) return\n parameters.setSettled()\n parameters.cleanup()\n parameters.rl.close()\n if (parameters.hidden) process.stderr.write(\"\\n\")\n parameters.reject(\n normalizePromptAbortReason(\n parameters.abortSignal?.reason ?? new Error(\"Terminal prompt aborted\")\n )\n )\n }\n}\n\n/**\n * Prompt the user for input on the terminal.\n * When `hidden` is true the typed characters are not echoed (for passwords).\n *\n * @param question - The prompt text shown to the user.\n * @param hidden - Whether to suppress character echo (password mode).\n * @param options - Optional prompt behavior.\n * @param options.abortSignal - Optional abort signal that cancels the active prompt.\n * @returns The entered string.\n */\nexport async function promptTerminal(\n question: string,\n hidden = false,\n options?: { abortSignal?: AbortSignal }\n): Promise<string> {\n const output = hidden ? createHiddenPromptOutput(process.stderr) : process.stderr\n const rl = createInterface({ input: process.stdin, output })\n const abortSignal = options?.abortSignal\n\n if (hidden) {\n // Write the prompt exactly once directly to stderr; readline's own\n // prompt emission is suppressed by HiddenPromptOutput._write because\n // chunk-based detection of the prompt text is unreliable when readline\n // splits the prompt across multiple writes.\n process.stderr.write(question)\n }\n\n return new Promise((resolve, reject) => {\n let settled = false\n\n const rejectClosedPrompt = (): void => {\n if (settled) return\n settled = true\n cleanup()\n if (hidden) process.stderr.write(\"\\n\")\n reject(new Error(\"Terminal prompt closed before input was received\"))\n }\n\n const cleanup = (): void => {\n abortSignal?.removeEventListener(\"abort\", rejectPrompt)\n rl.removeListener(\"close\", rejectClosedPrompt)\n }\n\n const resolvePrompt = (answer: string): void => {\n if (settled) return\n settled = true\n cleanup()\n rl.close()\n if (hidden) process.stderr.write(\"\\n\")\n resolve(answer)\n }\n\n const rejectPrompt = createPromptAbortHandler({\n abortSignal,\n cleanup,\n hidden,\n reject,\n rl,\n setSettled() {\n settled = true\n },\n settled: () => settled,\n })\n\n if (abortSignal?.aborted === true) {\n rejectPrompt()\n return\n }\n\n abortSignal?.addEventListener(\"abort\", rejectPrompt, { once: true })\n rl.once(\"close\", rejectClosedPrompt)\n\n rl.question(question, (answer) => {\n resolvePrompt(answer)\n })\n })\n}\n","/* eslint-disable max-lines -- central runner orchestration stays intentionally co-located */\nimport type { RecipeModule } from \"./recipe.js\"\nimport type {\n Environment,\n Module,\n ModuleResult,\n ModuleStatus,\n OrchestrationStep,\n ServerDefinition,\n} from \"./types.js\"\n\nimport { shouldExecuteApplyDuringDryRun } from \"./dryRunDispatch.js\"\nimport { dryRunRecipeModule } from \"./dryRunRecipe.js\"\nimport { loadDotEnvironment, mergeEnvironment } from \"./environment.js\"\nimport {\n assertValidModuleMetaEntries,\n isSshdPortMetaEntry,\n isSystemHostMetaEntry,\n isSystemRebootMetaEntry,\n mergeEnvironmentFromMeta,\n} from \"./meta.js\"\nimport {\n printCommandFailure,\n printModuleResult,\n printRecipeHeader,\n printRunContext,\n printSummary,\n startModuleSpinner,\n stopLiveModuleOutput,\n} from \"./output.js\"\nimport { withRunnerAbortSignal } from \"./runnerAbortSignal.js\"\nimport { resolveExitCode, signalExitCode } from \"./runnerHelpers.js\"\nimport { clearRegisteredSecrets, withRunScopedSecrets } from \"./secretSink.js\"\nimport { validateServerDefinition } from \"./server.js\"\nimport { getSignalBus } from \"./signalBus.js\"\nimport { runSignalModules, type SignalRunStatus } from \"./signalOrchestration.js\"\nimport { SshConnectionImpl } from \"./ssh.js\"\n\n/** Holds the shutdown listener, SSH setter, and getter for the first received signal. */\ntype ShutdownState = {\n handleShutdownSignal: (signal: NodeJS.Signals) => void\n promptAbortSignal: AbortSignal\n setSsh: (connection: SshConnectionImpl) => void\n /**\n * R-0000203: AbortSignal that fires the instant SIGINT/SIGTERM is observed,\n * so cooperative waits like {@link sleepRespectingShutdown} can return early\n * without polling.\n */\n shutdownAbortSignal: AbortSignal\n shutdownSignal: () => NodeJS.Signals | null\n}\n\n/** ASCII ESC byte (0x1B) used to start ANSI/VT100 control sequences. */\nconst ASCII_ESC = 0x1b\n\n/**\n * ANSI escape sequence \"ESC [ ? 25 h\" that re-enables the terminal cursor\n * after a spinner or prompt may have hidden it via \"[?25l\".\n */\nconst ANSI_SHOW_CURSOR = `${String.fromCharCode(ASCII_ESC)}[?25h`\n\n/**\n * Best-effort terminal/secret cleanup performed before `process.exit` on a\n * second SIGINT/SIGTERM. Each step is wrapped in a try/catch so a failure in\n * one step never prevents the others from running.\n */\nfunction performShutdownBestEffortCleanup(): void {\n try {\n stopLiveModuleOutput(true)\n } catch {\n // ignore: cleanup is best-effort\n }\n try {\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n } catch {\n // ignore: not all streams support raw mode\n }\n try {\n process.stdout.write(ANSI_SHOW_CURSOR)\n } catch {\n // ignore: stdout may already be closed\n }\n try {\n clearRegisteredSecrets()\n } catch {\n // ignore: secret sink should never throw, but guard defensively\n }\n}\n\n/**\n * Registers shutdown handlers.\n * @returns The listener state and first-signal getter.\n */\nfunction setupShutdownHandlers(): ShutdownState {\n let receivedSignal: NodeJS.Signals | null = null\n let ssh: null | SshConnectionImpl = null\n const promptAbortController = new AbortController()\n // R-0000203: dedicated controller for cooperative waits (e.g. the reboot\n // grace sleep). Abort fires synchronously when SIGINT/SIGTERM arrives, so\n // active sleeps end immediately instead of running their full duration.\n const shutdownAbortController = new AbortController()\n\n const handleShutdownSignal = (signal: NodeJS.Signals): void => {\n if (receivedSignal != null) {\n // A second signal forces a hard exit. Run best-effort cleanup first so\n // the terminal is not left in raw mode / cursor hidden, and so the\n // secret sink does not retain registered values.\n performShutdownBestEffortCleanup()\n // R-0000694: synchronously destroy the ssh2 client before exiting.\n // The first SIGINT queued `ssh.disconnect()` as a microtask\n // (R-0000257) so ssh2 stream internals had a coherent tick to\n // drain. The second SIGINT may arrive before that microtask has\n // run, so any in-flight ssh2 callback would die mid-flight on\n // `process.exit`. Calling `forceDestroy()` here tears down the\n // underlying socket inside the current tick — fully synchronous, no\n // microtask required — so no ssh2 state is left dangling.\n ssh?.forceDestroy()\n // eslint-disable-next-line node/no-process-exit\n process.exit(signalExitCode(signal))\n }\n receivedSignal = signal\n promptAbortController.abort(new Error(`Terminal prompt interrupted by ${signal}`))\n shutdownAbortController.abort(new Error(`Runner sleep interrupted by ${signal}`))\n stopLiveModuleOutput(true)\n console.error(`\\nReceived ${signal}, shutting down…`)\n // R-0000257: defer ssh.disconnect() to a microtask. The signal handler\n // runs synchronously inside Node's signal dispatch and disconnect ->\n // disconnectTransport iterates pendingRejects, which calls reject\n // handlers that may reentrantly invoke ssh2 stream internals. ssh2\n // assumes coherent event-loop tick lifetimes and reentrant stream\n // access is a known crash source. Returning to the next microtask\n // first lets the signal handler complete cleanly and lets ssh2\n // process any in-flight events before teardown begins.\n const sshToDisconnect = ssh\n queueMicrotask(() => {\n sshToDisconnect?.disconnect()\n })\n }\n\n getSignalBus().on(\"SIGINT\", handleShutdownSignal)\n getSignalBus().on(\"SIGTERM\", handleShutdownSignal)\n\n return {\n handleShutdownSignal,\n promptAbortSignal: promptAbortController.signal,\n setSsh(connection: SshConnectionImpl) {\n ssh = connection\n },\n shutdownAbortSignal: shutdownAbortController.signal,\n shutdownSignal: () => receivedSignal,\n }\n}\n\n/**\n * Exposed for tests to observe the second-signal cleanup path without\n * spawning a real process.\n */\nexport const __testing = {\n performShutdownBestEffortCleanup,\n}\n\nexport type RunOptions = {\n /**\n * When `true`, the runner asks dry-run-capable modules to also produce a\n * unified-diff string in {@link ModuleResult.diff}. The diff is rendered\n * below the status line of every module that opts in via the\n * `_dryRunDiffProducer` marker. Implies `dryRun`; the CLI rejects `--diff`\n * without `--dry-run` so this option only has an effect together with it.\n * Defaults to `false`.\n */\n diff?: boolean\n /** When `true`, modules report what would change without applying anything. Defaults to `false`. */\n dryRun?: boolean\n /** Path to a `.env` file whose variables are merged into the run environment. */\n envFile?: string\n /** Additional environment variables that override values from `envFile` and the server definition. */\n envOverrides?: Environment\n /**\n * Initial grace period (in seconds) the runner waits before the first\n * reconnect attempt after a `system.reboot` meta. Defaults to\n * {@link DEFAULT_REBOOT_GRACE_SECONDS}. The wait does not consume any of\n * the configured `maxReconnectAttempts` budget.\n */\n rebootGraceSeconds?: number\n /** Custom reconnect timeout in milliseconds passed to SSH, overriding the config default. */\n reconnectTimeout?: number\n /** When `true`, failed commands print full stdout/stderr in addition to the summary error. */\n verbose?: boolean\n}\n\n/** Default grace period before reconnecting after a reboot. */\nexport const DEFAULT_REBOOT_GRACE_SECONDS = 15\nconst REBOOT_GRACE_SECONDS_TO_MS = 1000\n\n/**\n * Per-`runPlaybook` reboot grace state. Bundling the grace duration, the\n * shutdown getter, and the abort signal in a single context keeps the\n * state out of module scope so concurrent runs cannot race on shared\n * mutable values. The context is created in\n * {@link initializeRunPlaybookContext} and threaded through every call\n * site that may schedule a reboot grace sleep (mirroring how\n * {@link ShutdownState} already flows through the runner).\n */\ntype RebootGraceContext = {\n // R-0000788: typed as a required `AbortSignal` because the only sleep that\n // consumes this context (`sleepRespectingShutdown`) now requires the\n // signal as well. Callers must thread a real shutdown-aware abort source\n // through; passing `undefined` would silently downgrade to a plain timer.\n abortSignal: AbortSignal\n graceMs: number\n shutdownSignal: () => NodeJS.Signals | null\n}\n\ntype SleepRespectingShutdownOptions = {\n abortSignal: AbortSignal\n keepAlive?: boolean\n}\n\nfunction createRebootGraceContext(\n options: RunOptions,\n shutdownSignal: () => NodeJS.Signals | null,\n abortSignal: AbortSignal\n): RebootGraceContext {\n const seconds = options.rebootGraceSeconds ?? DEFAULT_REBOOT_GRACE_SECONDS\n return {\n abortSignal,\n graceMs: Math.max(0, seconds) * REBOOT_GRACE_SECONDS_TO_MS,\n shutdownSignal,\n }\n}\n\n/**\n * Sleep helper that respects the runner's shutdown signal: returns early\n * (without throwing) if a shutdown is in progress so the caller can react.\n *\n * R-0000203: subscribes to a {@link AbortSignal} that fires the moment the\n * shutdown handler observes SIGINT/SIGTERM, so an in-flight sleep ends\n * immediately instead of running its full duration. Without the abort, the\n * reboot reconnect would idle through the entire grace period after the\n * operator pressed Ctrl-C.\n *\n * @param durationMs - The maximum sleep duration in milliseconds.\n * @param shutdownSignal - Getter that returns the active shutdown signal, or `null`.\n * @param options - Additional sleep controls.\n * @param options.abortSignal - `AbortSignal` that ends the sleep early. Required so\n * every caller threads a shutdown-aware abort source through; a plain\n * `setTimeout`-style sleep that ignores Ctrl-C is intentionally not\n * supported here.\n * @param options.keepAlive - When `true`, keeps the timer referenced so the\n * process stays alive until the wait completes.\n */\nasync function sleepRespectingShutdown(\n durationMs: number,\n shutdownSignal: () => NodeJS.Signals | null,\n options: SleepRespectingShutdownOptions\n): Promise<void> {\n const { abortSignal, keepAlive = false } = options\n if (durationMs <= 0) return\n if (shutdownSignal() != null) return\n if (abortSignal.aborted) return\n await new Promise<void>((resolve) => {\n const handleAbort = (): void => {\n clearTimeout(timer)\n cleanup()\n resolve()\n }\n const cleanup = (): void => {\n abortSignal.removeEventListener(\"abort\", handleAbort)\n }\n const timer = setTimeout(() => {\n cleanup()\n resolve()\n }, durationMs)\n // Avoid keeping the event loop alive solely for cooperative waits unless\n // the caller explicitly needs the timer to hold the runner open.\n if (!keepAlive && typeof timer.unref === \"function\") timer.unref()\n abortSignal.addEventListener(\"abort\", handleAbort, { once: true })\n })\n}\n\nclass RunStats {\n public changed = 0\n public failed = 0\n public ok = 0\n public signals = 0\n public skipped = 0\n\n public incrementSignals(): void {\n this.signals++\n }\n\n public update(status: ModuleStatus): void {\n switch (status) {\n case \"changed\": {\n this.changed++\n break\n }\n case \"failed\": {\n this.failed++\n break\n }\n case \"ok\": {\n this.ok++\n break\n }\n case \"skipped\": {\n this.skipped++\n break\n }\n }\n }\n}\n\ntype StepResult = {\n env: Environment\n flushSignals?: true\n shouldBreak: boolean\n status?: ModuleStatus\n stopRun?: true\n}\n\nfunction interruptedStepResult(environment: Environment): StepResult {\n return { env: environment, shouldBreak: true }\n}\n\nfunction shouldBreakAfterResult(result: Pick<ModuleResult, \"_stopRun\" | \"status\">): boolean {\n return result.status === \"failed\" || result._stopRun === true\n}\n\nfunction interruptedBeforeApply(\n environment: Environment,\n shutdownSignal: () => NodeJS.Signals | null\n): StepResult | undefined {\n if (shutdownSignal() == null) return undefined\n return interruptedStepResult(environment)\n}\n\nasync function applyCheckedModule(parameters: {\n currentEnvironment: Environment\n diff?: boolean\n dryRun?: boolean\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}): Promise<StepResult> {\n const interrupted = interruptedBeforeApply(\n parameters.currentEnvironment,\n parameters.shutdownSignal\n )\n if (interrupted != null) return interrupted\n\n return applyModule({\n currentEnvironment: parameters.currentEnvironment,\n diff: parameters.diff,\n dryRun: parameters.dryRun,\n rebootGrace: parameters.rebootGrace,\n shutdownSignal: parameters.shutdownSignal,\n ssh: parameters.ssh,\n targetModule: parameters.targetModule,\n verbose: parameters.verbose,\n })\n}\n\nfunction handleCaughtStepError(parameters: {\n environment: Environment\n error: unknown\n moduleName: string\n shutdownSignal: () => NodeJS.Signals | null\n verbose: boolean\n}): StepResult {\n if (parameters.shutdownSignal() != null) {\n return interruptedStepResult(parameters.environment)\n }\n printModuleResult(parameters.moduleName, \"failed\")\n printCommandFailure(parameters.error, parameters.verbose)\n return { env: parameters.environment, shouldBreak: true, status: \"failed\" }\n}\n\nfunction isRecipe(target: Module): target is RecipeModule {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- RecipeModule uses _isRecipe as discriminator\n return \"_isRecipe\" in target && (target as RecipeModule)._isRecipe\n}\n\nfunction addSshdPorts(ssh: SshConnectionImpl, metaEntries: ModuleResult[\"meta\"]): number[] {\n const portEntries = metaEntries?.filter((entry) => isSshdPortMetaEntry(entry)) ?? []\n const addedPorts: number[] = []\n for (const portEntry of portEntries) {\n if (ssh.addPort(portEntry.port)) {\n addedPorts.push(portEntry.port)\n }\n }\n return addedPorts\n}\n\nfunction hasSshdPortMeta(metaEntries: ModuleResult[\"meta\"]): boolean {\n return metaEntries?.some((entry) => isSshdPortMetaEntry(entry)) ?? false\n}\n\nfunction hasSystemRebootMeta(metaEntries: ModuleResult[\"meta\"]): boolean {\n return metaEntries?.some((entry) => isSystemRebootMetaEntry(entry)) ?? false\n}\n\nfunction isConnectedToReportedSshdPort(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): boolean {\n const connectedPort = ssh.getConnectionInfo().port\n return (\n connectedPort > 0 &&\n (metaEntries?.some((entry) => isSshdPortMetaEntry(entry) && entry.port === connectedPort) ??\n false)\n )\n}\n\nfunction shouldSkipPortChangeReconnect(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): boolean {\n return hasSystemRebootMeta(metaEntries) || isConnectedToReportedSshdPort(ssh, metaEntries)\n}\n\nasync function initializeEnvironment(\n options: RunOptions,\n definition: ServerDefinition\n): Promise<Environment> {\n const dotEnvironment =\n options.envFile == null ? undefined : await loadDotEnvironment(options.envFile)\n return mergeEnvironment({}, dotEnvironment, definition.env, options.envOverrides)\n}\n\nasync function handlePortChange(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"]\n): Promise<void> {\n if (!hasSshdPortMeta(metaEntries)) return\n const addedPorts = addSshdPorts(ssh, metaEntries)\n\n // Skip reconnect when a reboot is pending — the reboot handler will\n // reconnect on all registered ports (including the newly added ones).\n if (shouldSkipPortChangeReconnect(ssh, metaEntries)) return\n\n try {\n await ssh.reconnect()\n } catch (error) {\n // R-0000207: distinguish reconnect-failure from a post-connect failure\n // (e.g. host-key persist, sudo probe). If the SSH client is still\n // attached to a port, the reconnect itself succeeded and the new ports\n // are reachable — keep them registered so subsequent reuse works. Only\n // roll back when no port actually held a connection. Mirrors the\n // rollback behavior in modules/sshd.ts:applySshdPort.\n const connectedPort = ssh.getConnectionInfo().port\n const reconnectSucceeded = connectedPort > 0\n if (!reconnectSucceeded) {\n for (const port of addedPorts) ssh.removePort(port)\n }\n const portList = addedPorts.join(\", \")\n const diagnostic = reconnectSucceeded\n ? `Reconnect on port(s) ${portList} succeeded but a follow-up step failed: ${String(error)}.`\n : `Failed to reconnect on port(s) ${portList} after port change: ${String(error)}. ` +\n `Verify that port(s) ${portList} are allowed by the server's firewall rules.`\n console.error(diagnostic)\n throw error\n }\n}\n\nasync function handleReboot(\n ssh: SshConnectionImpl,\n metaEntries: ModuleResult[\"meta\"],\n rebootGrace: RebootGraceContext\n): Promise<void> {\n if (!(metaEntries?.some((entry) => isSystemRebootMetaEntry(entry)) ?? false)) return\n\n const hostEntry = metaEntries?.find((entry) => isSystemHostMetaEntry(entry))\n if (hostEntry != null) {\n ssh.updateHost(hostEntry.host)\n }\n\n // Wait an initial grace period before the first reconnect so attempts\n // during shutdown/boot do not waste the maxReconnectAttempts budget. The\n // wait short-circuits when a shutdown signal arrives — both via the\n // synchronous shutdown getter (already set when this is reached) and via\n // the AbortSignal that fires on a fresh SIGINT/SIGTERM mid-sleep.\n await sleepRespectingShutdown(rebootGrace.graceMs, rebootGrace.shutdownSignal, {\n abortSignal: rebootGrace.abortSignal,\n keepAlive: true,\n })\n\n try {\n await ssh.reconnect()\n } catch (error) {\n console.error(`Failed to reconnect after reboot: ${String(error)}`)\n throw error\n }\n}\n\nasync function applyRunnerControlPlaneMeta(\n ssh: SshConnectionImpl,\n step: Pick<OrchestrationStep, \"meta\" | \"status\">,\n rebootGrace: RebootGraceContext\n): Promise<void> {\n if (step.status === \"failed\") return\n if (step.meta == null) return\n assertValidModuleMetaEntries(step.meta)\n await handlePortChange(ssh, step.meta)\n await handleReboot(ssh, step.meta, rebootGrace)\n}\n\nasync function handleMetaAndBuildResult(parameters: {\n dryRun?: boolean\n environment: Environment\n rebootGrace: RebootGraceContext\n result: ModuleResult\n ssh: SshConnectionImpl\n}): Promise<StepResult> {\n const { dryRun, environment, rebootGrace, result, ssh } = parameters\n let currentEnvironment = environment\n\n if (result.meta != null) {\n currentEnvironment = await mergeEnvironmentFromMeta(currentEnvironment, result.meta)\n if (dryRun !== true) {\n await applyRunnerControlPlaneMeta(\n ssh,\n { meta: result.meta, status: result.status },\n rebootGrace\n )\n }\n }\n\n return {\n env: currentEnvironment,\n flushSignals: result._flushSignals,\n shouldBreak: shouldBreakAfterResult(result),\n status: result.status,\n stopRun: result._stopRun,\n }\n}\n\nasync function runDryRunRecipeModule(parameters: {\n diff: boolean\n environment: Environment\n recipeModule: RecipeModule\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n verbose: boolean\n}): Promise<StepResult> {\n return dryRunRecipeModule({\n environment: parameters.environment,\n options: { diff: parameters.diff, verbose: parameters.verbose },\n recipeModule: parameters.recipeModule,\n shutdownSignal: parameters.shutdownSignal,\n ssh: parameters.ssh,\n })\n}\n\nasync function runRecipeModule(parameters: {\n diff: boolean\n dryRun: boolean\n environment: Environment\n rebootGrace: RebootGraceContext\n recipeModule: RecipeModule\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<StepResult> {\n const {\n diff,\n dryRun,\n environment,\n rebootGrace,\n recipeModule,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n } = parameters\n try {\n if (dryRun) {\n return await runDryRunRecipeModule({\n diff,\n environment,\n recipeModule,\n shutdownSignal,\n ssh,\n verbose,\n })\n }\n\n // check() iterates all child modules; apply() checks them again internally via executeModules().\n startModuleSpinner(recipeModule.name)\n const checkResult = await recipeModule.check(ssh, environment)\n if (checkResult === \"ok\") {\n printModuleResult(recipeModule.name, \"ok\")\n return { env: environment, shouldBreak: false, status: \"ok\" }\n }\n\n const result = await recipeModule.apply(ssh, environment, {\n async onChildStep(step) {\n await applyRunnerControlPlaneMeta(ssh, step, rebootGrace)\n },\n async onSignalStep(step) {\n await applyRunnerControlPlaneMeta(ssh, step, rebootGrace)\n },\n shutdownSignal,\n signalHooks: {\n onSignalFinished(status: ModuleStatus) {\n stats.update(status)\n },\n onSignalStarted() {\n stats.incrementSignals()\n },\n },\n verbose,\n })\n return await handleMetaAndBuildResult({ environment, rebootGrace, result, ssh })\n } catch (error) {\n return handleCaughtStepError({\n environment,\n error,\n moduleName: recipeModule.name,\n shutdownSignal,\n verbose,\n })\n }\n}\n\nasync function executeModuleForApply(parameters: {\n connection: null | SshConnectionImpl\n currentEnvironment: Environment\n dryRun: boolean\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n}): Promise<ModuleResult> {\n const { connection, currentEnvironment, dryRun, rebootGrace, ssh, targetModule } = parameters\n if (dryRun && targetModule._applyDryRun != null) {\n return targetModule._applyDryRun(connection, currentEnvironment, {\n shutdownSignal: parameters.shutdownSignal,\n })\n }\n if (targetModule._supportsChildStepHook === true) {\n return targetModule.apply(connection, currentEnvironment, {\n async onChildStep(step) {\n await applyRunnerControlPlaneMeta(ssh, step, rebootGrace)\n },\n shutdownSignal: parameters.shutdownSignal,\n })\n }\n return targetModule.apply(connection, currentEnvironment)\n}\n\nasync function applyModule(parameters: {\n currentEnvironment: Environment\n diff?: boolean\n dryRun?: boolean\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}): Promise<StepResult> {\n const {\n currentEnvironment,\n diff = false,\n dryRun = false,\n rebootGrace,\n ssh,\n targetModule,\n verbose,\n } = parameters\n const connection = targetModule.local === true ? null : ssh\n const result = await executeModuleForApply({\n connection,\n currentEnvironment,\n dryRun,\n rebootGrace,\n shutdownSignal: parameters.shutdownSignal,\n ssh,\n targetModule,\n })\n const stepResult = await handleMetaAndBuildResult({\n dryRun,\n environment: currentEnvironment,\n rebootGrace,\n result,\n ssh,\n })\n const detail = dryRun ? (result._dryRunDetail ?? \"(dry-run)\") : result.detail\n const diffOutput = dryRun && diff ? result.diff : undefined\n printModuleResult(targetModule.name, result.status, detail, diffOutput)\n if (result.status === \"failed\" && result.error != null) {\n printCommandFailure(result.error, verbose)\n }\n return stepResult\n}\n\nasync function checkRegularModule(parameters: {\n env: Environment\n ssh: SshConnectionImpl\n targetModule: Module\n}): Promise<\"needs-apply\" | \"ok\"> {\n const { env, ssh, targetModule } = parameters\n const connection = targetModule.local === true ? null : ssh\n startModuleSpinner(targetModule.name)\n return targetModule.check(connection, env)\n}\n\nfunction buildDryRunChangedResult(environment: Environment): StepResult {\n return { env: environment, shouldBreak: false, status: \"changed\" }\n}\n\ntype RegularModuleArguments = {\n diff: boolean\n dryRun: boolean\n env: Environment\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n targetModule: Module\n verbose: boolean\n}\n\nasync function runRegularModule(parameters: RegularModuleArguments): Promise<StepResult> {\n const { diff, dryRun, env, rebootGrace, ssh, targetModule, verbose } = parameters\n const shutdownSignal = parameters.shutdownSignal\n\n try {\n const checkResult = await checkRegularModule({ env, ssh, targetModule })\n\n if (checkResult === \"ok\") {\n printModuleResult(targetModule.name, \"ok\")\n return { env, shouldBreak: false, status: \"ok\" }\n }\n\n if (dryRun) {\n if (shouldExecuteApplyDuringDryRun(targetModule, diff)) {\n return await applyCheckedModule({\n currentEnvironment: env,\n diff,\n dryRun: true,\n rebootGrace,\n shutdownSignal,\n ssh,\n targetModule,\n verbose,\n })\n }\n printModuleResult(targetModule.name, \"changed\", \"(dry-run)\")\n return buildDryRunChangedResult(env)\n }\n\n return await applyCheckedModule({\n currentEnvironment: env,\n dryRun: false,\n rebootGrace,\n shutdownSignal,\n ssh,\n targetModule,\n verbose,\n })\n } catch (error) {\n return handleCaughtStepError({\n environment: env,\n error,\n moduleName: targetModule.name,\n shutdownSignal,\n verbose,\n })\n }\n}\n\ntype LoopArguments = {\n definitionSignals?: Module[]\n diff: boolean\n dryRun: boolean\n env: Environment\n modules: Module[]\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\ntype ModuleLoopState = {\n currentEnvironment: Environment\n signalsPending: boolean\n stopRun?: true\n}\n\nfunction updateLoopSignalState(input: {\n currentSignalsPending: boolean\n result: StepResult\n stats: RunStats\n}): boolean {\n if (input.result.status == null) return input.currentSignalsPending\n input.stats.update(input.result.status)\n return input.result.status === \"changed\" ? true : input.currentSignalsPending\n}\n\nfunction shouldFlushTopLevelSignals(input: {\n definitionSignals?: Module[]\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalsPending: boolean\n stats: RunStats\n stepResult: StepResult\n}): input is {\n definitionSignals: Module[]\n dryRun: boolean\n shutdownSignal: () => NodeJS.Signals | null\n signalsPending: boolean\n stats: RunStats\n stepResult: { flushSignals: true } & StepResult\n} {\n return (\n input.stepResult.flushSignals === true &&\n !input.dryRun &&\n input.shutdownSignal() == null &&\n input.signalsPending &&\n input.stats.failed === 0 &&\n input.definitionSignals != null\n )\n}\n\nasync function flushPendingTopLevelSignals(input: {\n currentEnvironment: Environment\n definitionSignals: Module[]\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<SignalRunStatus> {\n return runSignals({\n env: input.currentEnvironment,\n rebootGrace: input.rebootGrace,\n shutdownSignal: input.shutdownSignal,\n signals: input.definitionSignals,\n ssh: input.ssh,\n stats: input.stats,\n verbose: input.verbose,\n })\n}\n\nfunction applyLoopResultToState(\n state: ModuleLoopState,\n result: StepResult,\n stats: RunStats\n): ModuleLoopState {\n return {\n currentEnvironment: result.env,\n signalsPending: updateLoopSignalState({\n currentSignalsPending: state.signalsPending,\n result,\n stats,\n }),\n stopRun: result.stopRun === true ? true : state.stopRun,\n }\n}\n\nasync function flushTopLevelSignalsIfRequested(parameters: {\n definitionSignals?: Module[]\n dryRun: boolean\n loopState: ModuleLoopState\n rebootGrace: RebootGraceContext\n result: StepResult\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<{ nextSignalsPending: boolean; outcome: \"break\" | \"continue\" }> {\n if (\n !shouldFlushTopLevelSignals({\n definitionSignals: parameters.definitionSignals,\n dryRun: parameters.dryRun,\n shutdownSignal: parameters.shutdownSignal,\n signalsPending: parameters.loopState.signalsPending,\n stats: parameters.stats,\n stepResult: parameters.result,\n })\n ) {\n return { nextSignalsPending: parameters.loopState.signalsPending, outcome: \"continue\" }\n }\n const definitionSignals = parameters.definitionSignals\n if (definitionSignals == null) {\n return { nextSignalsPending: parameters.loopState.signalsPending, outcome: \"continue\" }\n }\n\n const signalStatus = await flushPendingTopLevelSignals({\n currentEnvironment: parameters.loopState.currentEnvironment,\n definitionSignals,\n rebootGrace: parameters.rebootGrace,\n shutdownSignal: parameters.shutdownSignal,\n ssh: parameters.ssh,\n stats: parameters.stats,\n verbose: parameters.verbose,\n })\n return {\n nextSignalsPending: false,\n outcome: signalStatus === \"failed\" ? \"break\" : \"continue\",\n }\n}\n\nasync function createModuleStepPromise(parameters: {\n currentEnvironment: Environment\n currentModule: Module\n diff: boolean\n dryRun: boolean\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}): Promise<StepResult> {\n const {\n currentEnvironment,\n currentModule,\n diff,\n dryRun,\n rebootGrace,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n } = parameters\n\n return isRecipe(currentModule)\n ? runRecipeModule({\n diff,\n dryRun,\n environment: currentEnvironment,\n rebootGrace,\n recipeModule: currentModule,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n : runRegularModule({\n diff,\n dryRun,\n env: currentEnvironment,\n rebootGrace,\n shutdownSignal,\n ssh,\n targetModule: currentModule,\n verbose,\n })\n}\n\nasync function runModuleLoop(parameters: LoopArguments): Promise<{\n env: Environment\n signalsPending: boolean\n stopRun?: true\n}> {\n const {\n definitionSignals,\n diff,\n dryRun,\n modules,\n rebootGrace,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n } = parameters\n // R-0000842: hold loop state in a single `let` binding and reassign\n // wholesale each iteration. The previous code created the value as\n // `const` and used `Object.assign(loopState, applyLoopResultToState(...))`\n // to mutate the binding in place. Reassigning a plain object is easier to\n // reason about — it makes the per-iteration transition explicit and\n // avoids the fragile contract that `applyLoopResultToState` must return a\n // payload that fully describes every field on `ModuleLoopState`.\n let loopState: ModuleLoopState = {\n currentEnvironment: parameters.env,\n signalsPending: false,\n stopRun: undefined,\n }\n\n for (const currentModule of modules) {\n // A module already running when the signal arrived completes normally\n // and its result is still counted in stats before the loop exits here.\n if (shutdownSignal() != null) break\n const stepPromise = createModuleStepPromise({\n currentEnvironment: loopState.currentEnvironment,\n currentModule,\n diff,\n dryRun,\n rebootGrace,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n\n // eslint-disable-next-line no-await-in-loop\n const result = await stepPromise\n\n loopState = applyLoopResultToState(loopState, result, stats)\n // eslint-disable-next-line no-await-in-loop\n const flushResult = await flushTopLevelSignalsIfRequested({\n definitionSignals,\n dryRun,\n loopState,\n rebootGrace,\n result,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n loopState = { ...loopState, signalsPending: flushResult.nextSignalsPending }\n if (flushResult.outcome === \"break\") break\n if (result.shouldBreak) break\n }\n\n return {\n env: loopState.currentEnvironment,\n signalsPending: loopState.signalsPending,\n stopRun: loopState.stopRun,\n }\n}\n\ntype SignalArguments = {\n env: Environment\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n signals: Module[]\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\nasync function runSignals(parameters: SignalArguments): Promise<SignalRunStatus> {\n const { env, rebootGrace, shutdownSignal, signals, ssh, stats, verbose } = parameters\n return runSignalModules({\n environment: env,\n hooks: {\n onSignalFinished(status: ModuleStatus) {\n stats.update(status)\n },\n onSignalStarted() {\n stats.incrementSignals()\n },\n },\n async onSignalStep(step) {\n await applyRunnerControlPlaneMeta(ssh, step, rebootGrace)\n },\n shutdownSignal,\n signals,\n ssh,\n verbose,\n })\n}\n\nfunction throwIfShutdownRequested(shutdownSignal: () => NodeJS.Signals | null): void {\n const signal = shutdownSignal()\n if (signal == null) return\n throw new Error(`Bootstrap interrupted by ${signal}`)\n}\n\nasync function connectAndRegister(parameters: {\n definition: ServerDefinition\n options: RunOptions\n promptAbortSignal: AbortSignal\n setSsh: (c: SshConnectionImpl) => void\n shutdownSignal: () => NodeJS.Signals | null\n}): Promise<SshConnectionImpl> {\n const { definition, options, promptAbortSignal, setSsh, shutdownSignal } = parameters\n const sshConfig = {\n ...definition.ssh,\n ports: [...definition.ssh.ports],\n ...(options.reconnectTimeout == null ? {} : { reconnectTimeout: options.reconnectTimeout }),\n }\n const ssh = new SshConnectionImpl(definition.host, sshConfig)\n setSsh(ssh)\n throwIfShutdownRequested(shutdownSignal)\n await ssh.connect({ abortSignal: promptAbortSignal })\n throwIfShutdownRequested(shutdownSignal)\n return ssh\n}\n\ntype ExecuteRunArguments = {\n definition: ServerDefinition\n diff: boolean\n dryRun: boolean\n environment: Environment\n rebootGrace: RebootGraceContext\n shutdownSignal: () => NodeJS.Signals | null\n ssh: SshConnectionImpl\n stats: RunStats\n verbose: boolean\n}\n\nasync function executeRun(parameters: ExecuteRunArguments): Promise<void> {\n const {\n definition,\n diff,\n dryRun,\n environment,\n rebootGrace,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n } = parameters\n\n printRecipeHeader(definition.name)\n const loopResult = await runModuleLoop({\n definitionSignals: definition.signals,\n diff,\n dryRun,\n env: environment,\n modules: definition.run,\n rebootGrace,\n shutdownSignal,\n ssh,\n stats,\n verbose,\n })\n const finalEnvironment = loopResult.env\n\n if (\n !dryRun &&\n shutdownSignal() == null &&\n loopResult.signalsPending &&\n stats.failed === 0 &&\n definition.signals != null\n )\n await runSignals({\n env: finalEnvironment,\n rebootGrace,\n shutdownSignal,\n signals: definition.signals,\n ssh,\n stats,\n verbose,\n })\n\n printSummary(stats)\n}\n\nfunction rethrowIfNotShutdown(error: unknown, shutdownSignal: () => NodeJS.Signals | null): void {\n if (shutdownSignal() == null) throw error\n}\n\n/**\n * Tear down per-run resources: shutdown signal listeners, the runner abort\n * signal, and the ssh connection.\n *\n * R-0000518: the process-scoped secret sink is NOT cleared here. The sink in\n * `secretSink.ts` is reference-counted via `withRegisteredSecrets` /\n * `registerSecret` / `unregisterSecret`: every module that\n * registers a secret also releases it through `try/finally`, so the sink\n * drains on its own once each scope closes. Calling\n * `clearRegisteredSecrets()` unconditionally on teardown would wipe secrets\n * belonging to a concurrent `runPlaybook` invocation that shares the same\n * Node process — a parallel run would lose its redaction context the moment\n * the first run finishes. R-0000041's original goal (no bleed across runs)\n * is preserved by the reference-counting protocol itself.\n *\n * @param parameters - Cleanup context.\n * @param parameters.handleShutdownSignal - Listener installed for SIGINT/SIGTERM.\n * @param parameters.ssh - The SSH connection that may need disconnecting.\n */\nfunction teardownPlaybookResources(parameters: {\n handleShutdownSignal: (signal: NodeJS.Signals) => void\n ssh: SshConnectionImpl | undefined\n}): void {\n stopLiveModuleOutput(true)\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const)\n getSignalBus().off(signal, parameters.handleShutdownSignal)\n // R-0000743: the abort signal lives inside the AsyncLocalStorage scope\n // installed by `withRunnerAbortSignal` in `runPlaybook`. The scope ends\n // automatically once the wrapped body returns, so no explicit clear is\n // needed here and a parallel run is no longer affected.\n parameters.ssh?.disconnect()\n}\n\nfunction initializeRunPlaybookContext(options: RunOptions): {\n handleShutdownSignal: (signal: NodeJS.Signals) => void\n promptAbortSignal: AbortSignal\n rebootGrace: RebootGraceContext\n setSsh: (connection: SshConnectionImpl) => void\n shutdownSignal: () => NodeJS.Signals | null\n} {\n const { handleShutdownSignal, promptAbortSignal, setSsh, shutdownAbortSignal, shutdownSignal } =\n setupShutdownHandlers()\n // R-0000743: the abort signal is no longer installed eagerly into a\n // module-global slot. `runPlaybook` wraps its body in\n // `withRunnerAbortSignal(promptAbortSignal, …)` so the signal lives inside\n // an AsyncLocalStorage scope and parallel runs never observe each other.\n // R-0000203: scope the reboot grace state (duration, shutdown getter, abort\n // signal) to this invocation so concurrent `runPlaybook` calls cannot race\n // on shared mutable values.\n const rebootGrace = createRebootGraceContext(options, shutdownSignal, shutdownAbortSignal)\n return { handleShutdownSignal, promptAbortSignal, rebootGrace, setSsh, shutdownSignal }\n}\n\nexport async function runPlaybook(\n definition: ServerDefinition,\n options: RunOptions = {}\n): Promise<void> {\n validateServerDefinition(definition, { allowEmptyRun: true })\n const { diff = false, dryRun = false, verbose = false } = options\n const environment = await initializeEnvironment(options, definition)\n const { handleShutdownSignal, promptAbortSignal, rebootGrace, setSsh, shutdownSignal } =\n initializeRunPlaybookContext(options)\n const stats = new RunStats()\n\n // R-0000743: wrap the entire playbook lifecycle (connect, executeRun,\n // teardown, exit-code resolution) in the per-run AsyncLocalStorage scope\n // so every async branch that the run spawns observes its own abort signal\n // and a parallel `runPlaybook` invocation never overwrites it.\n await withRunnerAbortSignal(promptAbortSignal, async () => {\n let ssh: SshConnectionImpl | undefined\n\n printRunContext({\n dryRun,\n host: definition.host,\n name: definition.name,\n ports: definition.ssh.ports,\n })\n\n // No catch block: connect errors propagate to cli.ts, which prints them and exits with code 2.\n try {\n const connectedSsh = await connectAndRegister({\n definition,\n options,\n promptAbortSignal,\n setSsh(connection) {\n ssh = connection\n setSsh(connection)\n },\n shutdownSignal,\n })\n ssh = connectedSsh\n await withRunScopedSecrets(async () =>\n executeRun({\n definition,\n diff,\n dryRun,\n environment,\n rebootGrace,\n shutdownSignal,\n ssh: connectedSsh,\n stats,\n verbose,\n })\n )\n } catch (error) {\n rethrowIfNotShutdown(error, shutdownSignal)\n } finally {\n teardownPlaybookResources({ handleShutdownSignal, ssh })\n }\n\n resolveExitCode(shutdownSignal(), stats)\n })\n}\n"],"mappings":"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","names":["AsyncLocalStorage","pc","AsyncLocalStorage","resolve","AsyncLocalStorage","timingSafeEqual","readFile","stat","timingSafeEqual","timingSafeEqual","stat","readFile","isRecord","isRecord","AsyncLocalStorage","createHash","timingSafeEqual","createReadStream","readFile","stat","homedir","join","join","resolve","join","resolve","stat","createHash","createReadStream","homedir","join","resolve","readFile","timingSafeEqual","error","resolve","ENVIRONMENT_KEY_PATTERN","validateServerDefinition","printCauseChain","pc","AsyncLocalStorage"]}