{"record":{"id":"b321b30ff53f23a1","repo":"spring-projects/spring-ai","slug":"unsupported-url-protocol","errorCode":null,"errorMessage":"Unsupported URL protocol: ","messagePattern":"Unsupported URL protocol: ","errorType":"exception","errorClass":"SecurityException","httpStatus":null,"severity":"error","filePath":"models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java","lineNumber":563,"sourceCode":"\t\t\t\t\t\tcatch (SecurityException | RestClientException e) {\n\t\t\t\t\t\t\tthrow new RuntimeException(\"Failed to read media data from URL: \" + text, e);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthrow new SecurityException(\"URL is not valid under strict validation rules: \" + text);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Assume it's base64-encoded image data\n\t\t\t\t\tsourceBuilder.bytes(SdkBytes.fromByteArray(Base64.getDecoder().decode(text)));\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (media.getData() instanceof URL url) {\n\n\t\t\t\ttry {\n\t\t\t\t\tString protocol = url.getProtocol();\n\t\t\t\t\tif (!\"http\".equalsIgnoreCase(protocol) && !\"https\".equalsIgnoreCase(protocol)) {\n\t\t\t\t\t\tthrow new SecurityException(\"Unsupported URL protocol: \" + protocol);\n\t\t\t\t\t}\n\t\t\t\t\tbyte[] bytes = this.mediaFetcher.fetch(url.toURI());\n\t\t\t\t\tsourceBuilder.bytes(SdkBytes.fromByteArrayUnsafe(bytes)).build();\n\t\t\t\t}\n\t\t\t\tcatch (SecurityException | RestClientException | URISyntaxException e) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Failed to read media data from URL: \" + url, e);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow new IllegalArgumentException(\"Invalid Image content type: \" + media.getData().getClass());\n\t\t\t}\n\n\t\t\treturn ContentBlock.fromImage(ImageBlock.builder()\n\t\t\t\t.source(sourceBuilder.build())\n\t\t\t\t.format(BedrockMediaFormat.getImageFormat(mimeType))\n\t\t\t\t.build());\n\t\t}\n\t\telse if (BedrockMediaFormat.isSupportedDocumentFormat(mimeType)) { // Document","sourceCodeStart":545,"sourceCodeEnd":581,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java#L545-L581","documentation":"Thrown when Media data is a java.net.URL whose protocol is not http or https. mapMediaToContentBlock enforces an allowlist of HTTP/HTTPS to prevent SSRF-style access via file:, ftp:, jar:, etc. The protocol is included in the message.","triggerScenarios":"Constructing Media with new URL(\"file:///path/img.png\"), ftp://, or any non-HTTP(S) URL and sending it to the Bedrock model. Also triggered when the URL itself is fine but the subsequent fetch/URI conversion throws SecurityException, RestClientException, or URISyntaxException, which are rewrapped (see error 303).","commonSituations":"Developers building media from local files or classpath resources using file:// URLs; test fixtures using ftp servers; migrating code from other AI libraries that accepted file URLs.","solutions":["Switch to an http/https URL for the media resource.","Load the local resource yourself and pass byte[]/base64 data instead of a URL.","Upload the asset to an accessible HTTP endpoint (e.g. S3 with public/signed URL over https).","Check url.getProtocol() before constructing Media to fail fast with a clear message."],"exampleFix":"// before\nnew Media(MimeTypeUtils.IMAGE_PNG, new URL(\"file:///img/cat.png\"));\n// after\nbyte[] bytes = Files.readAllBytes(Path.of(\"/img/cat.png\"));\nnew Media(MimeTypeUtils.IMAGE_PNG, bytes);","handlingStrategy":"type-guard","validationCode":"String proto = url.getProtocol();\nif (!\"http\".equalsIgnoreCase(proto) && !\"https\".equalsIgnoreCase(proto)) {\n    throw new IllegalArgumentException(\"Only http/https media URLs supported, got: \" + proto);\n}","typeGuard":"boolean isHttpUrl(URL url) {\n    return url != null && (\"http\".equalsIgnoreCase(url.getProtocol()) || \"https\".equalsIgnoreCase(url.getProtocol()));\n}","tryCatchPattern":"try {\n    model.call(prompt);\n} catch (IllegalArgumentException e) {\n    if (e.getCause() instanceof SecurityException se && se.getMessage().contains(\"Unsupported URL protocol\")) {\n        // load bytes locally and resend with byte[] data\n    } else throw e;\n}","preventionTips":["Assert protocol is http/https before constructing Media with a URL","Use Files.readAllBytes for local assets instead of file:// URLs","Host assets on https endpoints (e.g. S3) when remote access is needed"],"tags":["security","url-protocol","ssrf-protection","bedrock"],"backgroundTag":"unsupported-operation","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"}