| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The prior guard resolved the target host, checked the IP against the
private/loopback/link-local/CGNAT/metadata blocklist, then let got fetch the
URL — which re-resolves DNS independently when it connects, leaving a rebinding
window where a low-TTL host can pass the check as public and connect as
internal.
Reimplement safeGot on node http/https with a custom lookup that validates and
returns the exact IP the socket then connects to, so the validation and the
connection target are one and the same (no resolve/connect gap). Redirects are
followed manually and every hop is re-validated the same way; IP-literal hosts
(which bypass the lookup hook) are checked up front; the 2MB cap is enforced on
the streamed body rather than a spoofable content-length header. Scheme stays
restricted to http(s) with an 8s timeout. safeGot's contract is unchanged, so
the three lnurl callers are untouched.
Verified in-container: every internal target (loopback, docker-internal,
169.254 metadata, CGNAT, ULA, IP-literal, octal-literal) is blocked by the
guard, a server-side redirect into the metadata endpoint is blocked at the hop,
and legit external resolution (including http->https redirects) still works
with correct TLS against the real hostname over the pinned IP.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DKkev76U4YRnRAqM6jqbae
|