Sensitive information stored in logs
Need
Exclusion of credentials and secrets from application logs
Context
• Usage of Rust 1.75+ for building application services
• Usage of the actix-web crate for handling HTTP requests
• Usage of the log crate for application logging
Description
1. Non compliant code
use actix_web::{post, web, HttpResponse};
use serde::Deserialize;
#[derive(Deserialize)]
struct LoginForm {
username: String,
password: String,
}...The `login` handler below writes the user name and the submitted password to the log with `log::info!` before checking the credentials. Logs have a much wider audience than the database. They are shipped to aggregation services, copied to backups, kept for months and read by developers, operators and support staff, often with weaker access controls than production data. Every login therefore leaves a working password in plain text in all those places, including the typos that reveal the passwords users have for other sites. The same problem occurs with tokens, API keys and session identifiers logged with `tracing::info!`, `debug!` or `warn!`, and when a whole request struct is logged with `{:?}`.
2. Steps
• Remove passwords, tokens, API keys, session identifiers and personal data from the arguments of `log` and `tracing` macros.
• Wrap secrets in `secrecy::SecretString` or `SecretBox` so their `Debug` output is redacted.
• Avoid logging whole request or configuration structs with `{:?}` unless every sensitive field is redacted.
• Configure log pipelines to mask patterns such as `password=` or bearer tokens as a second layer of defense.
• Restrict access to log storage and purge logs that already contain secrets, then rotate those secrets.
3. Secure code example
use actix_web::{post, web, HttpResponse};
use secrecy::SecretString;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct LoginForm {
username: String,
// Debug output is redacted and the value is zeroed on drop...The corrected handler logs only that a login attempt happened and for which user name. The password never reaches a log macro. The password field also uses `secrecy::SecretString` instead of `String`. Its `Debug` implementation prints a redacted placeholder, so even a later change that logs the whole form with `{:?}` does not reveal the value, and reading the secret requires an explicit `expose_secret()` call that stands out in code review. The value is also zeroed in memory when it is dropped. Logging the outcome of the authentication, success or failure, is still valuable for detecting attacks; it only needs to omit the secret.
References
• 059. Sensitive information stored in logs