From f459ebb667310058da9c4d65d905ea2ddcc2dd64 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 10 Oct 2026 22:36:19 -0400 Subject: [PATCH] fix(audit-185): probe binary presence live instead of caching (Refs #243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the negative-cache fix: caching positives is stale the other way — a PATH shim's finally-restore leaves the cached hit behind and the next gate run spawns a binary that is gone (CI: cli.test.ts gates run failed with `Executable not found in $PATH: gitleaks`). installed() is a live PATH probe now; a probe is microseconds and gate runs call it a handful of times. The unit test pins both directions (miss→shim→seen→removed→miss). TDD: CI run 38105290150 is the red witness for the stale-positive direction; gates-seeded.test.ts 13/13 green locally after. Refs #185 --- packages/core/src/gates/security.ts | 17 ++++++----------- packages/core/test/gates-seeded.test.ts | 4 ++++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/core/src/gates/security.ts b/packages/core/src/gates/security.ts index 8de951a..a38db71 100644 --- a/packages/core/src/gates/security.ts +++ b/packages/core/src/gates/security.ts @@ -8,21 +8,16 @@ import type { GateFinding } from "../schema/gates.ts" import { parseJsonc } from "../util/jsonc.ts" import { run } from "../util/proc.ts" -const which = new Map() - /** * Is `binary` an executable on PATH? Pure lookup (no shell), so no injection. - * Only positive answers are cached: a miss is re-probed every time, so a - * binary installed (or shimmed onto PATH) later in the process is seen. - * Caching a miss pinned Gate skips across PATH changes (#243: the gitleaks - * PATH shims in these tests read as absent once any earlier gate run cached - * the miss on a machine without the binary). + * Probed live on every call: caching the answer pinned Gate skips across + * PATH changes in both directions (#243 — a cached miss hid later shims; + * a cached hit outlived the shim's finally-restore and sent the gate at a + * binary that was already gone). A PATH probe is microseconds; gate runs + * call this a handful of times. */ export function installed(binary: string): boolean { - if (which.get(binary) === true) return true - const found = onPath(binary) - if (found) which.set(binary, true) - return found + return onPath(binary) } function onPath(binary: string): boolean { diff --git a/packages/core/test/gates-seeded.test.ts b/packages/core/test/gates-seeded.test.ts index 4df0b03..351eb33 100644 --- a/packages/core/test/gates-seeded.test.ts +++ b/packages/core/test/gates-seeded.test.ts @@ -325,6 +325,10 @@ describe("gitleaks shape and waiver glob edges", () => { process.env.PATH = previous await rm(shimdir, { recursive: true, force: true }) } + // And no stale positive survives the shim's removal: the next gate run + // must skip the scanner instead of spawning a binary that is gone (the + // cli.test.ts gates failure this exact staleness caused in CI). + expect(installed(missing)).toBe(false) }) test("a rule+files glob waiver hides only what both globs match", async () => {