/** * Fingerprint of one exact drop: same path, same new bytes, same set of * secret values being removed. The model confirms by re-issuing the identical * call, so the sentinel must match nothing broader — including the dropped * values themselves: if the file's secrets change between the deny and the * retry, the retry is dropping something the model was never warned about, * and must be re-denied. * @param {string} filePath * @param {string} content * @param {string[]} [dropped] the secret values the Write would remove * @returns {string} */ export function dropFingerprint(filePath: string, content: string, dropped?: string[]): string; /** * The confirm sentinel's path for one fingerprint. Predictable and * world-visible like the alert markers, hence the markerIsTrusted read. * @param {string} fingerprint * @returns {string} */ export function confirmMarkerPath(fingerprint: string): string; /** * Delete this project's confirm sentinels older than {@link CONFIRM_TTL_MS}. * * consumeConfirm removes the sentinel it honors, but an abandoned confirmation — * denied, never retried — is removed by nothing, so the store grows for the life * of the machine. Called from SessionStart, the one touchpoint that runs once per * session rather than once per tool call. A sentinel past the TTL is already inert * (consumeConfirm refuses it), so this reclaims space without changing a verdict. * @returns {void} */ export function sweepStaleConfirms(): void; /** * Whether git tracks `filePath`. Exit 0 is tracked; exit 1 (untracked) and 128 * (not a repository) both mean "no git recovery exists", which is what the * guard actually cares about. A spawn-level failure (no git binary) means we * cannot tell — report tracked so the guard skips (fail open) rather than * denying on missing tooling. * @param {string} filePath * @param {typeof spawnSync} [spawn] * @returns {boolean} */ export function gitTracked(filePath: string, spawn?: typeof spawnSync): boolean; /** * Deny a first-time Write that would drop a redacted secret from an untracked * file, or null to let it pass. `toolInput` is the FINAL Write input — after * rehydration substituted any placeholders — so a preserved secret shows up as * its real value in `content`. `io` is the rehydrate-shaped I/O bag (readFile / * redact / redactMap). Injectable seams: `isTracked` (the git probe), * `confirmSeen`/`recordConfirm` (the sentinel state). * @param {{file_path?: unknown, content?: unknown}} toolInput * @param {import("agent-sanitizer/rehydrate").RehydrateIo} io * @param {{ * isTracked?: (filePath: string) => boolean, * confirmSeen?: (fingerprint: string) => boolean, * recordConfirm?: (fingerprint: string) => void, * }} [opts] * @returns {Promise<{deny: string} | null>} */ export function secretDropGuard(toolInput: { file_path?: unknown; content?: unknown; }, io: import("agent-sanitizer/rehydrate").RehydrateIo, opts?: { isTracked?: (filePath: string) => boolean; confirmSeen?: (fingerprint: string) => boolean; recordConfirm?: (fingerprint: string) => void; }): Promise<{ deny: string; } | null>; /** * Compose a Layer-4 rehydrator with this guard: for a Write the rehydrator * did not deny, run the guard on the FINAL content — after any placeholder * substitution — so a rehydrated secret counts as preserved and only a * genuinely dropped one can trip it. A rehydrate deny short-circuits (the * guard never runs), non-Write tools skip the guard entirely, and a guard * deny wins over a pass/rewrite result. * @typedef {{updatedInput: any, context: string} | {deny: string} | null} RehydrateResult * @param {(tool: string, toolInput: any) => Promise} rehydrate * @param {import("agent-sanitizer/rehydrate").RehydrateIo} io * @param {typeof secretDropGuard} [guard] * @returns {(tool: string, toolInput: any) => Promise} */ export function withSecretDropGuard(rehydrate: (tool: string, toolInput: any) => Promise, io: import("agent-sanitizer/rehydrate").RehydrateIo, guard?: typeof secretDropGuard): (tool: string, toolInput: any) => Promise; /** A confirm sentinel older than this is stale, not a confirmation. */ export const CONFIRM_TTL_MS: number; /** * Compose a Layer-4 rehydrator with this guard: for a Write the rehydrator * did not deny, run the guard on the FINAL content — after any placeholder * substitution — so a rehydrated secret counts as preserved and only a * genuinely dropped one can trip it. A rehydrate deny short-circuits (the * guard never runs), non-Write tools skip the guard entirely, and a guard * deny wins over a pass/rewrite result. */ export type RehydrateResult = { updatedInput: any; context: string; } | { deny: string; } | null; import { spawnSync } from "node:child_process";