{"record":{"id":"0a7304df31c76673","repo":"conductor-oss/conductor","slug":"agenturl-resolves-to-a-cloud-metadata-address-ss","errorCode":null,"errorMessage":"agentUrl resolves to a cloud metadata address — SSRF blocked: {address}","messagePattern":"agentUrl resolves to a cloud metadata address — SSRF blocked: (.+?)","errorType":"validation","errorClass":"NonRetryableException","httpStatus":null,"severity":"critical","filePath":"ai/src/main/java/org/conductoross/conductor/ai/a2a/A2AService.java","lineNumber":448,"sourceCode":"     * behind a network-layer firewall that blocks egress to private ranges.\n     */\n    public void validateAgentUrl(String rawUrl) {\n        if (rawUrl == null || rawUrl.isBlank()) {\n            throw new NonRetryableException(\"agentUrl must not be blank\");\n        }\n        try {\n            URL url = new URL(rawUrl.trim());\n            String scheme = url.getProtocol();\n            if (!\"http\".equals(scheme) && !\"https\".equals(scheme)) {\n                throw new NonRetryableException(\"agentUrl must use http or https, got: \" + scheme);\n            }\n            String host = url.getHost();\n            InetAddress[] addresses = InetAddress.getAllByName(host);\n            for (InetAddress addr : addresses) {\n                // Cloud metadata endpoints are blocked even when private networks are allowed.\n                if (isMetadataAddress(addr)) {\n                    A2AMetrics.ssrfBlocked();\n                    throw new NonRetryableException(\n                            \"agentUrl resolves to a cloud metadata address — SSRF blocked: \"\n                                    + addr.getHostAddress());\n                }\n                if (allowPrivateNetwork) {\n                    continue;\n                }\n                if (addr.isLoopbackAddress()\n                        || addr.isSiteLocalAddress()\n                        || addr.isLinkLocalAddress()\n                        || addr.isAnyLocalAddress()\n                        || isUniqueLocalIpv6(addr)) {\n                    A2AMetrics.ssrfBlocked();\n                    throw new NonRetryableException(\n                            \"agentUrl resolves to a private/reserved address — SSRF blocked: \"\n                                    + addr.getHostAddress()\n                                    + \" (set \"\n                                    + ALLOW_PRIVATE_NETWORK_PROPERTY\n                                    + \"=true to allow private-network agents)\");","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/ai/src/main/java/org/conductoross/conductor/ai/a2a/A2AService.java#L430-L466","documentation":"Thrown by A2AService.validateAgentUrl() when the agentUrl hostname resolves to a cloud metadata endpoint address (IPv4 169.254.x.x or IPv6 fd00:ec2::254 / fe80::a9fe:a9fe). This SSRF guard blocks access to AWS IMDS, ECS metadata, and equivalent GCP/Azure endpoints even when private networks are explicitly allowed. It is a NonRetryableException.","triggerScenarios":"The agentUrl's hostname DNS-resolves to an IP in the 169.254.0.0/16 range (AWS IMDS 169.254.169.254, ECS 169.254.170.2) or matches known IPv6 metadata addresses. This check runs unconditionally — it cannot be overridden by the allow-private-network property.","commonSituations":"A malicious actor supplies an agentUrl that resolves to 169.254.169.254 to steal cloud credentials (IMDSv1). A DNS rebinding attack points a legitimate-looking hostname to a metadata IP. A misconfigured internal hostname accidentally resolves to the link-local range.","solutions":["Reject and investigate — this is a potential SSRF attack vector, not a configuration issue","If legitimate, deploy the agent behind a public DNS name that does not resolve to metadata addresses","Ensure the agentUrl hostname resolves to the agent's actual public or allowed private IP","If using a local agent, set conductor.a2a.client.allow-private-network=true AND use a non-metadata private address"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Reject metadata addresses before calling A2A (defensive, in addition to the built-in guard)\ntry {\n    InetAddress[] addrs = InetAddress.getAllByName(new URL(agentUrl).getHost());\n    for (InetAddress addr : addrs) {\n        byte[] b = addr.getAddress();\n        if (b.length == 4 && (b[0] & 0xFF) == 169 && (b[1] & 0xFF) == 254) {\n            throw new SecurityException(\"URL resolves to cloud metadata endpoint\");\n        }\n    }\n} catch (Exception e) {\n    // handle\n}","typeGuard":null,"tryCatchPattern":"try {\n    a2aService.validateAgentUrl(agentUrl);\n} catch (NonRetryableException e) {\n    if (e.getMessage().contains(\"cloud metadata\")) {\n        // Potential SSRF attack — log and investigate\n        log.error(\"SSRF attempt blocked: {}\", e.getMessage());\n        SecurityAudit.log(agentUrl, \"SSRF_METADATA_BLOCKED\");\n    }\n    throw e;\n}","preventionTips":["Never allow user-supplied URLs that resolve to 169.254.x.x","Treat this error as a potential security incident and investigate the source of the URL","Block cloud metadata IP ranges at the network/firewall layer in addition to application checks","Use SSRF protection at the network layer (egress firewall rules) for defense in depth"],"tags":["a2a","ssrf","security","cloud-metadata","non-retryable"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}