Skip to content

fix(farm): a raw NUL byte made the repo's largest source file unsearchable (#536) - #548

Merged
SUaDtL merged 2 commits into
mainfrom
fix/536-farm-nul-byte
Jul 29, 2026
Merged

fix(farm): a raw NUL byte made the repo's largest source file unsearchable (#536)#548
SUaDtL merged 2 commits into
mainfrom
fix/536-farm-nul-byte

Conversation

@SUaDtL

@SUaDtL SUaDtL commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Closes #536.

The bug

farm.ts carried a literal NUL character inside parts.join(...) — the actual byte, not the \0 escape.

ripgrep treats any file containing one as binary, so every content search across this repo silently skipped its largest source file (~3100 lines) and returned binary file matches instead of results. Hit live while investigating #515: a grep over farm.ts came back empty, and the reason was not the query.

git was unaffected and still is — its binary sniff reads only the first 8000 bytes and the NUL sat at 50941. That is why diffs, blame and review always looked normal and nothing ever flagged it. A search tool that quietly excludes a file is worse than one that errors, because the answer looks complete.

No behaviour change, and no payload change

  • farm.js rebuilds byte-identical — the emitted separator is unchanged.
  • The payload gate agrees: plugins/ca/tools/ is a build directory and only the declared artifact counts (SBX payload-version gate over-fires on dev-only lockfile changes #435). No ca version bump.
  • The crypto reviewer independently executed both forms under Node and confirmed sha256 over a raw-NUL join and an escape join produce the identical digest, so no setup fingerprint is invalidated and no setup cache is busted.

Clearing H-09b

The changed line is createHash("sha256"), so the guard fires correctly even though the change is one character of source spelling. I cleared it through the auth-crypto reviewer rather than around it; the recorded pass binds exactly that one line.

Worth recording: the first attempt was refused. I had moved the checkout out from under the reviewer mid-review, and minting the marker then would have bound the attestation to a different tree. That refusal was correct, and it is the failure mode ADR-0010 exists to prevent.

AC-3 — the durable half

A guard fails if any tracked file with a text extension reintroduces a raw NUL. Scoped to every tracked file rather than a list of trees: a NUL breaks ripgrep wherever it lands, and a five-tree pathspec left 335 tracked files unscanned while the failure message claimed "tracked source files" without qualification.

Both halves are proven by mutation rather than asserted:

control:                                   green
mutant detection-half (raw NUL restored):  KILLED
mutant vacuity-half (pathspec matches nothing): KILLED

The second matters more than it looks. git ls-files exits 0 on a pathspec that matches nothing — measured — so the returncode check alone would have passed vacuously forever. The scanned-count floor is the control that actually does the work.

Confirmation

Verified with the tool that could not read it: ripgrep now returns line-numbered matches from farm.ts.

SUaDtL added 2 commits July 29, 2026 11:11
…hable (#536)

farm.ts carried a literal NUL character inside `parts.join(...)` - the actual
byte, not the `\0` escape. ripgrep treats any file containing one as BINARY, so
every content search across this repo silently skipped its largest source file
and returned "binary file matches" instead of results. Hit live while
investigating #515: a grep over farm.ts came back empty and the reason was not
the query.

git was unaffected and still is - its binary sniff reads only the first 8000
bytes and the NUL sat at 50941 - which is why diffs, blame and review always
looked normal and nothing ever flagged it.

NO behaviour change, and not a payload change:

  - farm.js rebuilds BYTE-IDENTICAL, so the emitted separator is unchanged.
  - the payload gate agrees: plugins/ca/tools/ is a build directory and only the
    declared artifact counts (#435). No ca version bump.
  - the crypto gate independently executed both forms under Node and confirmed
    sha256 over a raw-NUL join and an escape join produce the identical digest,
    so no setup fingerprint is invalidated and no setup cache is busted.

Cleared H-09b through the auth-crypto reviewer rather than around it. The line is
`createHash("sha256")`, so the guard fires correctly even though the change is
one character of source spelling; the recorded pass binds exactly that one line.
The first attempt was refused - I had moved the checkout out from under the
reviewer mid-review, and minting then would have bound the attestation to a
different tree. That refusal was right.

AC-3, the durable half: a guard fails if any TRACKED file with a text extension
reintroduces a raw NUL. Scoped to every tracked file rather than a list of trees,
because a NUL breaks ripgrep wherever it lands and a five-tree pathspec left 335
files unscanned while the message claimed "tracked source files" unqualified.

Both halves are proven by mutation, not asserted: restoring the byte is caught,
AND a pathspec matching nothing is caught. The second matters because
`git ls-files` exits 0 on a pathspec that matches nothing - measured - so the
returncode check alone would pass vacuously forever. The scanned-count floor is
the control that actually does the work.

Confirmed fixed with the tool that could not read it: ripgrep now returns
line-numbered matches from farm.ts.

Claude-Session: https://claude.ai/code/session_01WJgVfZw7J81PB7mwpHyUxx
@SUaDtL
SUaDtL merged commit defa4ea into main Jul 29, 2026
43 checks passed
@SUaDtL
SUaDtL deleted the fix/536-farm-nul-byte branch July 29, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

farm.ts contains a raw NUL byte, so ripgrep treats the repo's largest source file as binary

1 participant