Skip to main content

Crate qubit_redact

Crate qubit_redact 

Source
Expand description

§Qubit Redact

Provides immutable, policy-driven redaction for scalar fields, maps, process diagnostics, and optionally HTTP data. Safe result types separate redacted text from text that has also been escaped for logs.

§Core values and maps

use std::collections::HashMap;
use qubit_redact::{RedactionPolicy, Redactor, Sensitivity};

let mut builder = RedactionPolicy::builder();
builder
    .fields()
    .raise("tenant_secret", Sensitivity::Secret)?;
let policy = builder.build()?;
let source = HashMap::from([
    ("tenant_secret".to_owned(), "raw".to_owned()),
    ("display_name".to_owned(), "Alice".to_owned()),
]);
let redacted = Redactor::new(policy).redact_map(&source);
assert_eq!(redacted["tenant_secret"], "<redacted>");
assert_eq!(source["tenant_secret"], "raw");

An application can install one process-wide RedactionPolicy during assembly or initialization. Builders are deterministic and never read process-wide state; use RedactionPolicy::default().to_builder() when an explicit extension of the installed snapshot is needed. Existing policy snapshots never change. Before an application installs a global policy, RedactionPolicy::global() and RedactionPolicy::default() return the fixed standard policy without preventing later installation. This fallback supports dependency construction during application assembly; it is not runtime reconfiguration. The executable, never a library, owns the single installation and should complete it before starting concurrent work. Anything created earlier keeps its standard-policy snapshot. Construct policy-sensitive objects afterward or inject the application policy.

use qubit_redact::{RedactionPolicy, Sensitivity};

let mut builder = RedactionPolicy::builder();
builder
    .fields()
    .raise("tenant_secret", Sensitivity::Secret)?;
let application_default = builder.build()?;
RedactionPolicy::install_global(application_default)?;
let snapshot = RedactionPolicy::default();
assert_eq!(snapshot.sensitivity_for("tenant_secret"), Some(Sensitivity::Secret));

RedactedText is not directly displayable. Explicitly cross a plain-text logging boundary with RedactedText::escape_for_log.

use qubit_redact::Redactor;

let safe = Redactor::default()
    .redact_field("message", "line one\nline two")
    .escape_for_log();
assert_eq!(safe.to_string(), "line one\\nline two");

§Domain objects

Add the companion qubit-redact-derive crate to annotate fields explicitly. Plain fields are never recursively redacted, nested is the recursion boundary, map classifies each value by its runtime key, and skip omits a field only from the redacted representation.

use std::collections::HashMap;
use qubit_redact::{Redact as _, RedactionPolicy, Sensitivity};
use qubit_redact_derive::Redact;

#[derive(Redact)]
struct Account {
    id: u64,
    #[redact(level = "secret")]
    password: String,
    #[redact(map)]
    metadata: HashMap<String, String>,
}

let mut builder = RedactionPolicy::builder();
builder.fields().raise("api_key", Sensitivity::Secret)?;
let policy = builder.build()?;
let account = Account {
    id: 1,
    password: "raw-password".to_owned(),
    metadata: HashMap::from([
        ("api_key".to_owned(), "raw-key".to_owned()),
    ]),
};
let output = format!("{:?}", account.redacted_with(&policy));
assert!(!output.contains("raw-password"));
assert!(!output.contains("raw-key"));

RedactMut is an explicit logical in-place redaction contract. The skipped field below remains unchanged, while nested uses the same policy for the child. It does not zeroize released allocations or affect aliases, existing copies, or borrowed backing data. Clone-based to_redacted temporarily retains a second raw copy. Use a separately designed zeroization strategy when memory erasure is required.

use qubit_redact::{Redact as _, RedactMut as _};
use qubit_redact_derive::{Redact, RedactMut};

#[derive(Clone, Redact, RedactMut)]
struct Secret {
    #[redact(level = "secret")]
    value: String,
}

#[derive(Clone, Redact, RedactMut)]
struct Envelope {
    #[redact(nested)]
    secret: Secret,
    #[redact(skip)]
    internal_note: String,
}

let mut envelope = Envelope {
    secret: Secret { value: "raw".to_owned() },
    internal_note: "unchanged".to_owned(),
};
envelope.redact_in_place();
assert_eq!(envelope.secret.value, "<redacted>");
assert_eq!(envelope.internal_note, "unchanged");

With the serde feature, a direct serde dependency, and the companion derive crate, #[redact(serde)] opts the redacted view into serialization. Redacted intentionally does not implement Deserialize.

use qubit_redact::Redact as _;
use qubit_redact_derive::Redact;

#[derive(Redact)]
#[redact(debug, display, serde)]
struct Credentials {
    #[redact(level = "secret")]
    token: String,
    #[redact(skip)]
    internal_note: String,
}

let value = Credentials {
    token: "raw-token".to_owned(),
    internal_note: "not serialized".to_owned(),
};
let json = serde_json::to_string(&value.redacted())?;
assert!(!json.contains("raw-token"));
assert!(!json.contains("internal_note"));
assert!(!format!("{value:?}").contains("raw-token"));
assert!(!format!("{value}").contains("raw-token"));

debug and display are opt-in implementations on the original type and use the process-wide default policy. Plain fields remain ordinary Debug values. Redacted Debug and Display output use the policy’s diagnostic output budget by default. Use with_output_limit() to select a different explicit limit. Do not request an implementation already supplied by the type, such as combining #[derive(Debug)] with #[redact(debug)].

Derives support named, tuple, and unit structs, plus enums with named, tuple, and unit variants. With #[redact(serde)], redacted serialization supports Serde’s external, internal, adjacent, and untagged enum representations through a structure-preserving attribute allowlist.

use qubit_redact::Redact as _;
use qubit_redact_derive::Redact;

#[derive(Redact)]
struct Token(#[redact(level = "secret")] String);

#[derive(Redact)]
enum Event {
    Credential(#[redact(level = "secret")] String),
    Ready,
}

assert_eq!(
    format!("{:?}", Token("raw".into()).redacted()),
    "Token(\"<redacted>\")",
);
assert_eq!(
    format!("{:?}", Event::Credential("raw".into()).redacted()),
    "Credential(\"<redacted>\")",
);
assert_eq!(format!("{:?}", Event::Ready.redacted()), "Ready");

redacted() snapshots the process default; redacted_with snapshots an explicit policy, which every nested and map field reuses. Field-specific map policies are not supported in the first version; use a domain newtype plus nested for a separate policy boundary.

§Process diagnostics

Process adapters use the InputOutputLimit in their RedactionPolicy snapshot. They stop before inspecting argv or environment input beyond the input limit and truncate their final log-safe list at the output limit.

use std::ffi::OsStr;
use qubit_redact::{ArgvRedactor, EnvRedactor, argv::ArgvItem};

let argv = [
    ArgvItem::plain(OsStr::new("client")),
    ArgvItem::plain(OsStr::new("--password")),
    ArgvItem::plain(OsStr::new("raw")),
];
assert!(!ArgvRedactor::default()
    .redact_heuristically(argv)
    .to_string()
    .contains("raw"));
assert_eq!(
    EnvRedactor::default().redact_pair("PASSWORD", "raw").to_string(),
    "PASSWORD=<redacted>",
);

§JSON values

With the json feature, RedactedJson, RedactedJsonText, and redact_json_text_in_place share the JsonDepthBudget stored in their immutable RedactionPolicy snapshot. The default maximum depth is 128; an over-depth object or array is replaced with the policy’s opaque Secret mask without visiting its descendants.

§HTTP bodies

Enable this API with qubit-redact = { version = "0.4", features = ["http"] }. http::BodyCapture makes completeness explicit, and the returned http::BodyRedaction implements std::fmt::Display with bounded, log-safe output.

use http::HeaderValue;
use qubit_redact::http::{BodyCapture, BodyRedaction, HttpRedactor};

let content_type = HeaderValue::from_static("application/json");
let result: BodyRedaction = HttpRedactor::default().redact_body(
    BodyCapture::complete(br#"{"password":"raw","mode":"debug"}"#),
    Some(&content_type),
);
assert!(!format!("{result}").contains("raw"));

Re-exports§

pub use argv::ArgvRedactor;
pub use domain::BoundedRedactedDisplay;
pub use domain::Redact;
pub use domain::RedactMapValue;
pub use domain::RedactMapValueMut;
pub use domain::RedactMut;
pub use domain::RedactValue;
pub use domain::RedactValueMut;
pub use domain::Redacted;
pub use domain::RedactedKeyedMap;
pub use domain::RedactedKeyedMapSession;
pub use domain::RedactedKeyedValue;
pub use domain::RedactedKeyedValueSession;
pub use domain::RedactedMap;
pub use domain::RedactedMapSession;
pub use domain::RedactedSessionView;
pub use domain::RedactedValue;
pub use env::EnvRedactor;
pub use json::RedactedJson;
pub use json::RedactedJsonSession;
pub use json::RedactedJsonText;
pub use json::RedactedJsonTextSession;
pub use json::redact_json_text_in_place;
pub use policy::AllowRule;
pub use policy::DiagnosticBudgetError;
pub use policy::FieldClassification;
pub use policy::FieldMatchKind;
pub use policy::FieldNameMatching;
pub use policy::InputOutputLimit;
pub use policy::MaskPolicy;
pub use policy::MaskingPolicy;
pub use policy::PolicyError;
pub use policy::PolicyLocation;
pub use policy::RedactionFloor;
pub use policy::RedactionFloorBuilder;
pub use policy::RedactionLimits;
pub use policy::RedactionPolicy;
pub use policy::RedactionPolicyBuilder;
pub use policy::RedactionRules;
pub use policy::RedactionSession;
pub use policy::RedactionSessionKind;
pub use policy::SensitiveFieldPreset;
pub use policy::SensitiveFieldRule;
pub use policy::Sensitivity;
pub use policy::UnknownFieldPolicy;
pub use policy::JsonDepthBudget;
pub use policy::JsonDepthBudgetError;
pub use policy::UnkeyedJsonValuePolicy;
pub use text::BoundedLogSafeDisplay;
pub use text::DiagnosticLogBuilder;
pub use text::DiagnosticWriteStatus;
pub use text::LogOutputLimit;
pub use text::LogOutputLimitError;
pub use text::LogSafeText;
pub use text::RedactedDebug;
pub use text::RedactedText;
pub use text::redacted_debug;
pub use uri::UriComponent;
pub use uri::UriFragmentPolicy;
pub use uri::UriInspection;
pub use uri::UriPathPolicy;
pub use uri::UriPolicy;
pub use uri::UriRedaction;
pub use uri::UriRedactionReason;
pub use uri::UriRedactionStatus;
pub use uri::UriRedactor;

Modules§

argv
Redaction adapters for process argument vectors.
domain
Runtime traits and borrowed views for domain-object redaction.
env
Redaction adapters for environment-variable diagnostics.
http
Immutable HTTP redaction policy, bounded body input, and safe results.
json
Policy-aware redaction for parsed JSON values and JSON stored as text.
policy
Immutable field classification and value-masking primitives.
text
Typed text values that distinguish redacted data from log-safe output.
uri
Policy-driven URI redaction.

Structs§

InstallGlobalPolicyError
Owns the policy that could not be installed because a global policy was already installed.
Redactor
Applies one immutable policy to scalar values and string maps.

Enums§

FieldRedaction
Explains whether a field value was masked or intentionally passed through.
PassThroughReason
Reason a field value was retained without masking.