Development record
termsafe.Sanitize masks four zero-width runes (U+200B/200C/200D/FEFF) but not U+2060 WORD JOINER (the modern successor to the deprecated FEFF-as-joiner it does mask), nor U+2061-2064/U+00AD/Hangul fillers, so those default-ignorable characters pass through and two byte strings render identically
termsafe.Sanitize masks four zero-width runes (U+200B/200C/200D/FEFF) but not U+2060 WORD JOINER (the modern successor to the deprecated FEFF-as-joiner it does mask), nor U+2061-2064/U+00AD/Hangul fillers, so those default-ignorable characters pass through and two byte strings render identically
Evidence
internal/termsafe/termsafe.go:104-110—isZeroWidthswitch covers only0x200B,0x200C,0x200D,0xFEFF.:88-100—isBidiControldoes not coverU+2060;:24-38Sanitizepasses it throughstrings.Mapunchanged (not C0, not DEL, not0x80-0x9f).- Package doc (
:2-6,:18-20) promises to neutralise "zero-width characters, which … hide text so the rendered line differs from the bytes" — an unbounded promise the enumeration under-delivers. - Callers carry hostile foreign-repo content (lifeboat, history show, ideate, skew release_tag).
Adversarial verdict
CONFIRMED (nitpick). Mechanical gap real; severity nitpick — visual-hiding only, no escape/reordering, below the project's "minor" calibration for the escape-injection render gaps (iss-259/iss-264). Notable that the code masks the deprecated FEFF joiner but not its Unicode-designated successor U+2060. Fix: extend the switch to 0x2060,0x2061,0x2062,0x2063,0x2064,0x00AD; add test invariants.
Frontmatter
| id | iss-366 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | nitpick |
.abcd/work/issues/resolved/iss-366-termsafe-sanitize-masks-four-zero-width.md
open on GitHub ↗ · commit history ↗
mentions3
- dcn-26082000000000052026-08-20 — bughunt round 3 (branch bughunt-a/round-3): captured twelve findings (iss-360..iss-369, plus two renumbered to iss-373/iss-374 after round 2's own collision re-mint landed inside this round's block — the week's fourth collision,
- iss-259intent-store paths render to the terminal unsanitised in several intent verbs: intent plan prints res.Intent.Path and intent create prints the minted path without termsafe.Sanitize, and a hostile clone can commit a drafts/itd-N-<slug>.md whose filename tail carries control bytes (intentFileRe admits anything but a newline after the id), reaching the owner's terminal as escape sequences. The promote render sanitises its two paths as of spc-24; close the class once by sweeping the sibling intent renders through termsafe.Sanitize.
- iss-264cmd/record-lint prints Finding.File and Finding.Message raw — no termsafe.Sanitize and no path scrub on the findings path (its scrubPaths covers only the two error exits), so any rule echoing repo content lets a hostile clone inject terminal escapes into CI logs, and any finding message embedding an os error can leak absolute paths. The abcd CLI surface sanitizes at its findings renderer; the standalone binary does not. Found by the spc-27 security review, which also caught (and the change fixed) the one in-diff instance.