In this lesson, you will learn how to design error types that distinguish between internal diagnostic needs and public-facing security boundaries to prevent sensitive information leakage.
The Conflict: Debuggability vs. Security
When an application encounters an error, developers often want to include as much context as possible—database connection strings, stack traces, or internal file paths. However, exposing these details in an API response or logs accessible to users is a security vulnerability. An attacker can use these "leaked" details to map your infrastructure, identify vulnerable dependency versions, or guess internal system architecture.
Decoupling Internal and External Representations
To prevent this, you should never return your raw, internal error types directly to the user. Instead, use an Opaque Error Type wrapper that maps internal failures to generic, safe status codes.
Consider this pattern:
pub enum InternalError {
DatabaseConnectionFailed(String), // Contains sensitive host/credentials
InvalidInput(String),
}
pub enum PublicError {
InternalServerError,
BadRequest,
}
impl PublicError {
pub fn from_internal(err: InternalError) -> Self {
// Log the detailed err internally, but return a safe variant to user
eprintln!("Log detailed error: {:?}", err);
match err {
InternalError::DatabaseConnectionFailed(_) => PublicError::InternalServerError,
InternalError::InvalidInput(_) => PublicError::BadRequest,
}
}
}
Best Practices for Leakage Prevention
- Centralize Mapping: Implement a conversion layer between your domain errors and your external API response. Never let an
std::error::Errortrait object pass through to a client-facing boundary. - Sanitize Dynamic Strings: If you must return a message, ensure that fields containing user input are sanitized. Never echo back a raw database exception string, even if it feels harmless.
- Environment Awareness: Use conditional logic to show verbose errors only when
debug_assertionsare enabled or via an explicitRUST_LOGlevel. In production, always default to a unique "Request ID" or "Correlation ID." This allows you to look up the exact error in your secure, private logs without exposing the details to the user.
By ensuring your public interface only communicates what the user needs to know—and nothing more—you protect your internal architecture from reconnaissance.
Key Takeaway: Always decouple internal diagnostic error data from public error reporting to prevent revealing system internals to unauthorized users.