/** * Bounded, hardened `git` invocation for read-only probes (T12511). * * A probe over hundreds of checkouts must neither hang nor run code the * repositories configure. This module owns both properties. * * ## Bounded * * - Every call has an absolute `deadline`. Git runs in its own process group * (POSIX `detached`), and at the deadline the whole group is SIGKILLed * (`taskkill /T /F` on Windows). The pipes are destroyed and the child is * unref'd, so a grandchild that escaped the group cannot keep the CLI alive. * - Output is capped at `maxOutputBytes` per call and optionally by a * run-wide {@link GitOutputBudget}; past either the group is killed. * - While any call is live, `SIGINT`/`SIGTERM`/`SIGHUP` and `exit` kill every * live group. A detached group is not in the terminal's foreground group, so * Ctrl-C would otherwise orphan it. The signal is re-raised when no other * listener handles it, which keeps Node's default exit behaviour. Teardown * (`markShuttingDown`) also kills them. * * ## Hardened * * Every call carries {@link BASE_CONFIG} — no fsmonitor, no hooks, a protocol * allowlist (http/https/ssh/file), empty gitProxy/alternateRefsCommand/ * credential helper/askpass, batch-mode ssh, no auto gc — plus caller pairs * such as {@link filterConfig}. Overrides travel as `GIT_CONFIG_COUNT` / * `GIT_CONFIG_KEY_n` / `GIT_CONFIG_VALUE_n`, never `-c`, because `-c` splits * at the first `=` and a key may contain one. The environment disables every * prompt (`GIT_TERMINAL_PROMPT=0`, empty `GIT_ASKPASS`/`SSH_ASKPASS`, * `SSH_ASKPASS_REQUIRE=never`, `GCM_INTERACTIVE=never`), forbids partial-clone * lazy fetches (`GIT_NO_LAZY_FETCH=1`), sets `GIT_OPTIONAL_LOCKS=0`, and drops * `GIT_SSH`/`GIT_SSH_COMMAND`/`GIT_PROXY_COMMAND`/`GIT_EXTERNAL_DIFF` and any * inherited `GIT_CONFIG_PARAMETERS`/`GIT_CONFIG_COUNT`/`GIT_CONFIG_GLOBAL`/ * `GIT_CONFIG_SYSTEM`. * * @task T12511 */ /** Default cap on one call's stdout + stderr, bytes. */ export declare const DEFAULT_GIT_MAX_OUTPUT_BYTES: number; /** One config override: `[key, value]`. */ export type GitConfigPair = readonly [key: string, value: string]; /** * Overrides applied to EVERY probe call. Any git command can reach the * network (a partial clone fetches missing blobs lazily), so the transport * pins apply everywhere, not only to `fetch`. * * - `core.fsmonitor=false`: no fsmonitor hook script, no fsmonitor daemon. * - `core.hooksPath=`: no hook. * - `protocol.allow=never` plus an http/https/ssh/file allowlist: no `ext::`, * `fd::` or `::` remote helper, no `git://` (whose `core.gitProxy` * runs a command). * - Empty `core.gitProxy`, `core.alternateRefsCommand`, `credential.helper` * (resets the list) and `core.askPass`; plain batch-mode ssh. * - `gc.auto=0`, `maintenance.auto=false`: no detached gc holding repo locks. */ export declare const BASE_CONFIG: readonly GitConfigPair[]; /** * Overrides that disable the named filter drivers. Git has no global switch, * so the caller enumerates `filter..*` from `git config` first. An empty * `clean`/`smudge`/`process` is "no driver", and `required=false` keeps a * disabled required filter from failing the command. Passed through * `GIT_CONFIG_KEY_n`/`GIT_CONFIG_VALUE_n`, so a name containing `=` is intact * (with `-c` git would split the key at that `=`). * * @param names - Filter driver names. * @returns Config pairs. */ export declare function filterConfig(names: Iterable): GitConfigPair[]; /** * Probe environment: no ambient repo, no prompts, no lazy fetch, no optional * locks, no inherited command-scope config (`GIT_CONFIG_PARAMETERS`, * `GIT_CONFIG_COUNT`/`KEY_n`/`VALUE_n`) or alternate config files * (`GIT_CONFIG_GLOBAL`/`GIT_CONFIG_SYSTEM`), and {@link BASE_CONFIG} plus * `config` as the only command-scope overrides. * * @param config - Overrides beyond {@link BASE_CONFIG}. * @returns Environment for a probe git process. */ export declare function hardenedGitEnv(config?: readonly GitConfigPair[]): NodeJS.ProcessEnv; /** * Bytes of git output held in memory at once across a whole probe run, so a * high concurrency cannot multiply the per-call cap into gigabytes. */ export declare class GitOutputBudget { readonly limit: number; private used; /** * @param limit - Maximum bytes in flight across all calls sharing this budget. */ constructor(limit: number); /** * Reserve `bytes`. * * @param bytes - Bytes about to be held. * @returns `false` when the reservation would exceed the limit. */ take(bytes: number): boolean; /** * Release `bytes` previously taken. * * @param bytes - Bytes no longer held. */ release(bytes: number): void; } /** Outcome of one bounded git invocation. */ export interface BoundedGitRun { /** Exit code; `null` when killed or never started. */ code: number | null; /** Captured stdout (UTF-8). */ stdout: string; /** Captured stderr (UTF-8). */ stderr: string; /** Killed at the deadline. */ timedOut: boolean; /** Killed because output passed `maxOutputBytes`. */ overflowed: boolean; /** Spawn failure (ENOENT for a missing git, EACCES for the cwd, …). */ spawnError: NodeJS.ErrnoException | null; } /** Options for {@link runBoundedGit}. */ export interface BoundedGitOptions { /** Working directory. */ cwd: string; /** Absolute epoch-ms deadline. */ deadline: number; /** Git executable. Default `git`. */ gitBin?: string; /** Output cap. Default {@link DEFAULT_GIT_MAX_OUTPUT_BYTES}. */ maxOutputBytes?: number; /** Overrides beyond {@link BASE_CONFIG} (e.g. {@link filterConfig}). */ config?: readonly GitConfigPair[]; /** Run-wide in-flight output budget. */ budget?: GitOutputBudget; } /** * Kill every live git process group started by {@link runBoundedGit}. * * Installed on `exit` and on SIGINT/SIGTERM/SIGHUP while any call is live; * exported for tests. * * @returns Number of groups signalled. */ export declare function killLiveGitProcesses(): number; /** Number of live bounded git calls (tests). */ export declare function liveGitProcessCount(): number; /** * Run `git ` in `cwd` with {@link hardenedGitEnv}, bounded by `deadline` and * `maxOutputBytes`. Never rejects. * * @param args - Git arguments. * @param options - cwd, deadline, git executable and output cap. * @returns The run outcome. * @example * ```ts * const r = await runBoundedGit(['status', '--porcelain=v2'], { cwd, deadline: Date.now() + 5000 }); * ``` */ export declare function runBoundedGit(args: readonly string[], options: BoundedGitOptions): Promise; /** Sentinel for {@link withDeadline}. */ export declare const DEADLINE_EXCEEDED: unique symbol; /** * Race `work` against an absolute deadline. The work is not cancelled (a * stuck filesystem call cannot be); the caller simply stops waiting. * * @param work - Promise to bound. * @param deadline - Absolute epoch-ms deadline. * @returns The result, or {@link DEADLINE_EXCEEDED}. */ export declare function withDeadline(work: Promise, deadline: number): Promise; //# sourceMappingURL=bounded-git.d.ts.map