Data Exposure in Actix with Hmac Signatures
Data Exposure in Actix with Hmac Signatures — how this specific combination creates or exposes the vulnerability
In Actix-based Rust services, using Hmac Signatures for request authentication can inadvertently lead to Data Exposure when implementation details are mishandled. Data Exposure here refers to the risk that sensitive payload content or authentication material is accessible beyond the intended recipient or is retained in logs and error traces. Even though Hmac Signatures provide integrity and authenticity, they do not inherently provide confidentiality; if the application logic leaks the signature, the payload, or the key material, an attacker who gains access to logs, debug output, or transport metadata can correlate requests and replay attacks.
A common pattern in Actix involves computing an Hmac over selected request components (e.g., HTTP method, path, selected headers, and a timestamp) and conveying the signature in a custom header. If the server validates the signature but then proceeds to deserialize and process a JSON body containing sensitive fields (PII, tokens, or secrets) without ensuring those fields are handled securely, the data may be exposed through insecure deserialization, verbose error messages, or structured logging that includes the full request payload. For example, logging the entire HttpRequest or response before filtering sensitive content can inadvertently expose data that the signature was meant to protect.
Moreover, if the server includes the same nonce or timestamp across multiple requests, or if the signature scope is too narrow (e.g., signing only the path while the query parameters contain sensitive filters), an attacker might infer relationships between requests or reconstruct sensitive data from observable side channels. MiddleBrick’s LLM/AI Security checks highlight such risks by detecting System Prompt Leakage patterns and testing for excessive agency where outputs might reveal structured data; similar reasoning applies when API responses expose internal structures or error details tied to authenticated requests.
Additionally, if the application uses an incorrect Hmac variant or a weak key derivation mechanism (e.g., low-entropy keys or hardcoded keys), the integrity guarantee weakens, making it easier for an attacker to produce valid signatures for tampered payloads. Even when the signature validates, the server might inadvertently expose data by returning detailed error responses that include stack traces or variable contents when signature validation fails. In Actix, this often occurs when extractors or guards produce panics or custom error handlers that serialize internal state into HTTP error bodies.
To mitigate Data Exposure in this context, scope the Hmac signature broadly over the parts of the request that must remain confidential and immutable, avoid logging sensitive payloads, and ensure error responses are generic. Combine Hmac with transport-layer encryption and strict data handling policies so that authenticated requests do not become a vector for leaking information. The goal is to ensure that even if an attacker observes authenticated requests, they cannot derive sensitive data from logs or responses.
Hmac Signatures-Specific Remediation in Actix — concrete code fixes
Remediation focuses on correct signature scope, safe error handling, and avoiding data leakage through logs and responses. Below are concrete, syntactically correct examples for Actix Web that demonstrate secure practices.
1. Compute and verify a broad Hmac signature while avoiding logging sensitive data
Sign a canonical string that includes method, path, selected headers, timestamp, and a hash of the body (if present). Use constant-time comparison for signature verification and ensure no sensitive body content is logged.
use actix_web::{web, HttpRequest, HttpResponse, Error};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::time::{SystemTime, UNIX_EPOCH};
// Type alias for Hmac-SHA256
type HmacSha256 = Hmac;
fn compute_hmac(key: &[u8], data: &str) -> Vec {
let mut mac = HmacSha256::new_from_slice(key).expect("HMAC can take key of any size");
mac.update(data.as_bytes());
mac.finalize().into_bytes().to_vec()
}
fn verify_hmac(key: &[u8], data: &str, received_sig: &str) -> bool {
let mut mac = HmacSha256::new_from_slice(key).expect("HMAC can take key of any size");
mac.update(data.as_bytes());
match hex::decode(received_sig) {
Ok(sig_bytes) => mac.verify_slice(&sig_bytes).is_ok(),
Err(_) => false,
}
}
async fn protected_handler(req: HttpRequest, body: web::Json) -> Result {
// Avoid logging the full body; log only metadata
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().to_string();
let method = req.method().to_string();
let path = req.path().to_string();
// Select headers that are part of the signature scope
let relevant_headers = req.headers().get("X-Client-Id").map_or("none", |v| v.to_str().unwrap_or("none"));
// Canonical data to verify: method, path, timestamp, selected headers, and optional body hash
let body_hash = if body.is_empty() {
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // sha256 of empty string
} else {
use sha2::Digest;
let hash = sha2::Sha256::digest(body.as_ref());
format!("{:x}", hash)
};
let data_to_verify = format!("{}|{}|{}|{}|{}", method, path, timestamp, relevant_headers, body_hash);
let signature = req.headers().get("X-Request-Signature")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if verify_hmac(SECRET_KEY.as_ref(), &data_to_verify, signature) {
// Safe: do not include body content in success logs
Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "ok" })))
} else {
// Safe: generic error, avoid exposing internal details
Ok(HttpResponse::Unauthorized().json(serde_json::json!({ "error": "invalid signature" })))
}
}
const SECRET_KEY: &[u8] = b"a-very-secure-random-key-used-for-hmac-should-be-managed-via-env-or-vault";
2. Use Actix extractors to avoid panics and generic error responses
Define a custom extractor that validates the Hmac and returns a generic error type rather than allowing panics to leak internal state.
use actix_web::{dev::Payload, Error, FromRequest, HttpRequest, body::BoxBody};
use actix_web::http::StatusCode;
use actix_web::error::Error as ActixError;
use futures_util::future::{ok, Ready};
struct AuthedRequest {
req: HttpRequest,
body_hash: String,
}
impl FromRequest for AuthedRequest {
type Error = ActixError;
type Future = Ready>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
// Perform Hmac verification; on failure return a generic 401
let timestamp = match req.headers().get("X-Timestamp") {
Some(v) => match v.to_str() { ok => ok, _ => return ok(Err(ActixError::from_response(StatusCode::UNAUTHORIZED, "Invalid timestamp"))) },
None => return ok(Err(ActixError::from_response(StatusCode::UNAUTHORIZED, "Missing timestamp"))),
};
let relevant_headers = req.headers().get("X-Client-Id").map_or("none", |v| v.to_str().unwrap_or("none"));
let body_hash = /* obtain from previously parsed payload or a marker */ "computed_or_provided_hash".to_string();
let data_to_verify = format!("{}|{}|{}|{}", req.method(), req.path(), timestamp, relevant_headers, body_hash);
let signature = match req.headers().get("X-Request-Signature") {
Some(v) => match v.to_str() { ok => ok, _ => return ok(Err(ActixError::from_response(StatusCode::UNAUTHORIZED, "Invalid signature header"))) },
None => return ok(Err(ActixError::from_response(StatusCode::UNAUTHORIZED, "Missing signature"))),
};
if verify_hmac(SECRET_KEY.as_ref(), &data_to_verify, signature) {
ok(Ok(AuthedRequest { req: req.clone(), body_hash }))
} else {
ok(Err(ActixError::from_response(StatusCode::UNAUTHORIZED, "Invalid signature")))
}
}
}
// Usage in a handler:
// async fn handler(authed: AuthedRequest) -> HttpResponse { ... }
3. Avoid key leakage and use environment-managed secrets
Never hardcode keys in source files. Load them via environment variables or a secrets manager and ensure they are not included in logs or crash dumps.
use std::env;
fn secret_key() -> Vec<u8> {
env::var("HMAC_SECRET")
.expect("HMAC_SECRET must be set")
.as_bytes()
.to_vec()
}
4. Ensure safe error handling to prevent data leakage
Make sure error handlers do not serialize internal structs or backtraces. Use simple, constant-time error responses for authentication failures.
use actix_web::HttpResponse;
fn auth_error() -> HttpResponse {
HttpResponse::Unauthorized().json(serde_json::json!({
"error": "authentication failed"
}))
}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 |