Skip to content

gstack-redact-prepush: no allowlist plumbing (engine supports it) + unchunked scan fail-closes on >1 MiB diffs — both push users onto GSTACK_REDACT_PREPUSH=skip, which skips all scanning #2304

Closed

Description

@sneakygriff

Two defects in bin/gstack-redact-prepush (observed on gstack 1.60.1.0, macOS, bun) that together train users to reach for GSTACK_REDACT_PREPUSH=skip — and that valve skips ALL scanning, which defeats the guard exactly when it matters. Related but distinct from #1946 (fail-open on git errors, which the current hook already fixes with gitStrict).

Defect 1 — the hook never passes the engine's allowlist option, so synthetic fixtures hard-block pushes

lib/redact-engine.ts supports value-exact suppression:

export interface ScanOptions {
  /** Extra allowlist entries (exact strings) that suppress a matched span. */
  allowlist?: string[];
  ...
}

…but the hook calls scan(added, { repoVisibility: "private" }) with no allowlist and no way for a repo to provide one. Any repo whose committed docker-compose / test files contain a synthetic DB URL like postgresql://booking:booking@localhost:5432/booking gets a HIGH db.url_with_password on every push that touches those lines (the booking:booking password is not caught by isPlaceholderSpan). There is no remediation path other than the skip valve, so the valve becomes routine — we counted the skip in 14 of 38 working transcripts in one project before intervening.

Suggested fix: read an optional repo-level allowlist file (e.g. .gstack-redact-allowlist at the repo root — one literal span per line, # comments) and pass it through to scan(). Value-exact spans only; the engine already does allow.has(span) so no engine change is needed.

Defect 2 — added lines are scanned as ONE string, so >1 MiB diffs fail closed with engine.input_too_large, and the only relief valve disables scanning entirely

The engine's 1 MiB maxBytes fail-closed cap is correct for a single scan call, but the hook concatenates all added lines per ref and scans once. Feature-branch pushes with big lockfiles/docs/generated files routinely exceed 1 MiB, producing:

HIGH  engine.input_too_large  Input too large to scan safely (N > 1048576 bytes) — blocking fail-closed

The push is blocked on SIZE, not on a finding — and the documented escape (GSTACK_REDACT_PREPUSH=skip) then performs zero scanning on exactly the largest, riskiest pushes.

Suggested fix: chunk the added-lines text on line boundaries under maxBytes (a single line larger than the chunk size can be sliced with a few KB of overlap so a token spanning the cut is still seen whole), scan each chunk, and aggregate findings. In our downstream copy of this approach, a 3.96 MB added-lines diff scans in 6 chunks in ~340 ms, and a single planted credential inside such a diff is still caught.

Why filing instead of patching

We run the fix as a repo-local pre-push.local leg (same engine, allowlist + chunking + fail-closed watchdog) because hand-editing the managed binary gets clobbered by upgrades. Happy to share the implementation if useful.

Activity

  1. added a commit that references this issue on Aug 15, 2026
    008dd65
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions