Api Key Exposure in Actix
How Api Key Exposure Manifests in Actix
Api Key Exposure in Actix applications typically occurs through several common patterns. The most frequent is logging sensitive data to console or log files. Actix developers often inadvertently log entire request objects or structured data that includes API keys, authentication headers, or query parameters. For example:
async fn handle_request(req: HttpRequest) -> impl Responder {
// BAD: logs entire request including Authorization header
log::info!("处理请求: {:?}", req);
// BAD: logs query params that might contain API keys
let params = req.query_string();
log::info!("Query parameters: {}", params);
HttpResponse::Ok().finish()
}
Another common manifestation is exposing API keys in error responses. When Actix applications encounter errors, they might include request context in error messages:
async fn sensitive_operation(req: HttpRequest) -> impl Responder {
let auth_header = req.headers().get("Authorization");
// BAD: error response includes auth header
if auth_header.is_none() {
return HttpResponse::BadRequest()
.body(format!("Missing Authorization header: {:?}", auth_header));
}
HttpResponse::Ok().finish()
}
API keys can also leak through Actix's built-in error handling when using extractors. If an extractor fails, Actix may include the raw request data in the error response:
async fn process_data(
json: Result<serde_json::Value, actix_web::Error>
) -> impl Responder {
// BAD: error includes raw JSON payload that might contain API keys
let data = match json {
Ok(d) => d,
Err(e) => return HttpResponse::BadRequest().body(format!("Invalid JSON: {}", e)),
};
HttpResponse::Ok().finish()
}
Middleware that logs request/response bodies without filtering sensitive headers is another attack vector:
struct LoggingMiddleware;
impl actix_web::dev::Transform for LoggingMiddleware {
type RequestBody = actix_web::dev::Payload;
type ResponseBody = actix_web::dev::Body;
type Error = actix_web::Error;
type InitError = ();
type Transform = LoggingMiddleware;
type Future = std::future::Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: &mut actix_web::dev::ServiceFactory)
-> Self::Future
{
std::future::ready(Ok(LoggingMiddleware))
}
}
impl<S, B> actix_web::dev::Service<actix_web::dev::ServiceRequest>
for LoggingMiddleware
where
S: actix_web::dev::Service<
actix_web::dev::ServiceRequest,
Response = actix_web::dev::ServiceResponse<B>,
>,
S::Error: Into<actix_web::Error>,
B: actix_web::dev::MessageBody,
{
type Response = actix_web::dev::ServiceResponse<actix_web::dev::Body>;
type Error = actix_web::Error;
type Future = actix_web::dev::ServiceResponse<actix_web::dev::Body>;
fn poll_ready(
&mut self,
_cx: &mut std::task::Context,
) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn call(&mut self, mut req: actix_web::dev::ServiceRequest)
-> Self::Future
{
// BAD: logs entire request including sensitive headers
log::info!("请求: {:?}", req);
let fut = req.service_call();
fut
}
}
Actix-Specific Detection
Detecting API key exposure in Actix applications requires examining both the source code and runtime behavior. Static analysis should focus on these Actix-specific patterns:
Log Statement Analysis Look for log statements that include entire request objects or structured data:
# Search for dangerous log patterns
rg "log::[a-z]*\(.*\?req" src/ --type rust
rg "log::[a-z]*\(.*\?headers" src/ --type rust
rg "log::[a-z]*\(.*\?query_string" src/ --type rust
Middleware Inspection Examine custom middleware for logging sensitive data:
# Find middleware implementations
rg "impl.*Transform.*for" src/ --type rust
rg "impl.*Service.*for" src/ --type rust
Extractor Usage Patterns Check how extractors handle errors:
# Find error handling in extractor functions
rg "match.*Result" src/ --type rust
rg "Err.*=>" src/ --type rust
Runtime Detection with middleBrick middleBrick's black-box scanning approach is particularly effective for Actix applications because it tests the actual running API without requiring source code access:
CLI Usage
# Scan an Actix API endpoint
middlebrick scan https://api.example.com/v1/users
# Scan with JSON output for CI integration
middlebrick scan https://api.example.com/v1/users --output json
GitHub Action Integration
name: API Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run middleBrick Scan
run: |
npm install -g middlebrick
middlebrick scan ${{ secrets.API_URL }} --threshold B
continue-on-error: true
MCP Server Integration For Actix developers using AI coding assistants:
# Install MCP server
npm install -g @middlebrick/mcp-server
# Configure Claude/Cursor to use middleBrick
# Then scan APIs directly from your IDE
Actix-Specific Remediation
Remediating API key exposure in Actix applications requires both code changes and architectural patterns. Here are Actix-specific solutions:
Safe Logging with Request Sanitization
use actix_web::{dev::ServiceRequest, http::HeaderValue};
fn sanitize_request(req: &ServiceRequest) -> String {
// Create a sanitized version of the request
let mut sanitized = format!("{} {}", req.method(), req.uri());
// Remove sensitive headers
let mut headers = req.headers().clone();
let sensitive_headers = ["authorization", "x-api-key", "apikey"];
for header in sensitive_headers.iter() {
headers.remove(*header);
}
if !headers.is_empty() {
sanitized.push_str("
Headers: ");
for (key, value) in headers.iter() {
sanitized.push_str(&format!("{}: {}, ", key.as_str(), value.to_string()));
}
}
sanitized
}
// Use in middleware
impl<S, B> actix_web::dev::Service<ServiceRequest> for LoggingMiddleware
where
S: actix_web::dev::Service<ServiceRequest, Response = ServiceResponse<B>>,
{
fn call(&mut self, req: ServiceRequest) -> Self::Future {
log::info!("处理请求: {}", sanitize_request(&req));
// ... rest of middleware
}
}
Safe Error Handling with Extractors
use actix_web::{error, http::StatusCode, HttpResponse};
use serde::Deserialize;
#[derive(Deserialize)]
struct UserData {
username: String,
email: String,
}
async fn process_user_data(
json: actix_web::web::Json<UserData>,
) -> Result<HttpResponse, actix_web::Error> {
// Use Result-based error handling instead of direct error responses
Ok(HttpResponse::Ok().finish())
}
// Global error handler that doesn't expose sensitive data
#[actix_web::main]
async fn main() -> std::io::Result<()> {
actix_web::HttpServer::new(|| {
App::new()
.wrap(actix_web::middleware::NormalizePath::default())
.wrap(error::ErrorHandlers::new())
.service(process_user_data)
})
.bind("127.0.0.1:8080")?.run().await
}
// Custom error handler implementation
impl error::ResponseError for MyCustomError {
fn error_response(&self) -> HttpResponse {
// Always return generic error message
HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
.content_type("text/plain")
.body("An internal error occurred")
}
}
Request Filtering Middleware
use actix_web::{dev::ServiceRequest, http::HeaderValue};
use std::collections::HashSet;
struct ApiKeyFilter;
impl actix_web::dev::Transform for ApiKeyFilter {
type RequestBody = actix_web::dev::Payload;
type ResponseBody = actix_web::dev::Body;
type Error = actix_web::Error;
type InitError = ();
type Transform = ApiKeyFilter;
type Future = std::future::Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, _service: &mut actix_web::dev::ServiceFactory)
-> Self::Future
{
std::future::ready(Ok(ApiKeyFilter))
}
}
impl<S, B> actix_web::dev::Service<ServiceRequest> for ApiKeyFilter
where
S: actix_web::dev::Service<
ServiceRequest,
Response = actix_web::dev::ServiceResponse<B>,
>,
S::Error: Into<actix_web::Error>,
B: actix_web::dev::MessageBody,
{
type Response = actix_web::dev::ServiceResponse<actix_web::dev::Body>;
type Error = actix_web::Error;
type Future = actix_web::dev::ServiceResponse<actix_web::dev::Body>;
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
// Remove sensitive headers before processing
let sensitive_headers = ["authorization", "x-api-key", "apikey"];
for header in sensitive_headers.iter() {
req.headers_mut().remove(*header);
}
req.service_call()
}
}