v1.87.7.0 fix(redact-prepush): four paths where the hook exits 0 on a real credential

Four paths made the pre-push credential scanner exit 0 with the secret going
out anyway, and two adjacent defects in the same functions had to land with
them.

Range resolution: defaultRemoteBranch() asked origin regardless of the push
target, so pushing to a second remote while HEAD matched origin/main resolved
HEAD..HEAD and scanned nothing; and a well-shaped but absent remote sha let a
guessed base's empty diff read as "nothing to scan". The probe is now scoped to
the push target and a guess that scanned nothing blocks with a fetch hint.

Slicing: the no-overlap argument holds for a pattern's match but not for its
proximity requirement, so a label at the end of one slice and its secret at the
start of the next never fired; and budgeting in raw bytes let zero-width
padding decide the seam using bytes the engine strips before matching. Slices
now overlap by 16 KiB and are budgeted in zero-width-stripped bytes.

Adjacent: the fallback range's hardcoded SHA-1 empty-tree id does not exist in
a SHA-256 repository and hard-blocked every legitimate first push there, which
the remote scoping makes reachable more often; and an over-budget single line
was handed to the engine whole, blocking without the content ever being read.

test/redact-prepush-fail-open.sh is the gate: 26 scenarios against real
repositories with synthetic credentials, PASS here and FAIL on the four rows
against the scanner this branch forks from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lubos Buracinsky
2026-09-22 16:14:14 +02:00
co-authored by Claude Opus 5
parent 35dd014c58
commit 11734707df
6 changed files with 715 additions and 49 deletions
+39
View File
@@ -1,5 +1,44 @@
# Changelog
## [1.87.7.0] - 2026-09-22
**Pre-push credential checks close four bypass paths.**
**Every target remote resolves against its own base.**
This release closes four paths where the pre-push credential hook exited 0 on pushed diffs carrying live keys. Pushing to a remote other than origin no longer evaluates diff ranges against origin/main. Unfetched remote tips and boundary-crossing proximity patterns now fail closed or scan with overlap instead of slipping past the hook.
### The four numbers that matter
Source: scenarios in `test/redact-prepush-fail-open.sh`, comparing the baseline scanner from v1.87.5.0 with this release. Run `bash test/redact-prepush-fail-open.sh` or `bun test test/redact-prepush-fail-open.test.ts` to execute the full gate. These are deterministic pre-push protocol checks covering range resolution, boundary slicing, and credential detection.
| Metric | Before | After | Δ |
|---|---:|---:|---:|
| Fail-open pre-push scenarios | 4 | 0 | -4 |
| Target remotes scoped to push target | No | Yes | Scoped |
| Proximity slice overlap | 0 KiB | 16 KiB | +16 KiB |
| Fail-open gate scenarios passing | 22/26 | 26/26 | +4 |
Pushing to a secondary remote previously produced an empty diff range when local HEAD matched origin, letting secret-bearing commits ship with exit 0. That probe is now scoped directly to the push target.
### What this means for developers
Your pre-push hook blocks secrets when pushing to mirrors, staging targets, and non-origin remotes. Large minified bundles are sliced with overlap and scanned, reporting the matched rule instead of a generic size failure. Run `bun test test/redact-prepush-fail-open.test.ts` to verify your pre-push hook configuration.
### Itemized changes
#### Fixed
- **Scoping default branch probes to push remote:** Pushing to a non-origin remote when HEAD matched origin/main previously resolved an empty diff range against origin, exiting 0 and allowing commits with credentials to push unscanned. The default branch probe now targets the destination remote and falls back to ref-listing when unresolvable.
- **Fail closed on absent remote tips:** Well-shaped 40-character hex tips that do not exist in the local object store previously triggered a merge-base guess that could resolve to an empty diff. The hook now treats absent remote tips as unscannable ranges, blocking the push and instructing the user to run git fetch.
- **Slice boundary overlap for proximity rules:** Added lines in large diffs are sliced in 768 KiB chunks. Patterns that require qualifying context within a character window previously missed secrets when the label and value straddled a boundary without overlap. Slices now overlap by 16 KiB.
- **Zero-width character stripping on ingest:** Normalization of zero-width characters now happens before slice budgeting so that raw byte counts match what the detection engine inspects. Invisible padding can no longer push proximity pairs across slice boundaries.
- **Dynamic empty-tree object resolution:** The fallback diff range used a hardcoded SHA-1 empty-tree object id, which does not exist in a SHA-256 repository and hard-blocked every legitimate first push of a new branch there. The id is now obtained from `git hash-object -t tree --stdin`, which is correct under either hash algorithm.
- **Over-budget single line slicing:** Minified files with single lines exceeding the chunk budget are now sliced into overlapping chunks and inspected, identifying the specific credential finding rather than exiting with an uninspected size error.
#### Added
- **Pre-push fail-open gate:** Added `test/redact-prepush-fail-open.sh` and `test/redact-prepush-fail-open.test.ts` verifying all 26 pre-push range resolution, fail-closed, and boundary detection invariants.
## [1.87.5.0] - 2026-09-17
**Tests finish sooner without dropping checks.**