Llm Data Leakage in Axum with Mutual Tls
Llm Data Leakage in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
LLM data leakage in an Axum service that uses mutual TLS (mTLS) occurs when an application inadvertently exposes sensitive information—such as system prompts, user data, or API keys—in responses generated by or forwarded to an LLM endpoint. Even with mTLS enforcing strong client authentication between services, the security of the communication channel does not prevent an LLM from returning sensitive content in its output. The risk is especially relevant when Axum handlers pass user-supplied input or internal data to an unauthenticated or poorly scoped LLM endpoint, and that content is then reflected back to users or logged without scrutiny.
With mTLS, both client and server present certificates to establish identity, reducing the risk of impersonation and on-path data interception. However, mTLS does not restrict what an authorized client can request from an LLM, nor does it sanitize the LLM’s response. If an Axum route accepts input that influences the LLM prompt—such as a user query or administrative instruction—and the application does not validate or constrain that input, an attacker may use prompt injection techniques to induce the LLM to reveal training data, internal instructions, or other sensitive information. This is a form of LLM data leakage independent of transport security, and it is detectable by middleBrick’s LLM/AI Security checks, which include system prompt leakage detection across common formats (ChatML, Llama 2, Mistral, Alpaca), active prompt injection probes, and output scanning for PII, API keys, and executable code.
In Axum, a common pattern involves forwarding a user message to an LLM endpoint, optionally along with system instructions that define the bot’s behavior. If these system instructions contain sensitive logic or assumptions, and if the application does not separate them from user-controlled input, an attacker may craft messages that trick the LLM into revealing those instructions. Because mTLS ensures the request reaches a trusted LLM endpoint, the application may incorrectly assume the interaction is fully secure, leading to a false sense of safety. middleBrick’s unauthenticated LLM endpoint detection helps identify such endpoints, while its active prompt injection testing probes for instruction override, DAN jailbreak, and data exfiltration scenarios specific to the Axum service’s integration pattern.
Another vector specific to Axum with mTLS arises when internal or debug information is included in LLM responses and then logged or displayed. For example, an Axum handler might attach metadata—such as request identifiers or feature flags—to the data sent to the LLM, and the model might echo that metadata in its output. If the logging layer does not redact these fields, sensitive operational details can be persisted or exposed. Because mTLS does not prevent an authorized client from requesting such behavior, the onus is on the application to ensure that only safe, sanitized data is sent to the LLM and that responses are scanned before being stored or displayed.
middleBrick’s LLM/AI Security checks are designed to surface these risks by analyzing both specification and runtime behavior. When scanning an Axum API that interacts with LLMs, the tool checks for system prompt leakage using 27 regex patterns tailored to common LLM formats, runs sequential prompt injection probes (system prompt extraction, instruction override, DAN jailbreak, data exfiltration, cost exploitation), and inspects LLM outputs for PII, API keys, and executable code. These capabilities are unique to middleBrick and provide visibility into leakage paths that standard API scanners, which focus on transport and schema validation, may miss.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
Remediation for LLM data leakage in Axum with mTLS centers on input validation, output sanitization, and strict separation of system instructions from user-controlled data. Transport-layer protections such as mTLS should be complemented with application-level controls that prevent sensitive information from reaching the LLM and ensure that LLM responses are inspected before use.
First, ensure that any data forwarded to the LLM is explicitly limited to what is necessary. In Axum, this can be achieved by defining dedicated request and response models that exclude internal fields and by validating input against strict schemas before constructing the LLM prompt. For example, using serde and validator crates helps enforce constraints on user input, reducing the risk that malicious payloads influence the LLM in unintended ways.
Second, protect system instructions by keeping them outside user-influenced paths. Instead of embedding sensitive prompts in requests that may include user data, store them as environment variables or configuration that the Axum handler references independently. This separation ensures that user input cannot directly alter the system instructions, mitigating prompt injection risks that lead to data leakage.
Third, inspect LLM responses before they are logged or returned to clients. Implement a response filter that removes or masks sensitive patterns such as API keys, email addresses, and internal identifiers. Even with mTLS in place, this step prevents sensitive data from persisting in logs or being exposed to downstream consumers.
Below are concrete Axum code examples that demonstrate mTLS setup alongside secure handling of LLM interactions.
Mutual TLS configuration in Axum
use axum::Router;
use std::net::SocketAddr;
use tokio_rustls::rustls::{ServerConfig, NoClientAuth, RootCertStore, Certificate, PrivateKey};
use tokio_rustls::TlsAcceptor;
use std::sync::Arc;
async fn configure_mtls() -> Arc<TlsAcceptor> {
// Load server certificate and private key
let cert_chain = vec![load_certificate("server.crt")];
let private_key = load_private_key("server.key");
// Configure trusted client CA certificates
let mut client_root_store = RootCertStore::empty();
client_root_store.add(&load_certificate("client-ca.crt")).expect("invalid client CA");
let mut server_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() // Will be replaced with client CA verification
.with_single_cert(cert_chain, private_key)
.expect("invalid server certificate");
server_config.client_root_certificates = Arc::new(client_root_store);
server_config.verify_client_cert = Some(Arc::new(|certs| {
// Custom verification logic can be applied here
!certs.is_empty()
}));
Arc::new(TlsAcceptor::from(Arc::new(server_config)))
}
fn load_certificate(path: &str) -> Certificate {
// Implementation to read PEM/DER certificate
unimplemented!()
}
fn load_private_key(path: &str) -> PrivateKey {
// Implementation to read PEM/DER private key
unimplemented!()
}
Axum handler with separated system instructions and input validation
use axum::{routing::post, Router, Json};
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Debug, Deserialize, Validate)]
struct LlmRequest {
#[validate(length(min = 1, message = "query is required"))]
query: String,
// Do not include internal fields such as session_token or debug flags
}
#[derive(Debug, Serialize)]
struct LlmResponse {
answer: String,
}
async fn handle_llm(
Json(payload): Json,
) -> Result<Json<LlmResponse>, axum::http::StatusCode> {
payload.validate().map_err(|_| axum::http::StatusCode::BAD_REQUEST)?;
// Retrieve system instructions from secure configuration, not from user input
let system_instructions = std::env::var("LLM_SYSTEM_INSTRUCTIONS")
.unwrap_or_else(|_| "You are a helpful assistant.".to_string());
// Construct prompt without injecting user data into instructions
let user_prompt = sanitize_input(&payload.query);
let full_prompt = format!("{}\nUser: {}\nAssistant:", system_instructions, user_prompt);
// Call LLM endpoint (example using a hypothetical client)
let llm_response = call_llm_endpoint(&full_prompt).await;
// Filter sensitive content from LLM output before use
let safe_response = filter_sensitive_content(&llm_response);
Ok(Json(LlmResponse { answer: safe_response }))
}
fn sanitize_input(input: &str) -> String {
// Implement trimming, length limits, and disallowed pattern checks
input.trim().to_string()
}
async fn call_llm_endpoint(prompt: &str) -> String {
// Implementation to call external LLM
"LLM output here".to_string()
}
fn filter_sensitive_content(response: &str) -> String {
// Remove or mask API keys, emails, and internal identifiers
response.to_string()
}
By combining mTLS for client authentication with these application-level practices, Axum services can reduce the likelihood of LLM data leakage while maintaining secure and verifiable transport.
Related CWEs: llmSecurity
| CWE ID | Name | Severity |
|---|---|---|
| CWE-754 | Improper Check for Unusual or Exceptional Conditions | MEDIUM |