apache/shenyu · error · IllegalArgumentException

Access to localhost is not allowed

Error message

Access to localhost is not allowed

What it means

As part of SSRF defense, validateHostForSSRF rejects hostnames that resolve to localhost (localhost, 127.x, ::1, etc.) and throws IllegalArgumentException "Access to localhost is not allowed". This prevents the admin server from being tricked into calling its own loopback interface where internal admin APIs would be reachable.

Solutions

  1. Use the backend service's real network address (container/service DNS name, LAN IP, or public domain) instead of localhost.
  2. In Docker Compose/K8s, use service names (e.g. http://backend:8080) rather than localhost or 127.0.0.1.
  3. Do not disable or bypass this check in production; it is an intentional SSRF safeguard.
  4. If the private-network policy is too strict for your topology, adjust allowlists consciously with a security review rather than catching and ignoring the exception.

Example fix

// before
validateUrlForSSRF("http://localhost:8080/actuator/health");
// after
validateUrlForSSRF("http://backend-service:8080/actuator/health");
Defensive patterns

Strategy: try-catch

Validate before calling

HttpUrl parsed = HttpUrl.parse(url);
String host = parsed != null ? parsed.host().toLowerCase() : "";
boolean isLoopback = "localhost".equals(host) || host.equals("127.0.0.1")
    || host.startsWith("127.") || host.equals("::1") || host.equals("0.0.0.0");
if (isLoopback) throw new IllegalArgumentException("localhost targets are not allowed");

Try / catch

try {
    UrlSecurityUtils.validateUrlForSSRF(url);
} catch (IllegalArgumentException e) {
    log.warn("Blocked localhost/internal target (SSRF guard): {}", url);
    return ResponseEntity.badRequest().body("localhost targets are not allowed");
}

Prevention

When it happens

Trigger: Configuring a target URL whose host is 'localhost', '127.0.0.1', a loopback alias, or '0.0.0.0' — either by accident (admin meant a local dev backend) or as an SSRF attack payload.

Common situations: Developers testing in-container setups pointing the URL at localhost instead of the service's Docker DNS name; attackers submitting webhook/health-check URLs targeting the gateway's own loopback to hit internal admin endpoints.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/253a11b9e0792b71. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/UrlSecurityUtils.java:87

    }

    /**
     * Validate host to prevent SSRF attacks.
     *
     * @param host the host to validate
     * @param port the port to validate
     * @throws IllegalArgumentException if the host is not allowed
     */
    public static void validateHostForSSRF(final String host, final int port) {
        if (Objects.isNull(host) || host.trim().isEmpty()) {
            throw new IllegalArgumentException("Host cannot be empty");
        }

        String normalizedHost = host.toLowerCase().trim();

        // Check for localhost variations
        if (isLocalhost(normalizedHost)) {
            throw new IllegalArgumentException("Access to localhost is not allowed");
        }

        // Check for private IP addresses
        if (isPrivateOrInternalIP(normalizedHost)) {
            throw new IllegalArgumentException("Access to private or internal IP addresses is not allowed");
        }

        // Check for sensitive ports
        if (isSensitivePort(port)) {
            throw new IllegalArgumentException("Access to sensitive ports is not allowed");
        }

        // Additional validation for DNS resolution
        try {
            InetAddress[] addresses = InetAddress.getAllByName(normalizedHost);
            for (InetAddress address : addresses) {
                if (address.isLoopbackAddress() || address.isLinkLocalAddress()
                        || address.isSiteLocalAddress() || address.isAnyLocalAddress()) {

View on GitHub (pinned to 567142e072)