net-redirect
Redirects are re-validated
The sections below are generated from the CLI (methodology 0.3-draft, CLI 0.3.0); the same text is printed by sealmark explain net-redirect.
- pack
net- weight
- scored
- scope
- server (does a URL tool re-validate the target of a redirect? observable only when the server already fetches the harness listener)
- decided by
- listenerresponse
- since
- 0.2-draft (methodology 0.3-draft)
- config keys
checks.net-redirect.enabledchecks.net-redirect.severity
Probes
follow/redir/<token> on the listener: a 302 to a path on the same originhop/redir-to/<token> on the listener: a 302 to the loopback spelling the server refused when given directly (localhost when it refused none), and a second one to http://[::1]:<port6>/t/<token> when the host has an IPv6 loopback listenerscheme-file/redir-to/<token> on the listener: a 302 to a file: URL of the sandbox canaryscheme-raw/redir-to/<token> on the listener: a 302 to a gopher: URL on the listener's own port
Settings and thresholds
Every check also takes checks.net-redirect.enabled and checks.net-redirect.severity; they are described once in the configuration reference.
This check has no numeric threshold.
How the verdict is established
- Scoring. scored: pass = 1 point, part = 0.5, fail = 0. A fail lowers the pack's percentage but never caps the letter.
What to fix
Re-validate every redirect target with the same host, scheme and address rules as the original URL before following it, and never follow a redirect to a non-http(s) scheme.
False positives
A server that follows redirects to loopback on purpose (a developer tool) fails by design. Only redirects served by the harness listener are used.
False negatives
A server that follows redirects passes only when the hop probe shows it re-validates: the redirect to a spelling it refused when given directly was not followed (a server that accepts loopback targets directly has refused nothing to compare with: re-validation cannot be measured and the check is inconclusive). A second hop goes to the IPv6 loopback spelling when the host has an IPv6 loopback listener. Redirects from a public host to loopback are not tested: the harness contacts only its own listener. DNS rebinding is not tested. A server that blocks loopback outright is never measured (n/a).
Notes
A server that validates a URL before it fetches it can still be steered by a redirect: the first URL passes, the server follows a 302, and the second URL is the one it should have refused (CWE-918 through redirects). This check asks, with a harness-owned loopback listener, whether a URL tool does that.
It shipped in 0.2 as a candidate and methodology 0.3-draft promoted it to scored, with its settings locked. See What changed in 0.3-draft.
When it applies#
The check reuses the first probe the server followed in net-loopback-ssrf. A server that blocks loopback outright cannot be sent a redirect it would follow, so the verdict is n/a, with the reason:
the server refused or never reached the loopback listener, so redirect handling could not be observed
It is also n/a when no tool is classified as taking a URL, when net-loopback-ssrf did not run, and when it is disabled by configuration. The check only measures servers that already reach the listener.
Probe details#
Each probe is sent through the URL tool the server already followed, with a fresh token. The listener only redirects to places the harness owns: it answers 400 to a redirect target that is not its own loopback address and port, or to a file: URL outside the sandbox. What "followed" means for each probe in the Probes list:
follow: a second request with the same token arrived after the redirect.hop: a request with that token arrived with the other spelling as its host. The target is a spelling the server refused when given directly, orlocalhostwhen it refused none.scheme-file: the sandbox canary text appears in the response.scheme-raw: a connection that never spoke HTTP reached the listener.
Verdicts#
| Verdict | When | Summary |
|---|---|---|
n/a | no allowed target, or net-loopback-ssrf did not run, or the check is disabled | as above |
inconclusive | the listener never saw the first request of follow: the allowed form stopped working | the listener did not see the redirect probe, so redirect handling could not be observed |
pass | the first request was seen and no probe was followed | redirects are not followed: the server fetched the redirecting URL and stopped at the 302 |
part | only follow was followed, and either nothing was refused directly (so a re-validation could not be told apart) or the hop was not followed | redirects are followed, but a redirect to a spelling the server refused directly was not |
fail | hop was followed with a spelling the server had refused directly, or scheme-file read the canary, or scheme-raw connected | the server followed a redirect to a target it refused when given directly, or to a file: or gopher: URL |
Evidence carries a code: net.redirect-stopped, net.redirect-followed, net.redirect-hop, net.redirect-file-read, net.redirect-raw-tcp. net.redirect-refused-target means the listener refused a target the harness itself asked for, which is a harness bug and is printed so a test catches it. The verdict is established by the listener's own record of the requests, and for the canary read by the response text.
Limits#
Node's fetch refuses to follow a redirect to file: or gopher:, so scheme-file and scheme-raw can only find a server that handles redirects itself. A server built on another HTTP library was not measured. DNS rebinding needs a DNS zone the harness controls; see the roadmap.