Api Key Exposure in Axum (Rust)
Api Key Exposure in Axum with Rust — how this specific combination creates or exposes the vulnerability
Axum is a popular Rust web framework that enables developers to build asynchronous HTTP services with strong type safety and composable routing. When API keys are used for authorization, exposure typically occurs not because of Axum itself, but through common patterns in route handling, request extraction, and logging in Rust applications. A key risk arises when API keys are passed in URL query parameters or HTTP headers and then reflected in logs, error messages, or debug output, effectively exposing the key to unintended recipients.
In Axum, extractor-based handlers often deserialize headers or query strings into structured types. If a developer defines a struct to capture an API key and inadvertently logs the struct or returns it as part of a response, the key can be leaked. For example, deriving Debug on a struct that contains a sensitive key and using {:#?} in logging macros can print the key to stdout or a log file. Additionally, Axum’s middleware and routing layers can inadvertently propagate keys if response types are not carefully managed, especially when stack traces or panics include request data.
Another exposure vector specific to Rust and Axum involves serialization formats. When APIs return structured error responses that include incoming request data for debugging, a key passed as a header or query parameter might be included in the error payload. Serde’s serialization behavior, combined with Axum’s error handling utilities, can result in keys being embedded in JSON error responses that are sent to clients. This is particularly risky when APIs are consumed by third-party clients that retain or cache error responses.
The broader security context includes checks such as Authentication, Input Validation, and Data Exposure, which are relevant when scanning an Axum endpoint. For instance, an unauthenticated scan might detect that an API key is transmitted in clear text over HTTP, or that it appears in logs or error payloads. These findings align with common weaknesses described in the OWASP API Top 10, such as excessive data exposure and security misconfiguration. Real-world attack patterns like CVE-2021-41773 (path traversal) illustrate how improper handling of inputs can lead to broader exposures, and similar risks apply when sensitive data like API keys are not isolated from logging and serialization paths.
middleBrick can detect these exposure risks by analyzing the unauthenticated attack surface of an Axum endpoint. Its checks include Data Exposure and Input Validation, which can identify whether API keys appear in responses or logs. The scanner also evaluates Authentication mechanisms and provides prioritized findings with severity levels and remediation guidance. For teams using the CLI, running middlebrick scan <url> can surface these issues quickly. Those on the Pro plan can enable continuous monitoring to detect regressions, while the GitHub Action can fail builds if risk scores drop below a defined threshold, helping prevent key exposure from reaching production.
Rust-Specific Remediation in Axum — concrete code fixes
To prevent API key exposure in Axum applications written in Rust, developers should adopt patterns that avoid reflecting sensitive data in responses, logs, or error payloads. The following code examples illustrate secure approaches for handling API keys, including proper header extraction, error handling, and logging hygiene.
1. Avoid logging sensitive data
Ensure that structs containing API keys do not derive Debug or are not logged with verbose format specifiers. Instead, use a dedicated logging strategy that redacts sensitive values.
use axum::{routing::get, Router};
use std::net::SocketAddr;
use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;
#[derive(Clone)]
struct ApiKey(String);
impl ApiKey {
fn from_request_header(headers: &axum::http::HeaderMap) -> Option {
headers.get("x-api-key").map(|v| {
let key = v.to_str().unwrap_or("").to_string();
info!(key_len = key.len(), "API key received");
ApiKey(key)
})
}
}
#[tokio::main]
async fn main() {
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::INFO)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
let app = Router::new().route("/secure", get(secure_handler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn secure_handler(
axum::headers::authorization::Bearer authorization_header: axum::headers::Authorization<axum::headers::Bearer>,
) -> String {
// Use the key without exposing it in logs or responses
format!("Authorized with key length: {}", authorization_header.token().len())
}
2. Sanitize error responses
Use custom error types that do not include raw API key values in serialized error responses. Axum’s IntoResponse implementation should return generic messages while logging details internally.
use axum::{http::StatusCode, response::IntoResponse};
use serde::Serialize;
#[derive(Serialize)]
struct ErrorResponse {
error: String,
// Do not include fields that may contain API keys
}
impl IntoResponse for ApiKeyError {
fn into_response(self) -> (StatusCode, String) {
let body = ErrorResponse {
error: self.public_message,
};
let json = serde_json::to_string(&body).unwrap();
(StatusCode::UNAUTHORIZED, json)
}
}
enum ApiKeyError {
MissingKey,
InvalidKey { public_message: String },
}
3. Use typed extractors and avoid query parameters
Prefer headers over query parameters for sensitive values, and validate the format before use. Axum extractors can enforce structure without exposing values in URLs.
use axum::async_trait;
use axum::extract::{FromRequest, Request};
use axum::http::request::Parts;
struct SecureApiKey(String);
#[async_trait]
impl axum::extract::FromRequest<S> for SecureApiKey {
type Rejection = (StatusCode, String);
async fn from_request(req: &mut Request, _state: &S) -> Result {
let headers = req.headers();
match headers.get("x-api-key") {
Some(value) if value.to_str().map(|s| !s.is_empty()).unwrap_or(false) => {
Ok(_) => Ok(SecureApiKey(value.to_str().unwrap().to_string())),
Err(_) => Err((StatusCode::BAD_REQUEST, "Invalid header".into())),
},
_ => Err((StatusCode::UNAUTHORIZED, "Missing API key".into())),
}
}
}
These practices reduce the risk of accidental key exposure through logs, serialization, or client-facing error messages. Security checks such as Authentication and Data Exposure in middleBrick can validate these mitigations by confirming that API keys are not reflected in responses or logs. By combining Rust’s type safety with disciplined handling in Axum, developers can minimize the likelihood of sensitive credential leakage.