Technical information leak - Print Functions
Need
Prevention of sensitive information exposure through print functions
Context
• Usage of Rust 1.75+ for building application services
• Usage of std::io for filesystem and console interaction
• Usage of the log and tracing crates for structured logging
Description
1. Non compliant code
use std::fs::File;
fn main() {
match File::open("config.toml") {
Ok(_) => {}
Err(e) => {
// Leaks internal error details (paths, OS info) to stderr
eprintln!("{:?}", e);...The `main` function below uses `eprintln!("{:?}", e)` to print the `std::io::Error` value to standard error when opening `config.toml` fails. Rust's `Debug` formatter for `std::io::Error` includes the OS error kind, the underlying errno, and — depending on platform and how the error was constructed — the full path that was being opened. In production environments where stderr is captured by a log aggregator or visible to end users (CLI tools, container logs, web error pages), this output discloses: - Filesystem paths inside the deployment (config locations, user directories, mount points). - Operating system version hints via the errno mapping and error kind names. - Internal application structure, because the call site is implied by the surrounding log lines. This information meaningfully widens the attack surface: an attacker who can trigger the error path (for example by sending crafted input that reaches a `File::open` call) learns about the runtime layout of the system and can use it to refine subsequent attacks. The same risk applies to `println!`, `dbg!`, `Error::source()` chains printed verbatim, and panics with `RUST_BACKTRACE=1`.
2. Steps
• Remove every `println!`, `eprintln!`, `print!`, `eprint!`, and `dbg!` macro that runs on a production code path; reserve them for tests and local development scripts.
• Route operational errors through the `log` crate (with `env_logger`) or the `tracing` crate (with `tracing-subscriber`) so verbosity, format, and destination are controlled by configuration rather than baked into source.
• Format errors with the short `{}` (`Display`) formatter rather than `{:?}` / `{:#?}` (`Debug`); the `Display` impl on `std::io::Error` omits internal fields that `Debug` includes.
• Ensure `RUST_BACKTRACE` is unset or `0` in production deployments, and do not echo `e.backtrace()` or `e.source()` chains directly into user-facing output.
• Add a CI lint (`clippy::print_stdout`, `clippy::dbg_macro`) that fails the build if any new print-family macro is added to library or binary crates that ship to production.
3. Secure code example
use log::error;
use std::fs::File;
fn main() {
env_logger::init();
match File::open("config.toml") {
Ok(_) => {}
Err(_e) => {...The corrected function replaces the raw `eprintln!` call with a structured-logger event emitted through the `log` facade and `env_logger` backend. The message passed to `error!` is a short, opaque description ("could not open configuration file") that does not embed the underlying `std::io::Error` value, so the original path, errno, and OS error kind never reach the output. The logger can then be configured per-environment: verbose, file-paths-included output in development (`RUST_LOG=debug`), and a redacted, structured JSON event in production that only carries an opaque error code or category. Equivalent patterns apply when using `tracing` + `tracing-subscriber` instead of `log` + `env_logger`. In addition, `RUST_BACKTRACE` should be left unset (or explicitly set to `0`) in production deployments so that panic output does not include the source-file paths and line numbers of every frame in the call stack. Combined, these measures prevent the application from leaking technical details — paths, OS internals, library versions — to any observer who can read its standard streams or aggregated logs.