Hi! Really enjoying wigolo. Following the "open an issue first for non-trivial changes" guidance before proposing anything.
The gap: guardFetchUrl / guardServeTarget validate only the literal hostname — which the code already notes ("we never actually resolve here… a follow-up tier could re-check at fetch time"). Since the fetch layer resolves DNS itself, a public hostname whose A/AAAA record points at a blocked address — cloud metadata (169.254.169.254), an RFC-1918 host, or a loopback service in serve mode — passes the guard and is only reached after resolution. That defeats the "always block metadata" and serve-mode loopback protections.
Proposed approach: add guardResolvedHost(host, field, { allowPrivate, lookup }) that resolves the host and runs every resolved IP back through your existing guardFetchUrl (as an http:/// literal) — so the resolved-IP policy is identical to the literal one, no drift. Wire it into the fetch path (http-client redirect loop first; then the dispatch / firecrawl / browser seams). lookup is injectable so it's unit-testable without real DNS.
Scope: this closes the static-record bypass (the metadata case). Full rebinding (TOCTOU) safety also wants the socket pinned to the validated IP (a lookup hook / custom dispatcher) — I'd suggest the resolved-IP re-check first (small, high-value), pinning as a follow-up.
I've got a focused branch + tests ready to PR if this direction works for you — happy to adjust first. Does this approach sound right, or would you rather pin at the connector level from the start?
Hi! Really enjoying wigolo. Following the "open an issue first for non-trivial changes" guidance before proposing anything.
The gap: guardFetchUrl / guardServeTarget validate only the literal hostname — which the code already notes ("we never actually resolve here… a follow-up tier could re-check at fetch time"). Since the fetch layer resolves DNS itself, a public hostname whose A/AAAA record points at a blocked address — cloud metadata (169.254.169.254), an RFC-1918 host, or a loopback service in serve mode — passes the guard and is only reached after resolution. That defeats the "always block metadata" and serve-mode loopback protections.
Proposed approach: add guardResolvedHost(host, field, { allowPrivate, lookup }) that resolves the host and runs every resolved IP back through your existing guardFetchUrl (as an http:/// literal) — so the resolved-IP policy is identical to the literal one, no drift. Wire it into the fetch path (http-client redirect loop first; then the dispatch / firecrawl / browser seams). lookup is injectable so it's unit-testable without real DNS.
Scope: this closes the static-record bypass (the metadata case). Full rebinding (TOCTOU) safety also wants the socket pinned to the validated IP (a lookup hook / custom dispatcher) — I'd suggest the resolved-IP re-check first (small, high-value), pinning as a follow-up.
I've got a focused branch + tests ready to PR if this direction works for you — happy to adjust first. Does this approach sound right, or would you rather pin at the connector level from the start?