Information Disclosure in Axum
How Information Disclosure Manifests in Axum
Information disclosure in Axum applications occurs when sensitive data is unintentionally exposed through API responses, error messages, or debug information. Unlike traditional web frameworks, Axum's async/await architecture and extractors create unique disclosure vectors that developers must guard against.
One common pattern involves improper error handling in extractors. When an extractor fails to parse a request parameter, Axum's default behavior may expose stack traces or internal implementation details:
use axum::extract::{Query, Path};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
async fn user_profile(Query(params): Query<HashMap<String, String>>) -> Response {
// If params parsing fails, Axum may expose internal details
let user_id = params.get("id").unwrap_or_else(|| "unknown");
// Sensitive data exposure through debug info
let user = find_user_by_id(user_id).await;
if user.is_none() {
return (StatusCode::NOT_FOUND, format!("User {} not found", user_id)).into_response();
}
// Exposing internal database IDs or implementation details
(StatusCode::OK, user).into_response()
}
Another Axum-specific disclosure vector involves the tower::BoxError handling in middleware chains. When errors bubble up through the middleware stack without proper sanitization, they can reveal service architecture:
use tower_http::trace::TraceLayer;
use axum::routing::get;
use axum::Router;
let app = Router::new()
.route("/api/users/:id", get(user_profile))
.layer(
TraceLayer::new_for_http()
// Default trace layer may expose internal details in logs
.on_request_fail(|error, _request| {
// Avoid logging sensitive error details
tracing::error!("Request failed: {}", error);
})
);
Extractors that wrap sensitive operations without proper error boundaries create additional disclosure risks. For example, a JSON extractor that fails on malformed input might expose schema details:
use axum::Json;
use serde::Deserialize;
#[derive(Deserialize)]
struct UserUpdate {
email: String,
// Sensitive field that shouldn't be exposed
internal_notes: String,
}
async fn update_user(Json(payload): Json<UserUpdate>) -> impl IntoResponse {
// If deserialization fails, Axum may expose schema structure
// in error messages, revealing internal field names
let user = update_user_in_db(payload.email, payload.internal_notes).await;
(StatusCode::OK, user).into_response()
}
Axum-Specific Detection
Detecting information disclosure in Axum requires examining both the routing structure and the error handling patterns. The framework's modular design means vulnerabilities can hide in extractors, middleware, or even the routing configuration itself.
middleBrick's scanner specifically targets Axum's async/await patterns and extractor chains. It identifies several Axum-specific disclosure patterns:
- Extractor error exposure - When Query, Path, or Json extractors fail, they may expose internal validation logic or schema details
- Middleware error bubbling - Tower middleware errors that propagate without sanitization
- Debug endpoint exposure - Development-only endpoints accidentally deployed to production
- Configuration leakage - Server configuration details exposed through error responses
Using middleBrick to scan an Axum API endpoint:
npx middlebrick scan https://api.example.com/users/123
The scanner tests unauthenticated endpoints for information disclosure by:
- Sending malformed requests to trigger extractor failures
- Testing error endpoints with invalid parameters
- Checking for debug information in 4xx/5xx responses
- Analyzing response headers for sensitive server details
For Axum applications, middleBrick specifically examines:
// What middleBrick looks for in Axum code
- Extractors that don't handle None cases properly
- Error responses that include stack traces or internal details
- Debug features enabled in production
- Sensitive data in error messages or response bodies
- Server version headers and framework details
The scanner's LLM security checks are particularly relevant for Axum applications using AI features, as they test for system prompt leakage and prompt injection vulnerabilities that could expose sensitive configuration or training data.
Axum-Specific Remediation
Remediating information disclosure in Axum requires leveraging the framework's native error handling and response patterns. The key is implementing proper error boundaries and sanitizing all error responses before they reach clients.
Proper error handling with Axum's Result type:
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde_json::json;
// Custom error type for sanitized responses
#[derive(Debug)]
enum ApiError {
NotFound,
ValidationError(String),
InternalError,
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status, json) = match self {
ApiError::NotFound => (
StatusCode::NOT_FOUND,
json!({ "error": "Resource not found" }),
),
ApiError::ValidationError(msg) => (
StatusCode::BAD_REQUEST,
json!({ "error": msg }),
),
ApiError::InternalError => (
StatusCode::INTERNAL_SERVER_ERROR,
json!({ "error": "Internal server error" }),
),
};
(status, Json(json)).into_response()
}
}
async fn user_profile(
Path(user_id): Path<String>,
) -> Result<Json<User>, ApiError> {
let user = find_user_by_id(&user_id).await
.ok_or(ApiError::NotFound)?;
// Sanitize sensitive fields before response
let sanitized = sanitize_user_response(&user);
Ok(Json(sanitized))
}
Middleware for consistent error handling across all routes:
use tower_http::set_header::SetResponseHeaderLayer;
use axum::middleware::from_fn;
// Error sanitization middleware
let sanitize_middleware = from_fn(|mut req: axum::http::Request<body>, next: axum::middleware::Next<body>| async move {
match next.run(req).await {
Ok(response) => Ok(response),
Err(err) => {
// Convert internal errors to generic responses
let sanitized = (StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": "An unexpected error occurred" })))
.into_response();
Ok(sanitized)
}
}
});
let app = Router::new()
.route("/api/users/:id", get(user_profile))
.layer(sanitize_middleware);
Production-ready configuration to prevent disclosure:
use axum::http::StatusCode;
use axum::response::IntoResponse;
use tower_http::trace::DefaultMakeSpan;
use tower_http::trace::TraceLayer;
let app = Router::new()
.route(...)
// Disable detailed tracing in production
.layer(TraceLayer::new_for_http()
.make_span_with(DefaultMakeSpan::new_for_http()
.include_headers(false)
.include_body(false))
.on_request_fail(|error, _req| {
// Log minimal details
tracing::error!("Request failed");
}))
// Remove server headers
.layer(SetResponseHeaderLayer::overriding("server", ""))
.layer(SetResponseHeaderLayer::overriding("x-powered-by", ""));