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..65535 allowed, >65535 rejected), 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).
  • ValidateHeaders rejects 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 urlHasNewline userinfo branch and the decoded-query path were previously untested).
  • Reordered validateHostname to short-circuit on the alphanumeric lead before net.ParseIP, dropping an allocation on the common DNS-name path (accept set unchanged).
  • Documented the url.Parse-derived-URL precondition on ValidateURL.

📏 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.

  • Plan: docs/plans/2026-07-16-s13-virtual-remote-foundation.md, Step 3 (this MR also records the errors.go file and the Step 2 MR links)
  • Spec: docs/specs/S13-virtual-remote-foundation.md, Security covers 1-3, 9, 10

Related to #324 (closed)

Merge request reports

Loading
Loading