{"record":{"id":"56587e69e04a889f","repo":"spring-projects/spring-ai","slug":"host-is-not-in-the-allowed-hosts-list-configur","errorCode":null,"errorMessage":"Host '' is not in the allowed hosts list. Configure MediaFetcher with the appropriate allowed hosts.","messagePattern":"Host '' is not in the allowed hosts list\\. Configure MediaFetcher with the appropriate allowed hosts\\.","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/MediaFetcher.java","lineNumber":147,"sourceCode":"\n\t/**\n\t * Fetches the content at {@code uri} and returns it as a byte array.\n\t *\n\t * <p>\n\t * The caller is responsible for validating the URI (protocol, host) before invoking\n\t * this method. This method enforces size limits and socket-level SSRF protection.\n\t * @param uri the URI to fetch\n\t * @return the response body as a byte array\n\t * @throws SecurityException if the response exceeds\n\t * {@link #DEFAULT_MAX_FETCH_SIZE_BYTES} or the host resolves to a blocked internal\n\t * address\n\t * @throws org.springframework.web.client.RestClientException on HTTP or I/O errors\n\t */\n\tpublic byte[] fetch(URI uri) {\n\t\tif (!this.allowedHosts.isEmpty()) {\n\t\t\tString host = uri.getHost();\n\t\t\tif (!isHostAllowed(host)) {\n\t\t\t\tthrow new SecurityException(\"Host '\" + host\n\t\t\t\t\t\t+ \"' is not in the allowed hosts list. Configure MediaFetcher with the appropriate allowed hosts.\");\n\t\t\t}\n\t\t}\n\t\treturn this.restClient.get().uri(uri).exchange((request, response) -> {\n\t\t\tlong contentLength = response.getHeaders().getContentLength();\n\t\t\tif (contentLength > DEFAULT_MAX_FETCH_SIZE_BYTES) {\n\t\t\t\tthrow new SecurityException(\"Media URL response exceeds maximum allowed size of \"\n\t\t\t\t\t\t+ DEFAULT_MAX_FETCH_SIZE_BYTES + \" bytes: \" + uri);\n\t\t\t}\n\t\t\ttry (InputStream body = response.getBody()) {\n\t\t\t\treturn readWithSizeLimit(body, DEFAULT_MAX_FETCH_SIZE_BYTES);\n\t\t\t}\n\t\t}, true);\n\t}\n\n\t/**\n\t * Returns {@code true} if {@code host} is permitted by the allowlist. An entry that\n\t * starts with {@code *.} is treated as a suffix wildcard matching any subdomain (e.g.","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/MediaFetcher.java#L129-L165","documentation":"MediaFetcher.fetch() enforces an SSRF-style allowlist: when allowedHosts is non-empty and the URI's host does not match any allowed host (including wildcard rules), a SecurityException is thrown before any HTTP request is made. This prevents fetching media from unapproved or internal hosts.","triggerScenarios":"Calling fetch(uri) where uri's host is not in the MediaFetcher's configured allowedHosts list (and no wildcard rule matches), e.g. host '' when the URI has no host, or an external host while only internal hosts are allowed.","commonSituations":"Misconfigured allowed-hosts list missing a new media CDN domain; URIs without a host (relative or malformed) producing host ''; wildcard patterns like *.example.com not covering apex example.com; internal metadata addresses deliberately blocked.","solutions":["Add the required host (or wildcard pattern) to MediaFetcher's allowedHosts configuration.","Fix the media URI so it has a valid, fully-qualified host.","If apex domains should be allowed, add the apex explicitly since *.example.com does not match example.com.","If no restriction is desired, configure the fetcher with an empty allowedHosts (allow-all) — only for trusted inputs."],"exampleFix":"// before\nnew MediaFetcher(restClient, List.of(\"media.example.com\"));\n// fetch(new URI(\"https://cdn.other.org/img.png\"))\n// after\nnew MediaFetcher(restClient, List.of(\"media.example.com\", \"cdn.other.org\"));","handlingStrategy":"validation","validationCode":"URI uri = URI.create(mediaUrl);\nif (uri.getHost() == null || !isHostAllowedByConfig(uri.getHost())) {\n    throw new IllegalArgumentException(\"Media URL host not allowed: \" + uri.getHost());\n}","typeGuard":"static boolean hasAllowedHost(URI uri, List<String> allowed) {\n    String h = uri.getHost();\n    return h != null && (allowed.isEmpty() || allowed.stream().anyMatch(p -> hostMatches(p, h)));\n}","tryCatchPattern":"try {\n    byte[] data = mediaFetcher.fetch(uri);\n} catch (SecurityException e) {\n    // skip media or fix allowlist configuration\n}","preventionTips":["Keep the allowlist in sync with all media CDN domains you use","Validate media URLs are absolute with a host before fetching","Remember wildcards do not match apex domains","Never fetch user-supplied URLs without allowlist enforcement"],"tags":["security","ssrf","allowlist","network","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"}