feat(remote): S13 upstream HTTP request/URL validators (step 3)
✨ This MR
S13 Step 3 of the virtual/remote foundation: the pure, no-network upstream-URL and header validators, security covers 1-3, 9, and 10 from the S13 Security covers. No DNS and no dial here; the resolved-address covers (4-6) land in the secure dialer (Step 4), and these validators are wired into the RoundTripper in Step 5.
internal/remote/upstreamhttp/validate.go:
ValidateURL, before any network call: scheme allowlist (cover 1; empty and unknown schemes rejected), port validation (cover 2; sub-1024 ports must be allowlisted,1024..65535allowed,>65535rejected), hostname format (cover 3; alphanumeric-leading or a valid IP literal, capped at 1024 bytes), and multi-line-URL rejection (cover 10; CR/LF after URL-decoding, across path, query, fragment, and userinfo).ValidateHeadersrejects any header name or value carrying CR, LF, or a null byte (cover 9).
Failures return the Step 2 error types carrying no network detail: a BlockedURLError with a fixed reason token, or the ErrHeaderInjection sentinel for the CRLF-class covers. internal/remote/errors.go gains the ReasonHostNotAllowed token; Step 2 reserved the closed reason set for Steps 3-5 to extend.
Tests exercise every cover and branch (scheme, port, and hostname boundaries; CRLF in path, query, and userinfo; header name and value injection; and a valid-request acceptance proof); the validators sit at 100% statement coverage.
🔍 Branch review applied
Ran the branch-review skill on this step; the findings are folded into the commits above:
- Added CRLF-in-userinfo and CRLF-in-query cases (the
urlHasNewlineuserinfo branch and the decoded-query path were previously untested). - Reordered
validateHostnameto short-circuit on the alphanumeric lead beforenet.ParseIP, dropping an allocation on the common DNS-name path (accept set unchanged). - Documented the
url.Parse-derived-URL precondition onValidateURL.
📏 Size
671 LOC, above the 500 review ceiling, because the change is test-heavy: 212 LOC production and 457 LOC test (68% of the code diff), with the validators at 100% coverage.
🔗 References
- Plan:
docs/plans/2026-07-16-s13-virtual-remote-foundation.md, Step 3 (this MR also records theerrors.gofile and the Step 2 MR links) - Spec:
docs/specs/S13-virtual-remote-foundation.md, Security covers 1-3, 9, 10
Related to #324 (closed)