Business information leak
Need
Exposure of only the fields a client needs in API responses
Context
• Usage of Rust 1.75+ for building application services
• Usage of the actix-web crate for handling HTTP requests
• Usage of the serde crate for JSON serialization
Description
1. Non compliant code
use actix_web::{get, web, HttpResponse};
use serde::Serialize;
#[derive(Clone, Serialize)]
struct UserRecord {
id: u64,
username: String,
email: String,...The `profile` handler below loads a user record and returns it with `HttpResponse::Ok().json(user)`. The same struct is used for the database row and for the response, and it derives `Serialize` for every field. As a result, the response includes the password hash, the API key of the user and an internal flag that the client never needed. Anyone who can call the endpoint, or read its responses in browser tools, proxies or logs, obtains credentials that allow offline password cracking and direct access to the API as that user. This problem tends to grow over time: every column added to the struct for internal use is published automatically in the API.
2. Steps
• Never return passwords, password hashes, API keys, tokens or internal flags in `HttpResponse::json` bodies, except on endpoints whose purpose is to issue a credential.
• Define dedicated response types that list only the fields the client needs, instead of serializing database records.
• Remove `Serialize` from structs that hold credentials, or mark those fields with `#[serde(skip_serializing)]`.
• Review responses of every endpoint when a column or field is added to a shared struct.
• Add tests that assert sensitive field names are absent from API responses.
3. Secure code example
use actix_web::{get, web, HttpResponse};
use serde::Serialize;
#[derive(Clone)]
struct UserRecord {
id: u64,
username: String,
email: String,...The corrected code separates the database record from the response. `UserRecord` no longer derives `Serialize`, so it cannot be sent by accident, and the handler builds a `ProfileResponse` with only the identifier, the user name and the email. The response type is an explicit allowlist: a new column added to the database struct stays private until someone deliberately adds it to the response. The `From` implementation keeps the mapping in one place. When a single struct must be serialized, `#[serde(skip_serializing)]` on the sensitive fields gives a similar guarantee, but a separate response type is easier to review.
References
• 038. Business information leak