Qubit Redact
Qubit Redact prevents sensitive values from leaking through Rust diagnostics:
logs, Debug output, process arguments, environment variables, and optional
HTTP traces. Define immutable policies once, then render typed results at an
explicit log-safe boundary.
Why Qubit Redact
- One policy model classifies named fields across scalar values, maps, domain objects, process diagnostics, and optional HTTP data.
- Typed results distinguish redacted values from text that is safe to write to a plain-text log.
- Malformed or truncated structured HTTP input fails closed, and finite budgets bound inspection, output, JSON recursion, and disclosure.
- One immutable
RedactionPolicyowns base fields, HTTP/URI context overrides, masking, and static limits. Nested diagnostic values reuse oneRedactionSession, so a child cannot reset the parent's budget. - URI redaction preserves raw scheme, authority, path, query order, and encoding while applying the core policy independently to username/password, query values, and configurable path/fragment boundaries.
- The default feature set is empty; the core crate has no external runtime dependencies.
Quick Start
[]
= "0.4"
use ;
The original value remains available to application logic. Call
escape_for_log() before writing a scalar result to a plain-text log sink.
Install a process-wide default during application assembly:
use ;
let mut builder = builder;
builder.fields.raise?;
let policy = builder.build?;
install_global?;
let snapshot = default;
# Ok::
If no policy is installed, global/default reads use the fixed standard policy.
They do not prevent a later install_global() call. Existing snapshots never
change when a policy is installed later.
Warning:
install_global()belongs only in executable application assembly. Call it once after the final policy is built and before workers or request processing start; libraries must never call it. Objects created before installation may snapshot the standard policy permanently. Create any object that requires the application policy after installation, or inject the policy explicitly. The fallback supports initialization ordering and is not runtime reconfiguration.
Derive Support
qubit-redact-derive provides procedural macros for applying redaction policies
to Rust structs and enums. Redact creates a borrowed Redacted<T> view for
diagnostics, while RedactMut performs an explicit logical replacement when an
owned value is required. Use it with the qubit-redact runtime crate; the
complete field attributes and Serde/JSON integration are covered in the
derive README and derive User Guide.
Choose a Tool
| Diagnostic input | Tool | Result and logging boundary |
|---|---|---|
| Named scalar value | Redactor::redact_field |
RedactedText; call escape_for_log() for plain-text logs. |
| Text-keyed map | Redactor::redact_map or redact_map_in_place |
A copied or mutated map; apply the final logging format yourself. |
| Rust struct or enum | Redact derive |
Borrowed Redacted<T> view with safe formatting. |
| Value that must be logically replaced | RedactMut derive |
Mutated value; this is not memory erasure. |
| Command arguments | ArgvRedactor |
RedactedArgv, safe to display. |
| Environment pairs | EnvRedactor |
RedactedEnvPair or LogSafeText. |
| URL, form, headers, or captured body | HttpRedactor |
Bounded, log-safe HTTP result types. |
| URI string | UriRedactor (uri feature) |
Structured, log-safe result with component reasons. |
Cargo Features
| Need | Cargo configuration |
|---|---|
| Core scalar, map, process, and text support | qubit-redact = "0.4" |
| Domain-object derives | Add qubit-redact-derive = "0.4". |
| Serialize redacted views | Enable serde and declare serde directly. |
Redact serde_json::Value or JSON text fields |
Enable json; add serde_json directly when your application uses it. |
| HTTP diagnostics | Enable http; add http directly when your application uses its types. |
| Policy-driven URI redaction | Enable uri; this is independent from http. |
The derive #[redact(json)] mode keeps JSON text fields as their outer Rust
String type. When combined with #[redact(serde)], the redacted value is
still serialized as a JSON string.
[]
# HTTP diagnostics only
= { = "0.4", = ["http"] }
= "1.5"
# URI diagnostics without the HTTP feature
# qubit-redact = { version = "0.4", features = ["uri"] }
Safety Boundaries
- Unknown field names pass through by default. Set
UnknownFieldPolicy::Redact(Sensitivity::Secret)when a boundary must mask every unclassified field;classify_field()still reportsUnknown.RedactionPolicy::strict()provides this boundary preset without changing the default policy semantics. - Application allow rules never bypass an enabled
RedactionFloor. UseRedactionPolicy::builder()for empty application rules with the standard floor; this builder is deterministic and never reads global state. UseRedactionPolicy::default().to_builder()for the normal "extend defaults" path.disable_floor()intentionally removes every floor and is appropriate only when the caller owns that security decision. - Configure all concerns through one
RedactionPolicyBuilder: usefields(),http(),uri(), andlimits()as mutable partition views. Context rules can add protection but cannot lower a stronger base-field decision. The policy has one masking table and one limit set. - Install one global policy with
RedactionPolicy::install_global()during application assembly. It affects only future snapshots; already-built policies and redactors never change. Before installation,global()anddefault()use the fixed standard policy without occupying the install slot. Debugfor redacted domain/map views uses the policy'slimits().diagnostic_event()output budget by default. Derived nested values, maps, JSON text, and explicit adapter sessions share the sameRedactionSession; a child cannot obtain a fresh budget silently.InputOutputLimitis the immutable policy setting;RedactionSessionis the non-cloneable runtime accounting object used for one operation or diagnostic event. Reuse one session across adapters. Output accounting is committed by those adapters so fallback markers cannot bypass the cumulative limit.redact_field()returnsFieldRedaction, which distinguishes masked values from allowed and unknown pass-through values.RedactedTextis not displayable by design. Redaction and log escaping are separate guarantees.- Redacted domain and map views apply the policy diagnostic output budget to
both
Debugand log-safeDisplayby default. Usewith_output_limit()to select a different explicit limit. RedactMutreplaces logical values only. It does not erase released allocations, aliases, copies, or borrowed backing storage.- JSON redaction stops at
JsonDepthBudgetand replaces an over-depth subtree with the policy's opaque Secret mask. The default maximum depth is 128. - HTTP redaction accepts only caller-provided captures. It never reads or
buffers a network body itself. Configure HTTP behavior on the root
RedactionPolicy;HttpRedactorconsumes that snapshot. - URI redaction is opt-in through
qubit_redact::uri::UriRedactor. Userinfo is split only at the first raw:; username uses theusernamefield rule and password usespassword. Query keys are decoded strictly for classification, while unmasked values retain their original percent-encoded spelling. Invalid URI syntax or undecodable query components return a fixed marker.
Learn More
- English User Guide and 中文用户手册
- Runtime API documentation
- qubit-redact-derive README for field attributes and serde support
- qubit-redact-derive User Guide
- Derive crate API documentation
Testing
# Run tests with the default feature set
# Run tests with all declared features
# Project CI checks
# Check code coverage
License
Copyright (c) 2025 - 2026. Haixing Hu. All rights reserved.
Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.
Contributing
Contributions are welcome. Please follow the Rust API guidelines, keep public
API documentation and tests current, and run ./align-ci.sh to format code and
./ci-check.sh to satisfy CI requirements before submitting a pull request.
Author
Haixing Hu - Qubit Co. Ltd.
Repository: https://github.com/qubit-ltd/rs-redact