How to fix Shadow API Exposure
in Salvo
Executive Summary
Shadow APIs represent the silent killers of modern microservices. In Salvo, these undocumented endpoints typically emerge from 'lazy routing'—mounting internal handlers directly to the root router or using broad catch-all patterns without attaching security middleware (Hoops). If an endpoint exists in your binary but isn't explicitly protected and documented, it's a backdoor for data exfiltration.
The Vulnerable Pattern
use salvo::prelude::*;#[handler] async fn get_user() -> &‘static str { “Public Profile Data” }
#[handler] async fn internal_debug_dump() -> &‘static str { “Sensitive System Internals” }
#[tokio::main] async fn main() { // VULNERABILITY: Shadow endpoint ‘internal/debug’ is exposed directly // on the main router without any authentication or documentation. let router = Router::new() .push(Router::with_path(“api/user”).get(get_user)) .push(Router::with_path(“internal/debug”).get(internal_debug_dump));
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await; Server::new(acceptor).serve(router).await;
}
The Secure Implementation
To kill shadow APIs in Salvo, you must enforce a strict routing hierarchy. Avoid mounting handlers flatly on the root router. Instead, group sensitive or internal functionality into sub-routers and apply security Hoops (middleware) at the parent level. This ensures that even if a developer adds a new sub-path, it inherits the 'deny-by-default' security posture of the parent router. Additionally, always use explicit path matching rather than regex catch-alls to prevent unintended endpoint exposure.
use salvo::prelude::*;#[handler] async fn auth_check(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) { if req.headers().get(“authorization”).is_none() { res.render(StatusError::unauthorized()); ctrl.skip_rest(); } }
#[tokio::main] async fn main() { // SECURE: Use nested routers with Hoops (middleware) to ensure no // endpoint is exposed without the required security context. let public_router = Router::with_path(“api/user”).get(get_user);
let internal_router = Router::with_path("internal") .hoop(auth_check) .push(Router::with_path("debug").get(internal_debug_dump)); let router = Router::new() .push(public_router) .push(internal_router); let acceptor = TcpListener::new("127.0.0.1:5800").bind().await; Server::new(acceptor).serve(router).await;
}
Your Salvo API
might be exposed to Shadow API Exposure
74% of Salvo 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.