secure-gate 0.3.0

Zero-overhead, feature-gated secure wrappers for secrets
docs.rs failed to build secure-gate-0.3.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: secure-gate-0.7.0-rc.14

secure-gate

A zero-overhead, no_std-compatible secret wrapper with automatic zeroization.

Key Features Summary

secure-gate is designed for seamless, safe secret handling with zero runtime overhead when features are disabled. Core highlights:

  • Auto-Gating: Switches between SecretBox<T> + zeroization (default zeroize feature) and plain Box<T> fallback for minimal builds—no code changes required.
  • No-Std Native: Full no_std + alloc support for embedded systems.
  • Safe & Ergonomic: All public API in 100% safe Rust; secure! macro for quick construction; aliases like SecurePassword (immutable default), SecurePasswordMut (mutable opt-in), and SecureKey32.
  • Redacted & Zeroized: Automatic Debug redaction ("[REDACTED]"); best-effort zeroization on drop/mutation via zeroize.
  • Serde-Ready: Opt-in serialization of secrets (explicitly exposes the secret in serialized form, e.g., JSON strings; protect output bytes appropriately).
  • Fuzz-Hardened: 5 libFuzzer targets running 300 CPU minutes nightly. ** - Zero-Alloc Fixed Secrets: Stack-only types for keys/nonces (default via stack feature) — no heap, cache-local, #![no_global_oom] friendly.**

Installation

Add this to your Cargo.toml:

[dependencies]

secure-gate = "**0.3.0**"

Usage

use secure_gate::{SecurePassword, SecurePasswordMut, Secure};

let password: SecurePassword = "hunter2".into();  // Immutable default, zeroized on drop

let key = Secure::<[u8; 32]>::new(rand::random());  // generic fixed-size (heap for dynamic fallbacks)
let token = Secure::<Vec<u8>>::new(vec![...]);  // dynamic buffer

// Scoped mutation (preferred for mutable cases)
let mut pw_mut: SecurePasswordMut = SecurePasswordMut::new("base".to_string());
pw_mut.expose_mut().expose_secret_mut().push_str("!!!");
pw_mut.finish_mut();  // reduce excess capacity via shrink_to_fit (best-effort; no scrub of freed memory)

// Extraction (use sparingly)
let bytes: Vec<u8> = token.into_inner();  // original zeroized immediately

Zero-Allocation Fixed-Size Secrets (Default)

For keys/nonces/IVs, aliases like SecureKey32 default to stack-only Zeroizing<[u8; N]> — no heap, deterministic, side-channel minimal:

use secure_gate::{SecureKey32, SecureNonce12};
use secure_gate::stack::{key32, nonce12};

static AES_KEY: SecureKey32 = key32([0x42; 32]);  // const-eligible!
let key: SecureKey32 = SecureKey32::new(rand::random::<[u8; 32]>());  // from RNG
let nonce: SecureNonce12 = nonce12([0u8; 12]);

// Access: deref to inner slice/array
assert_eq!(&*key, &[0x42; 32]);

Falls back to Secure<[u8; N]> if stack disabled. Ideal for crypto hot paths (rustls/ring-style).

Fuzzing Configuration

Target Description Runtime per CI run
expose Memory access + finish_mut 60 minutes
clone init_with, into_inner, scoped zeroization 60 minutes
serde JSON + bincode deserialization from untrusted input 60 minutes
parsing FromStr parsing 60 minutes
mut Unbounded expose_mut() mutation stress 60 minutes
  • 5 libFuzzer targets
  • 300 CPU minutes per nightly run (6 × 60 min)
  • Runs on GitHub Actions (ubuntu-latest, nightly toolchain)
  • -rss_limit_mb=4096, -max_total_time=3600, -timeout=60
  • Artifacts uploaded on every run

Dependencies

[dependencies]

secrecy = { version = "0.10.3", optional = true, default-features = false }

zeroize = { version = "1.8", optional = true, default-features = false, features = ["alloc", "zeroize_derive"] }

serde = { version = "1.0", features = ["derive"], optional = true }

Features

Feature Effect
zeroize Enables SecretBox<T> + zeroization on drop (default)
stack Zero-alloc fixed-size types (Zeroizing<[u8; N]> for SecureKey32 etc.; default)
serde Adds Serialize / Deserialize impls
unsafe-wipe Opt-in fast zeroization for Secure<String> (no allocation, preserves len/cap; requires zeroize). Disables #![forbid(unsafe_code)] for this path—safe usage (only overwrites used buffer with zeros; null bytes valid UTF-8). Use for performance-critical secrets; stick to safe path otherwise.
full Enables zeroize + stack + serde + unsafe-wipe

Zeroization Guarantees

Secure<T> provides best-effort memory zeroization on drop/mutation via the zeroize crate:

  • What It Does: Explicitly overwrites secret bytes (up to .len() for dynamic types like Vec/String) using volatile operations that resist compiler optimization.
  • Platform Coverage: Works on all stable Rust targets (x86, ARM, RISC-V, etc.) via portable intrinsics. No guarantees against hardware leaks (e.g., cache side-channels)—use constant-time primitives alongside.
  • Limitations: Only affects the wrapped value; doesn't secure against copies, logs, or kernel dumps. For full protection, avoid extraction (into_inner) and use scoped expose_mut().
  • finish_mut: After mutations, call this to reduce excess capacity (for Vec<u8>, String) via shrink_to_fit(). This is best-effort—some allocators may not shrink—and does not overwrite freed memory (old secrets may persist until allocator/OS reuse). Zeroization on drop still covers only the used portion (up to .len()).
  • Dynamic Container Caveats: For growable types like Vec<u8> or String, safe Rust cannot zero the full historical capacity (e.g., after truncate or realloc). Only the current slice up to .len() is overwritten on drop. Avoid patterns like filling a large buffer with secrets then truncating to small length—opt for fixed-size where possible or explicitly zero excess via expose_mut().fill(0) before shrinking.
  • Unsafe-Wipe Fast Path: When enabled, Secure<String> uses unsafe for zero-allocation wiping (preserves len/cap)—safe for used buffer only. Null bytes are valid UTF-8; no invariants broken. Opt-in for performance (e.g., high-frequency secrets); safe path used otherwise (allocates temp zeros).
  • Fallback Mode: Disabled without zeroize feature—treat as plain Box<T>. ** - Stack Aliases Note: Fixed-size types like SecureKey32 use Zeroizing<[u8; N]> by default (via stack feature) for zero-overhead zeroization — same guarantees, no heap.**

For details, see zeroize docs.

Contribution

Contributions welcome! Please submit PRs with tests/fuzz targets.

License

Licensed under MIT OR Apache-2.0