Skip to content

ceejbot/sensitive-fmt

Repository files navigation

sensitive-fmt

Tests audit-dependencies

Derive Debug and Display for Rust structs while honoring sensitive field annotations. Designed for one job: keeping PHI, PII, secrets, and tokens out of log lines.

I wrote this because none of the crates I could find (including my own safe-debug ) did exactly what I wanted as simply as I wanted to do it. This sensitive data redactor:

  • derives both Debug and Display
  • leans on existing implementations of both for non-primitive types, or lets you skip the field
  • is as stupid and simple as I could make it
use sensitive_fmt::{SensitiveDebug, SensitiveDisplay};

#[derive(SensitiveDebug, SensitiveDisplay)]
struct Patient {
    id: u64,
    name: String,
    #[sensitive(truncate = 4)]
    mrn: String,
    #[sensitive(redact)]
    email: String,
    #[sensitive(skip)]
    raw: NotFormattable,
}

struct NotFormattable;

let p = Patient {
    id: 42,
    name: "Alice".into(),
    mrn: "MRN-12345-WXYZ".into(),
    email: "alice@example.com".into(),
    raw: NotFormattable,
};

assert_eq!(
    format!("{p:?}"),
    r#"Patient { id: 42, name: "Alice", mrn: ****WXYZ, email: REDACTED, raw: <skipped> }"#,
);
assert_eq!(
    format!("{p}"),
    "Patient { id: 42, name: Alice, mrn: ****WXYZ, email: REDACTED, raw: <skipped> }",
);

Field attributes

One attribute, three modifiers (mutually exclusive):

Attribute Effect required trait
(none) normal Debug / Display Debug for SensitiveDebug, Display for SensitiveDisplay
#[sensitive(redact)] writes REDACTED none
#[sensitive(truncate = N)] writes ****<last N code points>, or REDACTED if value has fewer than N code points Display (in both derives)
#[sensitive(skip)] writes <skipped> none

Scope

  • Tuples fail at compile time with a clear message. Wrap the data in a named-field struct or enum instead.
  • Container types (Option<T>, Vec<T>, etc.) are treated as opaque. If you have an Option<String> field you can't Display-format directly, reach for redact or skip.
  • Generic structs are supported by adding trait bounds on the actual formatted field type. Plain fields require Debug for SensitiveDebug or Display for SensitiveDisplay; truncate always requires Display; redact and skip do not add formatting bounds.
  • no_std compatible. Requires alloc for the truncate modifier.
  • Minimum supported Rust version: 1.88.0.
  • Truncate counts code points, not graphemes. "café" is 4 code points.

Why not [other crate]?

Crate Debug Display Truncate Why this crate exists
veil yes no head + tail of *s no Display, no last-N
redactable yes yes named policies (Token / PII / …) grew slog/tracing/json features
secrecy / redact wrapper wrapper none wrapper-type model, not derives
safe-debug yes no n/a requires the facet ecosystem

License

MIT OR Apache-2.0

About

Derive Debug and Display while honoring #[sensitive(redact | truncate = N | skip)] field attributes.

Topics

Resources

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Contributors