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")); } }
}
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.
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.