Llm Data Leakage in Axum with Api Keys
Llm Data Leakage in Axum with Api Keys — how this specific combination creates or exposes the vulnerability
When integrating Large Language Model (LLM) endpoints into an Axum-based Rust service, developers often pass sensitive configuration such as API keys through request headers, environment variables, or structured input. If these keys are exposed in LLM responses—whether via verbose error messages, debug output, or generative completions—an LLM data leakage occurs. middleBrick detects system prompt leakage patterns across 27 regex templates that cover ChatML, Llama 2, Mistral, and Alpaca formats, which helps identify when API keys or other secrets inadvertently surface in LLM outputs.
In Axum, handlers that forward user input to LLM providers without strict output sanitization can propagate keys embedded in training data or injected via adversarial prompts. For example, if a handler uses a completion endpoint and the LLM echoes back a key included in the system prompt or retrieved from an injected document, the key is exposed. middleBrick’s active prompt injection testing runs five sequential probes, including system prompt extraction and data exfiltration, to verify whether an LLM endpoint leaks sensitive strings like API keys. The scanner also checks for excessive agency patterns, such as tool_calls or function_call usage, that may enable an LLM to retrieve or repeat sensitive values.
Because Axum applications often deserialize JSON or form data into strongly typed structures, keys may be logged, echoed, or returned in error payloads. An unauthenticated LLM endpoint exposed within an Axum route can allow an attacker to trigger responses that contain keys stored in system prompts or retrieved through injected context. middleBrick’s output scanning checks LLM responses for API keys, PII, and executable code, providing findings with severity and remediation guidance. By correlating OpenAPI/Swagger specifications with runtime behavior, the scanner identifies mismatches between documented authentication and actual exposure risks in LLM interactions.
Api Keys-Specific Remediation in Axum — concrete code fixes
To prevent LLM data leakage of API keys in Axum, ensure keys never travel into LLM prompts or responses. Use environment variables to load keys on the server side and pass only necessary, non-sensitive data to the LLM. Validate and sanitize all outputs from LLM calls before returning them to clients. The following examples illustrate secure patterns for handling API keys in Axum routes.
First, load API keys from environment variables and keep them out of request handling logic that interacts with LLMs:
use axum::{routing::get, Router};
use std::net::SocketAddr;
use std::env;
async fn handler() -> String {
// Load key from environment, do not forward to LLM
let api_key = env::var("EXTERNAL_API_KEY").expect("API key must be set");
// Use key only for server-side authorization, not in LLM context
format!("Key loaded successfully")
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/health", get(handler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Second, when calling an LLM endpoint, construct a request that excludes sensitive values from the payload and headers. Use a dedicated client with timeout and retry policies, and ensure error responses do not echo back keys:
use axum::{routing::post, Json, Router};
use serde::{Deserialize, Serialize};
use reqwest::Client;
use std::env;
#[derive(Deserialize)]
struct CompletionRequest {
user_input: String,
}
#[derive(Serialize)]
struct CompletionPayload<'a> {
prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
system: Option<&'a str>,
}
async fn llm_handler(
Json(payload): Json,
) -> Result, (axum::http::StatusCode, String)> {
let api_key = env::var("LLM_API_KEY").map_err(|_| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Missing key".into()))?;
let client = Client::new();
let completion = CompletionPayload {
prompt: payload.user_input,
system: None, // Do not include sensitive context
};
let response = client
.post("https://api.example.com/v1/completions")
.bearer_auth(api_key)
.json(&completion)
.send()
.await
.map_err(|e| (axum::http::StatusCode::BAD_GATEWAY, e.to_string()))?;
let json = response.json::().await.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
// Ensure no key is present in the response before returning
if json.to_string().contains(&api_key) {
return Err((axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Potential key leakage".into()));
}
Ok(Json(json))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/chat", post(llm_handler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
These patterns ensure API keys remain server-side and are not included in LLM prompts or returned to the client. middleBrick’s scans can validate these protections by checking for insecure handling of keys in both spec definitions and runtime outputs.
Related CWEs: llmSecurity
| CWE ID | Name | Severity |
|---|---|---|
| CWE-754 | Improper Check for Unusual or Exceptional Conditions | MEDIUM |