Development record
cite fetch records a redirect-controlled FinalURL verbatim, so C1, bidi and zero-width runes in a hostile Location query reach abcd docs lint --json and the committed citations baseline raw; the termsafe doc claim that encoding/json escapes control characters is false for those classes
issueresolvedminoriss-359
cite fetch records a redirect-controlled FinalURL verbatim, so C1, bidi and zero-width runes in a hostile Location query reach abcd docs lint --json and the committed citations baseline raw; the termsafe doc claim that encoding/json escapes control characters is false for those classes
Evidence
internal/core/cite/fetch.go:204assignsout.FinalURL = resp.Request.URL.String().url.Parserejects C0/DEL but preserves raw non-ASCII inRawQuery, andString()writes it verbatim — so the live payload set from a hostile redirect is C1 (2-byte U+009B), bidi (U+202E) and zero-width (U+200B).- Reproduced end-to-end with
newHTTPCheckeragainst an httptest 302 whose Location query carried all three:Status=="ok", raw bytes inFinalURL,lint.SaveBaselineaccepted them (Baseline.validatechecks only non-empty), andabcd docs lint --json(internal/surface/cli/cli.go:445 render) marshals them raw —encoding/jsonescapes only <0x20 plus U+2028/9. - The false invariant is stated at
internal/termsafe/termsafe.go:22-23and duplicated atinternal/core/lifeboat/coverage.go:149. - Refuter verdict: CONFIRMED substantive (minor, security). Remedy: percent-encode surviving non-printing runes at the fetch boundary (byte-wise, so invalid UTF-8 stays lossless too) + a
Baseline.validaterung + correct both doc claims; a blanket JSON-path sanitizer was rejected (would corrupt a committed record that must round-trip). Distinct from iss-259/iss-264 (terminal text renders). - Re-minted from the round-2 id that collided with a parallel hunt's mint.
Frontmatter
| id | iss-359 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-359-cite-fetch-records-a-redirect-controlled.md
open on GitHub ↗ · commit history ↗
mentions8
- dcn-26082000000000022026-08-20 — bughunt round 2 (branch bughunt-b/round-2): captured the round-2 records (iss-346..iss-357, with the two colliding ids re-minted as iss-358/iss-359), fixed
- dcn-26082000000000082026-08-20 — Bug-hunt round 3 (hunt B), branch bughunt-b/round-3. Baseline
- 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.
- iss-357memory ingest records a redirect-controlled FinalURL verbatim into stored material origins — the iss-359 class one door over; the cite fix neither touched nor recorded the sibling site
- iss-384Baseline.validate sanitises final_url alone while echoing the entry key, url and quoted fields raw, so a committed citations-baseline entry writes ESC/bidi/zero-width to the terminal through the blocking docs-lint gate
- iss-2608301206073609hidden runes reach committed records verbatim because termsafe.EncodeHiddenRunes is not applied at the record-write boundary
- iss-2609261036366114scribe ingest --json renders refusals[].reason, refusals[].subject and the fidelity flags, which are scribe free text, raw: the text render neutralises them through termsafe and the JSON render leaves bidi and C1 controls in place (the iss-359 class)