/** * One-time scrub of secrets already written to disk. * * Before #806 Crewly typed `export GEMINI_API_KEY=…` (and other keys) into * agent shells, so keys sit in ~/.crewly/logs/sessions/*.log, in the gzipped * rotations under ~/.crewly/logs/archive/, and in the user's shell history. * This service runs every such file through the same redactor the live * session-log writer uses (utils/secret-redactor). * * - Dry run by default: reports what WOULD be masked; nothing is written. * - Reports counts only — never a secret value, never a matched line. * - Idempotent: a second `apply` finds 0 (masks are never re-masked) and * rewrites nothing. * - Files are read and written as bytes (latin1): zsh stores history * "metafied" (not valid UTF-8), so a UTF-8 round trip would corrupt it. * Every pattern is ASCII, so byte-wise matching is exact. * - Rewrites are atomic (temp file + rename) and keep the file mode. * * Used by `crewly security scrub-logs [--apply]`. * * @module services/security/secret-scrub.service */ import { type SecretEnvValue } from '../../utils/secret-env.js'; /** Which kind of file a scrub target is. */ export type ScrubTargetKind = 'session-log' | 'session-log-archive' | 'shell-history'; /** Per-file outcome (path + counts only, never content). */ export interface ScrubFileResult { /** Absolute path */ path: string; /** Kind of file */ kind: ScrubTargetKind; /** Secrets found (dry run) or masked (apply) */ secrets: number; /** Whether the file was rewritten */ rewritten: boolean; /** Error message when the file could not be read/written (no content) */ error?: string; } /** Totals for one scrub run. */ export interface ScrubSummary { /** True when files were rewritten; false for a dry run */ applied: boolean; /** Files examined */ filesScanned: number; /** Files that contain (dry run) / contained (apply) at least one secret */ filesWithSecrets: number; /** Files rewritten */ filesRewritten: number; /** Total secrets found or masked */ secrets: number; /** Files that could not be processed */ errors: number; /** Per-file detail */ files: ScrubFileResult[]; } /** Options for a scrub run. */ export interface ScrubOptions { /** Rewrite files (default false: dry run) */ apply?: boolean; /** CREWLY_HOME override (default getCrewlyHomePath()) */ crewlyHome?: string; /** Home directory whose shell history is scrubbed (default os.homedir()) */ homeDir?: string; /** Also scrub shell history files (default true) */ includeShellHistory?: boolean; /** Env maps whose secret-named values are masked by exact value (default [process.env]) */ envs?: ReadonlyArray>; } /** A file to scrub. */ interface ScrubTarget { path: string; kind: ScrubTargetKind; } /** * Collects exact secret values from settings.json `apiKeys` (every string * leaf) so keys that were typed into shells are masked even when their * shape is unknown. Read directly from the file: no settings service side * effects (migrations) in a scrub. * * @param crewlyHome - CREWLY_HOME path * @returns Secret values, labelled SETTINGS_ */ export declare function collectSettingsSecretValues(crewlyHome: string): Promise; /** * Lists the files a scrub covers: session logs, their gzipped rotations and * (optionally) the user's shell history files. Missing files are skipped. * * @param crewlyHome - CREWLY_HOME path * @param homeDir - User home directory * @param includeShellHistory - Include shell history files * @returns Existing regular files to scrub */ export declare function listScrubTargets(crewlyHome: string, homeDir: string, includeShellHistory: boolean): Promise; /** * Runs the scrub (dry run unless `apply`). * * @param options - See ScrubOptions * @returns Totals and per-file counts — no secret values * * @example * ```ts * const dry = await scrubSecretsOnDisk(); // what would change * const done = await scrubSecretsOnDisk({ apply: true }); * ``` */ export declare function scrubSecretsOnDisk(options?: ScrubOptions): Promise; export {}; //# sourceMappingURL=secret-scrub.service.d.ts.map