{"record":{"id":"ac5b2de3fa3b5f45","repo":"ruvnet/ruflo","slug":"resolved-ip-for-hostname-is-internal-address","errorCode":null,"errorMessage":"Resolved IP for ${hostname} is internal (${address})","messagePattern":"Resolved IP for (.+?) is internal \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/server/urlSafety.ts","lineNumber":75,"sourceCode":"\t\t// If the hostname is a raw IP literal, validate it\n\t\tconst cleanHostname = hostname.replace(/^\\[|]$/g, \"\");\n\t\tif (isIP(cleanHostname)) {\n\t\t\treturn !isUnsafeIp(cleanHostname);\n\t\t}\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Assert that a resolved IP address is safe (not internal/private).\n * Throws if the IP is internal. Used in undici's custom DNS lookup\n * to validate IPs at connection time (prevents TOCTOU DNS rebinding).\n */\nexport function assertSafeIp(address: string, hostname: string): void {\n\tif (isUnsafeIp(address)) {\n\t\tthrow new Error(`Resolved IP for ${hostname} is internal (${address})`);\n\t}\n}\n","sourceCodeStart":57,"sourceCodeEnd":78,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/ruflo/src/ruvocal/src/lib/server/urlSafety.ts#L57-L78","documentation":"Thrown by assertSafeIp in urlSafety.ts, an SSRF defense hooked into undici's custom DNS resolution. After a hostname resolves, the IP is checked against internal ranges (IPv4 0.0.0.0/8, 100.64.0.0/10, 127/8, 169.254/16, 172.16/12, 192.168/16; IPv6 loopback, link-local, and IPv4-mapped forms like ::ffff:127.0.0.1; unknown formats are blocked too). Validating at connect time instead of URL-parse time closes the TOCTOU window of DNS rebinding.","triggerScenarios":"Any outbound fetch through the guarded client whose hostname resolves to a private/loopback/link-local IP: a URL pointing at http://127.0.0.1:8080 or http://192.168.1.10, an internal DNS name (e.g., http://metadata, http://db.internal) that resolves into RFC1918 space, or an external domain that rebinds to an internal address between validation and connection.","commonSituations":"Users asking the chat to fetch a URL on their LAN; a fetch-url endpoint being probed for cloud metadata (169.254.169.254); dev setups pointing at localhost services through a hostname that resolves internally; DNS records intentionally mapping a public name to an internal address.","solutions":["Use a genuinely public URL — the block is intentional for user-supplied fetch targets.","If you operate the deployment and genuinely need an internal target, whitelist it in the URL-safety layer (extend isValidUrl's explicit localhost/host.docker.internal allowance) instead of disabling assertSafeIp globally.","For local development needs, prefer hostnames already allowed by isValidUrl (localhost, 127.0.0.1, host.docker.internal) rather than other RFC1918 addresses.","Never remove the connect-time check in undici's lookup — that reopens DNS-rebinding SSRF."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import { isValidUrl } from \"$lib/server/urlSafety\";\n\nif (!isValidUrl(targetUrl)) {\n\treturn new Response(\"URL not allowed\", { status: 400 });\n}","typeGuard":"import { isIP } from \"node:net\";\n\nfunction isPublicIpLiteral(host: string): boolean {\n\tconst h = host.replace(/^\\[|]$/g, \"\");\n\treturn isIP(h) === 0 ? true : !isInternalIp(h); // pair with your own subnet list\n}","tryCatchPattern":"try {\n\tawait fetchTarget(url);\n} catch (err) {\n\tif (String(err).includes(\"is internal\")) {\n\t\treturn new Response(\"Refusing to fetch internal addresses\", { status: 400 }); // expected block, not a bug\n\t}\n\tthrow err;\n}","preventionTips":["Validate user-supplied URLs with isValidUrl before any fetch.","Keep the undici connect-time assertSafeIp hook — it is the DNS-rebinding defense; never bypass it.","Expect and handle this error as a normal 400 path; do not widen the IP allowlist under pressure.","Block unknown address formats (the guard already does) rather than defaulting to allow."],"tags":["security","ssrf","network","dns"],"backgroundTag":"ssrf-protection-blocked","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}