{"record":{"id":"cd769cc9f09c2570","repo":"spring-projects/spring-ai","slug":"url-host-resolves-to-a-blocked-internal-address","errorCode":null,"errorMessage":"URL host '' resolves to a blocked internal address: ","messagePattern":"URL host '' resolves to a blocked internal address: ","errorType":"exception","errorClass":"java.lang.SecurityException","httpStatus":null,"severity":"error","filePath":"models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/URLValidator.java","lineNumber":137,"sourceCode":"\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Resolves all IP addresses for the given hostname and throws\n\t * {@link SecurityException} if any resolve to a loopback, link-local, site-local, or\n\t * wildcard address. Protects against SSRF via internal network access (including IPv6\n\t * equivalents) and limits exposure from DNS rebinding by checking all returned\n\t * addresses.\n\t * @param host the hostname to check\n\t * @throws SecurityException if the host resolves to a blocked internal address or\n\t * cannot be resolved\n\t */\n\tpublic static void assertNoInternalAddress(String host) {\n\t\ttry {\n\t\t\tfor (InetAddress address : InetAddress.getAllByName(host)) {\n\t\t\t\tif (isBlockedAddress(address)) {\n\t\t\t\t\tthrow new SecurityException(\"URL host '\" + host + \"' resolves to a blocked internal address: \"\n\t\t\t\t\t\t\t+ address.getHostAddress());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcatch (UnknownHostException e) {\n\t\t\tthrow new SecurityException(\"Failed to resolve host: \" + host, e);\n\t\t}\n\t}\n\n\t/**\n\t * Returns {@code true} if the given address is a loopback, link-local, site-local, or\n\t * wildcard address. Covers both IPv4 and IPv6 private/internal ranges.\n\t * @param address the address to test\n\t * @return {@code true} if the address should be blocked\n\t */\n\tpublic static boolean isBlockedAddress(InetAddress address) {\n\t\treturn address.isLoopbackAddress() || address.isLinkLocalAddress() || address.isSiteLocalAddress()\n\t\t\t\t|| address.isAnyLocalAddress();","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/URLValidator.java#L119-L155","documentation":"URLValidator.assertNoInternalAddress resolves the given host with InetAddress.getAllByName and throws this SecurityException if any resolved IP is a blocked internal address (loopback, link-local, site-local/private, wildcard). It is part of the strict URL validation (isValidURLStrict) performed on user-supplied media URLs before fetching, to prevent SSRF against internal networks.","triggerScenarios":"Passing a URL to strict validation (isValidURLStrict) whose host's DNS resolution includes any internal IP — e.g. localhost names, 127.0.0.1, 10.x/172.16.x/192.168.x ranges, 169.254.169.254 metadata IP, or IPv6 loopback/link-local.","commonSituations":"Developer URLs like http://localhost:8080/media.png used in integration tests; internal-only artifact hosts; DNS in containerized/Kubernetes environments where service names resolve to cluster-internal IPs; SSRF probes targeting cloud metadata endpoints.","solutions":["Use a public-facing URL whose host resolves only to public IP addresses.","For internal content, fetch it yourself and pass the media bytes inline to Media rather than a URL.","Inspect resolution with nslookup/dig and fix unintended private DNS records or /etc/hosts entries.","Catch SecurityException in your input-validation layer and reject the URL with a user-facing message before invoking the model."],"exampleFix":"// before\nURLValidator.assertNoInternalAddress(\"169.254.169.254\"); // throws\n// after\nURLValidator.assertNoInternalAddress(\"cdn.example.com\"); // public resolution passes","handlingStrategy":"validation","validationCode":"// Java: run the same strict validation before invoking the model\ntry {\n    org.springframework.ai.bedrock.converse.api.URLValidator.assertNoInternalAddress(new URI(mediaUrl).getHost());\n} catch (SecurityException e) {\n    throw new IllegalArgumentException(\"Rejecting media URL: \" + e.getMessage(), e);\n}","typeGuard":null,"tryCatchPattern":"try {\n    model.call(prompt);\n} catch (SecurityException e) {\n    if (e.getMessage().startsWith(\"URL host\")) {\n        // return 400-style validation error to the caller; no retry\n    } else { throw e; }\n}","preventionTips":["Call URLValidator.assertNoInternalAddress/isValidURLStrict on user-supplied URLs at ingestion time, not at model-call time.","Whitelist approved media hosts rather than accepting arbitrary URLs.","Never whitelist localhost or private-range hosts in your URL allowlist.","Log rejected hosts with resolved addresses to spot SSRF probing early."],"tags":["security","ssrf","dns","url-validation","bedrock"],"backgroundTag":"invalid-url","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}