SEO audit fetches a URL before the private-address check
Imported from GitHub issue El-SaMa/oma#159 by @El-SaMa.
What is wrong
fetchUrl in services/core-api/src/seo-audit.ts requests the submitted URL, following redirects, and only then calls resolvePublicAddresses on the final hostname.
normalizeAuditUrl blocks a literal private IP and localhost. It does not resolve the name first. resolvePublicAddresses asks 1.1.1.1 and 8.8.8.8, not the address the connection actually used.
Any signed-in customer can submit a URL (apps/web/app/api/ai/seo-audit/route.ts, 12 per rate-limit window). A redirect onto a LAN address is already sent before the later check throws. When that check fails, the body is dropped and the audit says the site did not respond, so this is the portal making an internal request, not a proven database read. If the server's own DNS and 1.1.1.1 disagree, the internal response is returned in the audit.
Webhooks and uptime monitors already use assertSafePublicHttpUrl in services/core-api/src/platform/ssrf-url.ts, which resolves and rejects private addresses before the request.
Fix
Resolve and reject private, link-local, and metadata addresses before connecting. Pin the connection to the checked address so a later DNS change cannot swing it. Re-check every redirect the same way. Reuse assertSafePublicHttpUrl rather than a second checker.
Found
Read-only review of origin/main, 8 Oct 2026. Not confirmed against the live server.