Crate safe_debug

Crate safe_debug 

Source
Expand description

§SafeDebug

A Rust derive macro for std::fmt::Debug that automatically redacts sensitive fields marked with #[facet(sensitive)]. Derive Facet and SafeDebug, then decorate fields with sensitive data with #[facet(sensitive)]. You’ll get:

  • a full std::fmt::Debug implementation– respects {:?} and {:#?}
  • automatic redaction of fields you’ve marked as sensitive
  • fallback to full redaction on any failure to find valid structure metadata
  • all the benefits of deriving facet and existing in that ecosystem

The benefit of opting into facet here is, for example, the ability to do other things based on the presence of the shape metadata and the sensitivity flag, which you might want when implementing things in a context where this matters

Tests Coverage

§Example

use facet::Facet;
use safe_debug::SafeDebug;

#[derive(Facet, SafeDebug)]
struct PatientRecord {
    id: String,
    name: String,
    #[facet(sensitive)]
    ssn: String,
    #[facet(sensitive)]
    medical_history: String,
}

fn main() {
    let record = PatientRecord {
        id: "12345".to_string(),
        name: "John Doe".to_string(),
        ssn: "123-45-6789".to_string(),
        medical_history: "Confidential medical data".to_string(),
    };

    // While logging, somebody drops in data that includes fields with PHI in them...
    println!("useful but inattentive log line; record='{:?}'", record);
    // but we are saved because the output is:
    // useful but inattentive log line; record=PatientRecord { id: "12345", name: "John Doe", ssn: "[REDACTED]", medical_history: "[REDACTED]" }

    // Pretty-print also works
    println!("{:#?}", record);
}

See the ./examples directory for more usage examples.

§Installation

Add safe-debug and facet to your crate’s dependencies:

cargo add safe-debug
cargo add facet

Or add manually to your Cargo.toml:

[dependencies]
safe-debug = "0.1"
facet = "0.31"

§Usage

For any struct or enum where you want automatic sensitive field redaction:

  1. Derive both Facet and SafeDebug
  2. Mark sensitive fields with #[facet(sensitive)]
  3. Use {:?} or {:#?} formatting as normal

The Debug implementation will automatically redact fields marked as sensitive.

§Troubleshooting

§Error: “the trait bound YourType: Facet is not satisfied”

Remember to derive Facet as well as SafeDebug:

#[derive(Facet, SafeDebug)]
struct YourType { /* ... */ }

§Error: “the trait bound T: std::fmt::Debug is not satisfied”

Make sure all your type parameters implement Debug:

#[derive(Facet, SafeDebug)]
struct Container<T: Debug> {  // add a bound here to enforce it
    data: T,
}

§Sensitive data appearing in logs

Make sure you’re actually using the Debug trait (via {:?} or {:#?}), not Display or other formatting traits, via {}. This crate only derives Debug, leaving Display for you to control if you need it to print sensitive data for legitmate reasons.

§License

This crate uses the same licensing approach as facet itself.

Licensed under either of:

at your option.

§SafeDebug - Debug with Field Redaction

Provides a derive macro for std::fmt::Debug that automatically redacts sensitive fields marked with #[facet(sensitive)]. Designed for HIPAA-compliant healthcare applications and other systems handling sensitive data.

§Requirements

  • Must also derive or implement facet::Facet
  • Sensitive fields marked with #[facet(sensitive)]

§Performance

Minimal runtime overhead:

  • One Self::SHAPE const access per Debug::fmt call
  • One field.is_sensitive() check per field (O(1) bitflag check)
  • No heap allocations beyond standard Debug formatting
  • Zero cost for non-sensitive fields (formatted normally)

The overhead is typically <1% compared to hand-written Debug implementations, which is negligible in practice since Debug formatting is already I/O-bound.

§Example

use facet::Facet;
use safe_debug::SafeDebug;

#[derive(Facet, SafeDebug)]
struct PatientRecord {
    id: String,
    #[facet(sensitive)]
    ssn: String,
}

let record = PatientRecord {
    id: "12345".to_string(),
    ssn: "123-45-6789".to_string(),
};

// SSN will be redacted in debug output
let debug_output = format!("{:?}", record);
assert!(debug_output.contains("12345"));
assert!(!debug_output.contains("123-45-6789"));
assert!(debug_output.contains("[REDACTED]"));

Derive Macros§

SafeDebug
Derives std::fmt::Debug with automatic redaction for sensitive fields.