Cache Poisoning in Axum with Api Keys
Cache Poisoning in Axum with Api Keys — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker manipulates cached responses so that subsequent users receive malicious or incorrect data. In Axum, this risk can emerge when API keys are handled in ways that cause responses to be cached per-key or when keys are treated as part of the cache key without proper normalization.
Consider an endpoint that includes the API key in a header, such as x-api-key, and uses it to personalize responses. If the caching layer (e.g., a reverse proxy or in-memory cache) uses the full request, including headers, as the cache key, responses for different keys may be incorrectly reused. For example, a user with a privileged key might receive data intended for a low-privilege key because a cached response for the privileged key is served to a less-privileged user sharing the same backend path.
Another scenario involves query parameters that include keys or key-derived values. An endpoint like /data?key=USER_KEY may inadvertently cache a response keyed to the literal query string, which includes the API key. If the same endpoint is called with a different key but the same logical request, a cached response from a prior keyed request could be returned, exposing one user’s data to another. This is a form of broken access control that intersects with BOLA/IDOR patterns and can be surfaced by the 12 security checks, including Property Authorization and Input Validation.
Additionally, if an Axum service uses shared caching for authenticated-like behavior via keys (instead of session tokens), and the cache does not differentiate by authorization context, sensitive data may be cached in what should be a public or low-privilege context. The scanner’s Authentication and BOLA/IDOR checks are designed to detect cases where endpoints expose data across users, and the Data Exposure check can highlight responses that should not be cacheable.
To detect these risks, middleBrick runs 12 security checks in parallel, including tests that analyze how responses vary with different keys and whether caching headers encourage storage of sensitive or user-specific content. Findings include severity levels and remediation guidance mapped to frameworks such as OWASP API Top 10 and PCI-DSS.
Api Keys-Specific Remediation in Axum — concrete code fixes
Remediation focuses on ensuring that caching behavior is independent of API key values and that keys are never used to derive cache keys directly. Below are concrete Axum patterns that reduce cache poisoning risk when using API keys.
1. Exclude API keys from cache keys. Do not include raw headers like x-api-key in cache key construction. Instead, normalize requests before caching.
use axum::{routing::get, Router};
use std::net::SocketAddr;
use axum::extract::Request;
use axum::response::Response;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
async fn normalize_request_for_cache(req: &Request, body: &B) -> u64
where
B: Hash + ?Sized,
{
let mut hasher = DefaultHasher::new();
// Hash only the method and path, excluding headers like x-api-key
req.method().hash(&mut hasher);
req.uri().path().hash(&mut hasher);
body.hash(&mut hasher);
hasher.finish()
}
async fn handler() -> &'static str {
"public data"
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/data", get(handler));
// In production, place a caching layer (e.g., reverse proxy) in front of Axum
// and configure it to ignore x-api-key when forming cache keys.
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
2. Use Vary headers appropriately. If you must cache responses that differ by authorization context, ensure Vary does not rely on the API key value itself but on the presence or role derived from it, and avoid caching sensitive responses.
// In your handler or middleware, set Vary to a non-identifying attribute
use axum::headers::authorization::Bearer;
use axum::http::HeaderValue;
async fn handler_with_vary() -> (axum::http::StatusCode, axum::headers::Vary, &'static str) {
// Do not include the key itself in Vary; instead use a role claim if available
(axum::http::StatusCode::OK, axum::headers::Vary::new(&["authorization"]), "role_based_data")
}
3. Avoid query parameters that echo API keys. Use path-based routing or authenticated sessions rather than key-in-query patterns that leak keys into logs and caches.
// Instead of: GET /data?api_key=SECRET
// Use: GET /data with Authorization: Bearer or x-api-key header handled server-side
// and not reflected in URLs.
These patterns align with the scanner’s checks for Input Validation and Rate Limiting, and they support secure configurations that minimize unintended data exposure. middleBrick’s GitHub Action can be added to CI/CD pipelines to fail builds if risk scores drop below your chosen threshold, while the CLI allows quick local scans with middlebrick scan <url>.