GuardAPI Logo
GuardAPI

Fix Shadow API Exposure in Javalin

Shadow APIs are the blind spots in your attack surface. In Javalin, these are 'ghost' routes—undocumented, unauthenticated, or legacy endpoints that bypass security audits and WAF rules. If an endpoint exists in the binary but not in your documentation, it's a backdoor waiting to be kicked in. This guide focuses on eliminating these exposures through strict route gating and environment-aware registration.

The Vulnerable Pattern

import io.javalin.Javalin;

public class App { public static void main(String[] args) { var app = Javalin.create().start(8080);

    // Public API
    app.get("/api/v1/user", ctx -> ctx.result("User Data"));

    // SHADOW API: Leftover debug endpoint leaking environment variables
    // No authentication, undocumented, and active in production
    app.get("/debug/env", ctx -> ctx.json(System.getenv()));

    // SHADOW API: Legacy endpoint used for testing that still has DB write access
    app.post("/internal/test-db-wipe", ctx -> {
        // Database.wipe();
        ctx.result("Database wiped for testing");
    });
}

}

The Secure Implementation

To kill Shadow APIs in Javalin, you must apply a zero-trust approach to route registration. First, implement environment gating: use boolean flags to ensure debug or 'internal-only' routes are never registered in the production runtime. Second, use Javalin's `before` handlers as a global security interceptor for specific path patterns (e.g., `/internal/*`) to ensure that even if a route is accidentally exposed, it remains unreachable without valid high-privilege credentials. Finally, treat your route definitions as code-level documentation; if it isn't explicitly defined in your production routing manifest, it shouldn't exist in the compiled artifact.

import io.javalin.Javalin;
import io.javalin.http.UnauthorizedResponse;

public class App { public static void main(String[] args) { boolean isProd = “production”.equals(System.getenv(“APP_ENV”));

    var app = Javalin.create(config -> {
        // Disable route overview in production to prevent discovery
        if (isProd) {
            config.showJavalinBanner = false;
        }
    }).start(8080);

    // 1. Global Authorization Filter for sensitive namespaces
    app.before("/internal/*", ctx -> {
        String apiKey = ctx.header("X-Internal-Key");
        if (apiKey == null || !apiKey.equals(System.getenv("INTERNAL_SECRET"))) {
            throw new UnauthorizedResponse("Forbidden");
        }
    });

    // Standard Public Routes
    app.get("/api/v1/user", ctx -> ctx.result("User Data"));

    // 2. Environment-Gated Routes: Prevent shadow endpoints from ever being registered in Prod
    if (!isProd) {
        app.get("/debug/env", ctx -> ctx.json(System.getenv()));
        app.post("/internal/test-db-wipe", ctx -> ctx.result("Wiped"));
    }
}

}

System Alert • ID: 7283
Target: Javalin API
Potential Vulnerability

Your Javalin API might be exposed to Shadow API Exposure

74% of Javalin apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.

RUN FREE SECURITY DIAGNOSTIC
GuardLabs Engine: ONLINE

Free Tier • No Credit Card • Instant Report

Verified by Ghost Labs Security Team

This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.