spring-projects/spring-ai · critical · java.lang.SecurityException

Host '' resolves to a blocked internal address:

Error message

Host '' resolves to a blocked internal address: 

What it means

MediaFetcher's custom DNS resolver wraps the system resolver and, immediately after resolving the media URL's host, checks every returned InetAddress against URLValidator.isBlockedAddress. If any resolved address is internal (loopback/private/link-local), resolution itself is aborted with this SecurityException so the request never proceeds to a connection. Like the other checks here it is a defense against SSRF via DNS.

Source

Thrown at models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/MediaFetcher.java:311

	/**
	 * DNS resolver that rejects hostnames resolving to internal addresses. Acts as an
	 * early-rejection layer for hostname-based requests, complementing the socket-level
	 * check in {@link SsrfBlockingPlainSocketFactory} and
	 * {@link SsrfBlockingSSLSocketFactory} which covers raw IP literals that skip DNS
	 * resolution entirely.
	 */
	private static final class SsrfSafeDnsResolver implements DnsResolver {

		@Override
		public InetAddress[] resolve(String host) throws UnknownHostException {
			InetAddress[] addresses = SystemDefaultDnsResolver.INSTANCE.resolve(host);
			for (InetAddress address : addresses) {
				if (URLValidator.isBlockedAddress(address)) {
					// Throw SecurityException (RuntimeException) rather than
					// UnknownHostException so it propagates through Spring RestClient
					// without being wrapped in ResourceAccessException.
					throw new SecurityException(
							"Host '" + host + "' resolves to a blocked internal address: " + address.getHostAddress());
				}
			}
			return addresses;
		}

		@Override
		public String resolveCanonicalHostname(String host) throws UnknownHostException {
			return SystemDefaultDnsResolver.INSTANCE.resolveCanonicalHostname(host);
		}

	}

}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Point the media URL at a host that resolves to public addresses (public S3 bucket/CDN endpoint).
  2. If the media is internal, download it yourself with your own authorized client and pass the bytes inline to Media instead of a URL.
  3. Check DNS with dig/nslookup: if the host unexpectedly resolves to private IPs, correct the DNS record or use the correct public hostname.
  4. In test environments, host the media on an externally resolvable endpoint or embed it inline rather than using loopback URLs.

Example fix

// before
Media media = new Media(MimeTypeUtils.IMAGE_JPEG, new URL("http://internal-artifacts.corp/media/cat.jpg"));
// after: inline bytes fetched by your own internal client
byte[] bytes = artifactsClient.download("/media/cat.jpg");
Media media = new Media(MimeTypeUtils.IMAGE_JPEG, bytes);
Defensive patterns

Strategy: validation

Validate before calling

// Java: mirror the library's DNS-level check before submitting the URL
InetAddress[] addrs = InetAddress.getAllByName(mediaUrl.getHost());
for (InetAddress a : addrs) {
    if (a.isLoopbackAddress() || a.isLinkLocalAddress() || a.isSiteLocalAddress() || a.isAnyLocalAddress()) {
        throw new IllegalArgumentException("Host " + mediaUrl.getHost() + " resolves to internal address " + a.getHostAddress());
    }
}

Try / catch

try {
    model.call(prompt);
} catch (SecurityException e) {
    if (e.getMessage().contains("resolves to a blocked internal address")) {
        // treat as rejected URL input; surface to user, no retry
    } else { throw e; }
}

Prevention

When it happens

Trigger: fetch() of a media URL whose hostname's DNS A/AAAA records include (or resolve solely to) a blocked internal address — checked in resolve() before any socket is opened.

Common situations: Split-horizon DNS where an internal name resolves to 10.x/192.168.x addresses; running the app inside a cluster where public-looking hostnames resolve to private IPs; localhost aliases or /etc/hosts entries mapping test domains to 127.0.0.1; stale DNS records repointed to private infrastructure.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/8aebefefa6cf385b. Report an issue: GitHub.