import type { ComparePdfOptions } from '../types/ComparePdfOptions.js'; export declare function validateDiffsOutputFolder(diffsOutputFolder: ComparePdfOptions['diffsOutputFolder']): string; export declare function ensureDiffOutputDirectory(outputPath: string, diffsOutputFolder: string): void; export declare function assertDiffOutputPathUsesRealFilesystemEntries(diffFilePath: string, diffsOutputFolder: string): void; /** * Atomically creates a fresh, randomly-named tempfile in the same directory as the diff leaf * and returns its path. The third-party comparator is then directed at this tempfile, and the * result is later promoted into `diffFilePath` via {@link publishDiffOutputTempfile}. * * This two-step "stage then rename" pattern is materially stronger than writing directly to * `diffFilePath`: * - The tempfile name carries an unpredictable random suffix, so an attacker with write * access in `diffsOutputFolder` cannot pre-plant a symlink at the future tempfile path. * - The tempfile is opened with `O_CREAT | O_EXCL | O_NOFOLLOW`, so any racing attempt to * pre-create a regular file or symlink at the same path during the open is rejected * (CWE-61 / CWE-367). * - The eventual atomic `renameSync` (see {@link publishDiffOutputTempfile}) replaces any * pre-existing entry at `diffFilePath` — including a symlink planted there during the * write window — without ever following the destination's symlink. */ export declare function createSecureDiffOutputTempfile(diffFilePath: string, diffsOutputFolder: string): string; export type PublishDiffOutputTempfileOptions = { /** * `true` when the comparator was expected to produce a diff PNG (mismatched pages above * threshold). A missing or empty tempfile in that case becomes a * `ComparePdfConfigurationError` because the diff bytes must already be on disk; their * absence implies attacker deletion, a symlink-redirected write, or a silent comparator * failure that callers must surface. * * `false` when the comparator was expected to skip writing (matching pages). Missing or * empty tempfile is then the normal "no diff to report" case; any stale leaf at the final * path is removed so on-disk state matches the pre-fix behavior. */ expectDiffWritten: boolean; }; /** * Promotes the staged tempfile to `diffFilePath` via atomic `renameSync`. This is the * core anti-TOCTOU primitive: `rename()` replaces any pre-existing file or symlink at * the destination as a single directory-entry operation without ever following the * destination. A symlink planted at `diffFilePath` during the write window is therefore * harmlessly overwritten — the attacker's chosen target is never opened by this library. * * Throws `ComparePdfConfigurationError` when: * - the tempfile is no longer a regular file (symlink swap during the comparator's write * — bytes may have leaked through the swapped symlink to an attacker-chosen target); * - a diff was expected but the tempfile is missing or zero bytes; * - the tempfile cannot be inspected, or rename fails for reasons other than absence. */ export declare function publishDiffOutputTempfile(tempfilePath: string, diffFilePath: string, diffsOutputFolder: string, options: PublishDiffOutputTempfileOptions): void; /** * Best-effort removal of a diff output file. Callers use this to roll back the staged * tempfile when the third-party comparator throws before writing, and to clear stale * leaves at the published path when no diff was produced for matching pages. */ export declare function discardDiffOutputLeaf(diffFilePath: string): void; export declare function assertCanonicalDiffOutputPath(diffFilePath: string, diffsOutputFolder: string): void;