Secure Types
The goal of this crate is to provide a simple way to properly handle sensitive data in memory (e.g. passwords, private keys, etc).
Currently there are 3 types:
SecureString: For working with strings.SecureVec: For working withVec<T>.SecureArray: For working with&[T; LENGTH].
Features
- Zeroization on Drop: Memory is wiped when dropped.
- Memory locking (OS only): Pages are
mprotectedPROT_NONEexcept during anunlock*scope. That stops accidental reads, including from this process; it is not a defence againstptraceor a debugger. On the malloc path the allocation is also mlocked (Windows:VirtualLock) and, where the OS allows it, excluded from core dumps. Linux usesmemfd_secretwhen the kernel supports it. See How memory is locked. - Scoped access: No
Index/Deref—secret[0]does not compile. Contents are only reachable through theunlock*closures (theexpose-ptrtesting feature aside). Memory is unprotected only for that closure. - Send, not Sync: Values can move to another thread. Sharing one instance needs an explicit lock (
Arc<Mutex<_>>); concurrentunlockraces on page protection. no_std: Zeroization only. Disable the default features — see Feature Flags.- Serde: Optional serialization for
SecureString,SecureVec<T>, andSecureArray<T, LENGTH>. Au8container serializes as a byte buffer; other element types as a sequence (SeqElement). - Binary codec (feature
codec): AserdeSerializer/Deserializer that encodes into locked memory and decodes out of it. No extra dependency beyondserde.
How memory is locked
- Windows: VirtualProtect and VirtualLock.
- Linux: memfd_secret when the kernel supports it. Otherwise mlock and madvise (
MADV_DONTDUMP) on the malloc path. Amemfd_secretallocation is notmlocked and is not markedMADV_DONTDUMPbymemsec. Every path still usesmprotect(PROT_NONE)between unlocks. - Other Unix (macOS, FreeBSD, …): mlock plus
mprotect(PROT_NONE). FreeBSD/DragonFly also usemadvise(MADV_NOCORE). Nomemfd_secret.
mlock is best-effort: memsec ignores its return value, so hitting RLIMIT_MEMLOCK still constructs. mprotect failure is Error::LockFailed. A failed re-lock after an unlock* scope panics in every profile.
Usage
SecureString
use SecureString;
// Create a SecureString
let mut secret = from;
// The memory is locked here
// Safely append more data.
secret.push_str;
// The memory is locked here.
// Use a scope to safely access the content as a &str.
secret.unlock_str;
// When `secret` is dropped, its data zeroized.
SecureVec
use SecureVec;
// Create a new, empty secure vector.
let mut secret_key: = new.unwrap;
// Push some sensitive data into it.
secret_key.push;
secret_key.push;
secret_key.push;
// The memory is locked here.
// Use a scope to safely access the contents as a slice.
secret_key.unlock_slice;
SecureArray
use SecureArray;
let exposed_array: &mut = &mut ;
let mut secure_array = from_slice_mut.unwrap;
secure_array.unlock_mut;
Binary codec
The codec feature is a small binary serde format. Encode goes into locked memory; decode reads out of it. #[derive(Serialize, Deserialize)] and #[serde(...)] work as usual. No extra dependency beyond serde.
#
# Ok::
encode returns a SecureBytes. decode unlocks only for the parse and re-locks afterwards, including on error. Types are raw binary — a SecureArray<u8, 32> is 32 bytes — and strings are not escaped, so there is no scratch copy of an unescaped string.
Format evolution. FORMAT_VERSION is the first byte. An unknown version is refused. Adding a field with #[serde(default)] does not need a bump: fields are named and length-prefixed, so unknown fields are skipped and missing ones take their default. Changing a field's type does need a bump.
Not supported. No type tags, so deserialize_any is unimplemented. #[serde(flatten)], #[serde(untagged)], and Value-shaped fields fail with DecodeError::Unsupported. Untagged enums can still serialize; they cannot be read back.
See also the examples.
Feature Flags
use_os(default): Enables all OS-level security features. Supported on Linux, Windows, and other Unix (macOS, FreeBSD, …); thememfd_secretbacking (and core-dump exclusion viaMADV_DONTDUMP) is Linux-only.no_os: No-op, kept for backwards compatibility.no_stdis selected by disabling the default features (--no-default-features), which leaves only the zeroize-on-drop guarantee.serde: Enables serialization/deserialization.codec: Addsencode/encode_with_capacity/decode/decode_slice, a binary format written into locked memory and read out of it. Impliesserde, works inno_std+alloc, and adds no dependency.expose-ptr: For testing purposes. Exposes the locked memory region pointer.
Security notes
- Serialization writes plaintext.
serde_json::to_string/to_vecleave the document in an ordinaryString/Vecthat nothing wipes. Zeroize that buffer yourself, write throughSecureBytesWriter, or use the binary codec. - Deserialization reads a buffer you own.
serde_json::from_str/from_slicetake a plain&str/&[u8]. Parse from inside locked memory (locked.unlock_slice(|json| serde_json::from_slice::<Vault>(json))) so the input is unlocked only for the parse. Escaped JSON strings still land inserde_json's own scratch buffer, which this crate cannot wipe. The codec decoder has no such scratch.
Running tests
Public-API tests live in tests/ (one integration crate per module). Internals, memory-protection checks, and crash tests that spawn a child stay in src/ — a fault on locked memory kills the process, so they run in a child. Shared fixtures are in tests/common/.
License
Licensed under the MIT license.