sanitization
Dependency-free, no_std-first secret memory sanitization for Rust.
sanitization is for projects that want a small secret-container layer without
pulling in zeroize or a proc-macro dependency by default. The main design is
architectural: keep secrets inside redacted, non-Copy, non-Clone,
clear-on-drop containers from creation, and use explicit opt-in APIs when an
ordinary buffer must be wiped.
Current Status
The crate is published as a release candidate on crates.io. The current
candidate is intended for downstream integration testing before the stable
1.0.0 release.
Implemented now:
no_stddefault build.- zero runtime dependencies.
- no unsafe code in default builds.
SecretBytes<N>for fixed-size secrets.Secret<T>for custom sanitizable values.secure_sanitize_struct!andsecure_drop_struct!helper macros.- optional
allocsupport withSecretVecandSecretString. - optional
unsafe-wipevolatile backend for existing ordinary buffers. - redacted
Debugfor secret-owning wrapper types. - clear-on-drop behavior for crate-owned secret containers.
- local CI/check script and GitHub workflow.
- threat model and unsafe-boundary documentation.
Trust Dashboard
| Area | Status |
|---|---|
| License | MIT OR Apache-2.0 |
| MSRV | Rust 1.90.0 |
| Default target | no_std |
| Runtime dependencies | zero external crates |
| Default unsafe policy | #![forbid(unsafe_code)] |
| Optional unsafe | only behind unsafe-wipe, isolated in unsafe_wipe |
| Heap support | alloc feature |
| Proc macros | none |
| Main guarantee | narrow ownership, redaction, and clear-on-drop hygiene |
| Out of scope | stack-history wiping, cache flushing, OS memory locking, dumps/swap |
Read THREAT_MODEL.md and SAFETY.md before using this crate for high-assurance secret handling.
Rust Version Support
The minimum supported Rust version is Rust 1.90.0. New deployments should
prefer the latest stable Rust.
Compatibility evidence:
| Rust | Local Evidence |
|---|---|
1.90.0 |
full check gate |
1.91.0 |
cargo check --all-features |
1.92.0 |
cargo check --all-features |
1.93.0 |
cargo check --all-features |
1.94.0 |
cargo check --all-features |
1.95.0 |
cargo check --all-features |
1.96.0 |
cargo check --all-features |
Install
[]
= "1.0.0-rc.2"
For heap-backed secret containers:
[]
= { = "1.0.0-rc.2", = ["alloc"] }
For explicit volatile wiping of ordinary buffers:
[]
= { = "1.0.0-rc.2", = ["unsafe-wipe"] }
For heap containers plus volatile wiping:
[]
= { = "1.0.0-rc.2", = ["alloc", "unsafe-wipe"] }
Features
| Feature | Default | Purpose |
|---|---|---|
alloc |
no | Enables SecretVec and SecretString. |
std |
no | Currently aliases alloc for downstream convenience. |
unsafe-wipe |
no | Enables explicit volatile wiping APIs for ordinary buffers. |
Default builds are dependency-free, no_std, and forbid unsafe code.
Fixed-Size Secrets
Use SecretBytes<N> for keys, tokens, nonces, salts, or other fixed-size
secret byte arrays that you control from creation.
use SecretBytes;
let key = from_fn;
assert_eq!;
assert!;
The type intentionally does not implement Clone, Copy, Deref,
AsRef<[u8]>, or secret-printing Debug.
Copying Secrets Into External APIs
Some cryptographic or protocol APIs require &[u8]. Use expose_secret for
short-lived closure access. The temporary copy is cleared on the normal return
path and during unwinding, but cannot be cleared if the process aborts.
use SecretBytes;
let key = from_array;
let first_byte = key.expose_secret;
assert_eq!;
Updating and Clearing Fixed-Size Secrets
Multi-byte mutation and clearing require &mut self, so shared references
cannot observe partially-cleared multi-byte writes.
use SecretBytes;
let mut key = zeroed;
key.copy_from_slice.unwrap;
assert!;
key.write_byte.unwrap;
assert_eq!;
key.secure_clear;
assert!;
Heap Secrets
Enable alloc for dynamic secret bytes and secret UTF-8 text.
use ;
let mut token = from_secret_str;
assert_eq!;
assert!;
token.push_str;
assert_eq!;
let mut bytes = from_slice;
assert_eq!;
assert!;
bytes.with_secret_mut;
SecretVec and SecretString wipe initialized bytes and spare heap capacity
before freeing their allocations. They expose contents through closures and
redact Debug.
Custom Structs Without Proc Macros
Use secure_drop_struct! when the macro should own Drop and clear every
field on drop:
use ;
secure_drop_struct!
let credentials = SessionCredentials ;
assert!;
Use secure_sanitize_struct! when you need to write a custom Drop
implementation yourself:
use ;
secure_sanitize_struct!
let mut credentials = Credentials ;
credentials.secure_sanitize;
These macros are declarative macro_rules! macros. They do not require syn,
quote, proc-macro2, or any compile-time code-generation dependency.
Generic Secret Wrapper
Use Secret<T> when you already have a type that implements SecureSanitize
and you want clear-on-drop plus redacted Debug.
use ;
let mut pair = new;
pair.with_secret_mut;
Explicit Volatile Wiping
Safe Rust cannot volatile-wipe arbitrary existing memory. If a secret already
lives in an ordinary buffer, enable unsafe-wipe and call the volatile backend
explicitly.
use volatile_sanitize_bytes;
let mut bytes = ;
volatile_sanitize_bytes;
assert_eq!;
With alloc and unsafe-wipe, Vec<u8> and String helpers are available:
use ;
let mut bytes = vec!;
volatile_sanitize_vec;
assert!;
let mut token = Stringfrom;
volatile_sanitize_string;
assert!;
For clear-on-drop volatile behavior, use VolatileOnDrop:
use VolatileOnDrop;
let secret = new;
assert_eq!;
Choosing the Right API
| Use case | Recommended API |
|---|---|
| Fixed-size key or token | SecretBytes<N> |
| Dynamic secret bytes | SecretVec with alloc |
| Secret UTF-8 text | SecretString with alloc |
| Custom struct, macro-owned drop | secure_drop_struct! |
| Custom struct, custom drop | secure_sanitize_struct! |
| Existing ordinary buffer | unsafe_wipe::volatile_sanitize_* |
| Generic clear-on-drop wrapper | Secret<T> |
Relationship to zeroize
zeroize is broader and more ergonomic for retrofitting existing types,
especially with #[derive(Zeroize, ZeroizeOnDrop)]. This crate deliberately
does not ship a proc-macro derive in the core crate because that would add
compile-time dependencies and supply-chain surface.
The intended trade-off:
- use wrapper types from the start for stronger ownership discipline;
- use dependency-free declarative macros for custom structs;
- use explicit volatile APIs only where ordinary memory must be wiped.
Local Checks
Run the local matrix before changing release-sensitive code:
The check script covers formatting, feature-matrix tests, examples, clippy, docs with warnings denied, and package listing.
Limits
This crate reduces accidental retention and accidental exposure. It does not provide complete process-memory secrecy.
Important limits:
- Safe Rust cannot volatile-wipe arbitrary existing memory.
- Safe Rust cannot soundly scrub old stack frames from previous moves.
panic = "abort"prevents destructors from running.- Whole-program optimization can weaken best-effort safe cleanup.
- CPU cache flushes, SIMD clearing, platform memory locking, guard pages, and inline assembly require target-specific unsafe code and are intentionally not part of the default API.
- It does not protect against swap, hibernation, core dumps, debugger access,
/proc/<pid>/mem, kernel compromise, DMA, firmware compromise, or copies made by third-party libraries.
See THREAT_MODEL.md, SAFETY.md, and SECURITY.md for the security model and maintenance policy.