Both corsproxy/ and activitypub/'s gatedFetch validate the target host with dns.lookup and reject private/loopback addresses. But fetch() then resolves the hostname again, independently — so a hostname that returns a public address to the guard's lookup and a private one to fetch's resolution (DNS rebinding) can slip past.
Fix direction: pin the validated IP into the actual connection (resolve once, connect to the literal address with the original Host header), or use a custom lookup/agent that reuses the checked result. No clean dep-free seam today.
Same residual in both plugins; documented as accepted-risk in SECURITY.md. Low practical severity (delivery is blind-ish) but worth closing before any public deployment.
Both
corsproxy/andactivitypub/'sgatedFetchvalidate the target host withdns.lookupand reject private/loopback addresses. Butfetch()then resolves the hostname again, independently — so a hostname that returns a public address to the guard's lookup and a private one to fetch's resolution (DNS rebinding) can slip past.Fix direction: pin the validated IP into the actual connection (resolve once, connect to the literal address with the original Host header), or use a custom lookup/agent that reuses the checked result. No clean dep-free seam today.
Same residual in both plugins; documented as accepted-risk in SECURITY.md. Low practical severity (delivery is blind-ish) but worth closing before any public deployment.