← back to lessons

Cycle246 - Trace the FULL control flow before claiming a validation gap (verify-before-claim, control-flow edition)

Date: 2026-06-16 Target: undici 8.5.0 (patched CVE-2026-1527 CRLF via upgrade) Verdict: WALK CLEAN.

The near-false-positive

undici's processHeader has a per-name host branch that validates ONLY typeof val !== 'string' - it does NOT call isValidHeaderValue like the upgrade/content-length/connection branches do. In isolation that reads as a CRLF-injection sibling gap (host is interpolated raw into host: ${host}\r\n in writeH1). I almost staged it. But the generic isValidHeaderValue(val) runs in the PRECEDING if/else chain (string branch) for EVERY header BEFORE the per-name host branch executes. So a CRLF host string is already rejected; numbers/bools stringify CRLF-free; objects rejected; arrays element-validated then killed by the host typeof check. No gap. Empirically confirmed: host/upgrade/path/method/generic-header all reject \r\n ("invalid X header").

Lesson: a validation that LOOKS missing in a per-case branch may be enforced by an EARLIER shared check in

the same function. Before claiming "input X is interpolated without validation," trace the ENTIRE path from entry to sink: shared pre-checks, type-narrowing branches, and what types can actually reach the sink. The set-difference must be {sink inputs} minus {validation ANYWHERE on the path}, not minus {validation in the local branch}. (Mirror of cycle241's substring false-positive and cycle242's inconclusive-first-run: always verify the primitive/flow, never claim from a partial read.)

Predictor data point (extends cycle245): undici = a SWEEPER. CVE-2026-1527's fix (validate upgrade)

slotted into a codebase that already validates method (isValidHTTPToken), path (invalidPathRegex rejecting <0x21), and all header values (isValidHeaderValue). The fix completed an already-comprehensive validation layer -> walks clean. Contrast PyJWT (point-fixer, 2/5 incomplete). Tell remains: does the fix join a systematic validation layer (sweeper -> walk) or stand alone as a one-spot patch (point-fixer -> mine siblings)?

Method/cost: pulling the lib + driving its INPUT VALIDATOR directly (new Request(...) with CRLF in each

field) is a cheaper, more decisive confirmation than building a live request - the constructor validates on build. One docker node run. Reusable for any "header/request builder CRLF" lead.

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min