Pii Leakage in Axum with Api Keys
Pii Leakage in Axum with Api Keys — how this specific combination creates or exposes the vulnerability
Pii Leakage in an Axum service often occurs when API keys are handled in an unauthenticated or improperly constrained endpoint. Axum, a Rust web framework, does not enforce authentication by default; if route handlers accept an API key as a query parameter or header and then return resources that include personally identifiable information, the combination exposes PII to any unauthenticated caller.
Consider an endpoint that retrieves user profile details. If the handler validates the presence of an API key but does not enforce scope-based or ownership checks, an attacker can enumerate user IDs and harvest emails, phone numbers, or addresses. This is a BOLA/IDOR pattern enabled by weak authorization tied to API keys rather than a per-user identity. middleBrick’s unauthenticated scan flags this as Data Exposure with severity High, noting that API keys alone are insufficient to mediate access to PII.
Another scenario involves logging or error messages. If an Axum handler echoes the API key in logs or responses, and those logs or error payloads contain PII, the key and the sensitive data are co-located in outputs that may be exposed through monitoring or crash reports. This aligns with findings categorized under Unsafe Consumption, where structured output is not sanitized before being returned to the client. The scan also inspects OpenAPI specs for $ref definitions and runtime behavior; if the spec describes a 200 response containing fields like email or phone without tying them to authenticated user context, middleBrick correlates this with runtime observations to highlight PII leakage risks.
LLM/AI Security checks add another dimension: if an Axum endpoint is also an LLM inference endpoint, leaking API keys in prompts or model outputs can lead to prompt injection or cost exploitation. middleBrick’s active prompt injection probes and output scanning for PII, API keys, and executable code help identify whether API keys appear in LLM responses or system prompts, which would constitute a critical exposure.
Api Keys-Specific Remediation in Axum — concrete code fixes
Remediation centers on ensuring API keys are treated as authorization tokens, not identity, and that PII is only returned when the key maps to the correct subject. Use middleware to validate the key against a permissions store and bind it to a user identifier before allowing access to sensitive fields.
Example 1: Scoped API key validation that restricts data to the owning user.
use axum::{routing::get, Router, extract::State, http::HeaderMap};
use std::sync::Arc;
use serde::Serialize;
#[derive(Clone)]
struct AppState {
key_store: Arc<KeyStore>,
}
#[derive(Serialize)]
struct UserProfile {
user_id: u64,
email: String,
// Do not include PII unless key scope matches
}
async fn get_profile(
State(state): State<Arc<AppState>>,
headers: HeaderMap,
) -> Result<String, (axum::http::StatusCode, String)> {
let api_key = headers.get("X-API-Key")
.ok_or((axum::http::StatusCode::UNAUTHORIZED, "Missing API key"))?
.to_str()
.map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "Invalid header"))?;
let key_record = state.key_store.lookup(api_key)
.await
.ok_or((axum::http::StatusCode::FORBIDDEN, "Invalid API key"))?;
// Enforce scope: only allow access if the key is scoped to this user
if key_record.user_id != key_record.allowed_user_id {
return Err((axum::http::StatusCode::FORBIDDEN, "Insufficient scope").into());
}
let profile = UserProfile {
user_id: key_record.user_id,
email: key_record.email.clone(),
};
let body = serde_json::to_string(&profile).unwrap();
Ok(body)
}
#[tokio::main]
async fn main() {
let state = Arc::new(AppState {
key_store: Arc::new(KeyStore::new()),
});
let app = Router::new()
.route("/profile", get(get_profile))
.with_state(state);
axum::Server::bind("0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
Example 2: Centralized middleware that validates API keys and attaches user claims, preventing PII leakage by ensuring downstream handlers only receive authorized data.
use axum::{routing::get, Router, Extension, http::HeaderMap};
use std::sync::Arc;
use tower_http::ServiceBuilderExt;
struct KeyValidator;
impl KeyValidator {
async fn validate(headers: &HeaderMap) -> Result<Claims, (axum::http::StatusCode, String)> {
let key = headers.get("X-API-Key")
.and_then(|v| v.to_str().ok())
.ok_or((axum::http::StatusCode::UNAUTHORIZED, "Invalid key header"))?;
// Replace with actual lookup; ensure claims do not include raw PII unless necessary
if key == "valid_scoped_key" {
Ok(Claims { user_id: 1, scope: "profile:read" })
} else {
Err((axum::http::StatusCode::FORBIDDEN, "Invalid scope").into())
}
}
}
async fn get_user(
Extension(claims): Extension<Claims>,
) -> String {
// Return only non-PII or PII explicitly allowed by scope
format!({{"user_id": {}}} , claims.user_id)
}
// In router setup:
// let app = Router::new()
// .route("/user", get(get_user.layer(Extension(key_validator_middleware))))
// .layer(Extension(key_validator));
These examples demonstrate how to bind API key validation to user identity and scope, ensuring PII is only surfaced when authorization matches. middleBrick’s CLI can be used to verify these changes: scan from terminal with middlebrick scan
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |