Excessive Data Exposure in Actix
How Excessive Data Exposure Manifests in Actix
Excessive Data Exposure in Actix applications typically occurs through several Actix-specific patterns. The most common scenario involves returning entire database models or structs directly from handlers without filtering sensitive fields. For example, when using Actix's derive macros with serde, developers often return entire structs like this:
#[derive(Serialize)]
struct User {
id: i32,
email: String,
password_hash: String, // Sensitive!
ssn: String, // Sensitive!
credit_card: String, // Sensitive!
}
async fn get_user(id: web::Path) -> impl Responder {
let user = User::find_by_id(id).await.unwrap();
HttpResponse::Ok().json(user) // Returns ALL fields!
}
This pattern is particularly dangerous in Actix because the framework's derive macros make it trivial to expose entire structs. Another Actix-specific manifestation occurs with the web::Json extractor, which automatically serializes entire structs:
async fn create_user(user: web::Json) -> impl Responder {
// Entire User struct is deserialized, including sensitive fields
// Then potentially returned or logged
HttpResponse::Created().json(user.0)
}
Actix's middleware system can also contribute to data exposure. Custom middleware that logs request bodies or responses might inadvertently capture sensitive data. For instance:
async fn log_middleware(
req: actix_web::dev::ServiceRequest,
srv: &mut actix_web::dev::Service,
) -> Result {
let response = srv.call(req).await?;
// This logs the entire response body, including sensitive data
if let Ok(body) = response.clone().into_body().try_into_bytes() {
log::info!("Response: {}", String::from_utf8_lossy(&body));
}
Ok(response)
}
Actix's query parameter handling can also lead to exposure. When using web::Query to deserialize query parameters into structs, all fields become accessible, potentially exposing internal implementation details:
#[derive(Deserialize)]
struct SearchParams {
query: String,
user_id: i32, // Internal ID exposed to client
include_sensitive: bool, // Dangerous flag
}
async fn search(params: web::Query<SearchParams>) -> impl Responder {
// Client can manipulate internal fields
if params.include_sensitive {
// Returns sensitive data based on client-controlled flag
}
}
Actix-Specific Detection
Detecting Excessive Data Exposure in Actix applications requires examining both the code structure and runtime behavior. In Actix applications, you can identify this issue by looking for patterns where entire structs are returned without field filtering. Using middleBrick's CLI, you can scan your Actix endpoints:
npx middlebrick scan https://api.example.com/actix-endpoint
middleBrick's scanner will identify endpoints that return complete structs and flag potential data exposure. The scanner specifically looks for Actix's derive macro patterns and web::Json usage that might expose sensitive fields.
Manual code review in Actix applications should focus on these patterns:
- Search for
#[derive(Serialize)]on structs that contain sensitive fields - Look for direct
json()returns of entire structs - Examine middleware that logs or processes request/response bodies
- Check for
web::Querydeserialization into structs with internal fields
For runtime detection, Actix's logging can help identify what data is being transmitted. Configure detailed logging to see response bodies:
use actix_web::middleware::Logger;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.service(get_user)
})
.bind("127.0.0.1:8080")?.run().await
}
This logging will show you exactly what data your Actix endpoints are returning, making it easier to spot excessive exposure. For automated scanning in CI/CD, use middleBrick's GitHub Action:
name: API Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick Scan
uses: middlebrick/middlebrick-action@v1
with:
url: ${{ secrets.API_URL }}
fail-on-severity: high
Actix-Specific Remediation
Remediating Excessive Data Exposure in Actix requires a multi-layered approach. The most effective strategy is to create dedicated response DTOs (Data Transfer Objects) that expose only the necessary fields:
// Original struct with sensitive fields
#[derive(Deserialize, Serialize)]
struct User {
id: i32,
email: String,
password_hash: String,
ssn: String,
credit_card: String,
}
// Safe response DTO
#[derive(Serialize)]
struct UserResponse {
id: i32,
email: String,
}
async fn get_user(id: web::Path<i32>) -> impl Responder {
let user = User::find_by_id(id).await.unwrap();
let response = UserResponse {
id: user.id,
email: user.email.clone(),
};
HttpResponse::Ok().json(response)
}
For Actix applications using async-std or tokio, you can leverage async mapping to transform data before returning:
async fn get_users() -> impl Responder {
let users = User::find_all().await.unwrap();
// Transform to safe DTOs
let safe_users: Vec<UserResponse> = users
.into_iter()
.map(|u| UserResponse {
id: u.id,
email: u.email.clone(),
})
.collect();
HttpResponse::Ok().json(safe_users)
}
Actix's middleware system can help enforce data exposure policies. Create a middleware that automatically filters sensitive fields:
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use futures_util::future::{ready, Ready};
use serde_json::Value;
struct DataExposureFilter;
impl<S, B> Transform<S> for DataExposureFilter
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
B: actix_web::dev::MessageBody,
{
type Request = ServiceRequest;
type Response = ServiceResponse<Body>;
type Error = S::Error;
type InitError = ();
type Transform = DataExposureMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(DataExposureMiddleware { service }))
}
}
struct DataExposureMiddleware<S> {
service: S,
}
impl<S, B> Service for DataExposureMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
B: actix_web::dev::MessageBody,
{
type Request = ServiceRequest;
type Response = ServiceResponse<Body>;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let service_future = self.service.call(req);
Box::pin(async move {
let response = service_future.await?;
// Check if response is JSON and filter sensitive fields
if let Ok(body) = response.clone().into_body().try_into_bytes() {
if let Ok(json) = serde_json::from_slice::(&body) {
let filtered = filter_sensitive_data(json);
let new_body = Body::from(filtered.to_string());
return Ok(response.map_body(|_, _| new_body));
}
}
Ok(response)
})
}
}
fn filter_sensitive_data(json: Value) -> Value {
// Implement field filtering logic here
json
}
For Actix applications using SQLx or other database libraries, ensure you only select the fields you need:
async fn get_user(id: web::Path<i32>) -> impl Responder {
let user = sqlx::query_as::<_, User>(
"SELECT id, email FROM users WHERE id = $1"
)
.bind(id)
.fetch_one(&pool)
.await?;
HttpResponse::Ok().json(user)
}
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |