{"record":{"id":"a0c494f1a22c5c52","repo":"pinpoint-apm/pinpoint","slug":"webhook-url-resolves-to-a-non-public-address","errorCode":null,"errorMessage":"Webhook URL resolves to a non-public address","messagePattern":"Webhook URL resolves to a non-public address","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"commons-server/src/main/java/com/navercorp/pinpoint/common/server/webhook/WebhookUrlValidator.java","lineNumber":174,"sourceCode":"    private static void validateHostWithoutResolving(String host) {\n        String normalizedHost = normalizeHost(host);\n        if (isBlockedHostName(normalizedHost)) {\n            throw new IllegalArgumentException(\"Webhook URL host is not allowed\");\n        }\n\n        IPAddress address = toHostLiteralAddress(normalizedHost);\n        if (address != null) {\n            validateResolvedAddress(normalizedHost, address.toInetAddress(), WebhookHostPolicy.denyAll());\n        }\n    }\n\n    public static void validateResolvedAddress(String host, InetAddress address, WebhookHostPolicy policy) {\n        Objects.requireNonNull(policy, \"policy\");\n        if (address == null) {\n            throw new IllegalArgumentException(\"Webhook URL resolved address is required\");\n        }\n        if (isBlockedAddress(address)) {\n            throw new IllegalArgumentException(\"Webhook URL resolves to a non-public address\");\n        }\n        if (isPrivateAddress(address) && !isAllowedPrivateHost(host, policy)) {\n            throw new IllegalArgumentException(\"Webhook URL resolves to a private address that is not allowed\");\n        }\n    }\n\n    /**\n     * The policy matches host names only. Allowing an IP literal to match would let a caller\n     * reach an internal address without going through an allowed host name.\n     */\n    private static boolean isAllowedPrivateHost(String host, WebhookHostPolicy policy) {\n        if (host == null) {\n            return false;\n        }\n        String normalizedHost = normalizeHost(host);\n        if (toHostLiteralAddress(normalizedHost) != null) {\n            return false;\n        }","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/commons-server/src/main/java/com/navercorp/pinpoint/common/server/webhook/WebhookUrlValidator.java#L156-L192","documentation":"validateResolvedAddress rejects addresses that isBlockedAddress classifies as non-public: loopback, link-local, unspecified (0.0.0.0/::), multicast, or other reserved ranges. This prevents webhook calls from reaching internal infrastructure (SSRF mitigation). The URL host may look public but its DNS result points at a protected range.","triggerScenarios":"Calling validateResolvedAddress with an InetAddress that is loopback, link-local (169.254.x.x, fe80::), any-local (0.0.0.0), multicast, or otherwise in the blocked set — e.g. 'http://127.0.0.1/hook' or a DNS name resolving to 169.254.169.254.","commonSituations":"SSRF attempts or misconfiguration where a webhook points to cloud metadata services (169.254.169.254), internal loopback services, or hostnames that recently started resolving to internal IPs.","solutions":["Point the webhook at a genuinely public endpoint; remove internal/metadata addresses from webhook config.","If the address is intentionally internal and trusted, extend WebhookHostPolicy to explicitly allow it (e.g. private-host allowlist).","Audit DNS for the webhook host to see why it resolves to a blocked range."],"exampleFix":"// before\nString url = \"http://169.254.169.254/latest/meta-data\";\n// after\nString url = \"https://hooks.example.com/webhook\";","handlingStrategy":"validation","validationCode":"InetAddress addr = InetAddress.getByName(host);\nif (addr.isLoopbackAddress() || addr.isLinkLocalAddress() || addr.isAnyLocalAddress() || addr.isMulticastAddress()) {\n    throw new IllegalArgumentException(\"webhook host resolves to a non-public address\");\n}","typeGuard":"boolean isPublicAddress(InetAddress a) { return !(a.isLoopbackAddress() || a.isLinkLocalAddress() || a.isAnyLocalAddress() || a.isMulticastAddress()); }","tryCatchPattern":"try {\n    WebhookUrlValidator.validateResolvedAddress(host, addr, policy);\n} catch (IllegalArgumentException e) {\n    securityLog.warn(\"Blocked webhook to non-public address: {} -> {}\", host, addr);\n}","preventionTips":["Treat this error as a potential SSRF attempt and log the host/address","Resolve the hostname and inspect the address before configuring webhooks","Keep metadata/loopback endpoints out of webhook configuration"],"tags":["ssrf","security","webhook","ip-validation"],"backgroundTag":"invalid-url","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}