4.1 KiB
SecureBuffer
(no submodules)
Purpose
SecureBuffer is the canonical envelope for sensitive cryptographic
material in @suaveplan/crypto-vault. It owns a private Uint8Array,
exposes an opt-in bytes accessor, and overwrites the backing storage
with zero bytes the moment close() is called. A module-level registry
of WeakRef<SecureBuffer> lets zeroizeAll() sweep every live buffer in
one call — useful on lock, on a security event, or as a final
process-shutdown hook. Strings and JSON serializations are masked so the
buffer can never leak through accidental logging.
Features
- Eager zeroization via
Uint8Array#fill(0)onclose(). - Idempotent close (
close()afterclose()is a no-op). - Clone isolation —
clone()allocates a fresh independent backing store. - Slice copy semantics —
slice()returns a freshUint8Array; the caller cannot mutate the buffer through the result. - Random fill via
fillRandom(rng?)(usescrypto.getRandomValuesby default; honours an injectedRngwithnextBytes). toStringandtoJSONprint onlylengthandclosedflags.- Global
zeroizeAll()sweeps every reachableSecureBufferand closes it.
Usage: Basic Example
import { SecureBuffer } from "@suaveplan/crypto-vault/secure/secure-buffer";
const buf = new SecureBuffer(32);
buf.fillRandom();
// ... use buf.bytes ...
buf.close();
Usage: Advanced Example
import {
SecureBuffer,
zeroizeAll,
} from "@suaveplan/crypto-vault/secure/secure-buffer";
// Initialise from existing key material — copied, never shared.
const seed = new Uint8Array(32).fill(7);
const masterBuf = new SecureBuffer(seed);
// Clone for an isolated branch derivation.
const branch = masterBuf.clone();
branch.bytes[0] = 1;
console.log(masterBuf.bytes[0]); // 7 — clone is fully isolated
// Lock everything in one shot.
zeroizeAll();
console.log(masterBuf.isClosed); // true
console.log(branch.isClosed); // true
API Reference
class SecureBuffer
new SecureBuffer(lengthOrSource, options?)
lengthOrSource: number | Uint8Array. Anumberallocates a fresh zero-filled buffer of that length. AUint8Arrayis copied into the internal store — the buffer never aliases caller storage.options?: SecureBufferOptions = { clock?, rng? }. Reserved for future allocation telemetry;fillRandomreadsrngdirectly.
Throws CryptoVaultError(CRYPTOVAULT.SECURE.INVALID_LENGTH) when the
numeric length is negative or non-integer.
bytes: Uint8Array
Mutable view of the live bytes. Throws
CryptoVaultError(CRYPTOVAULT.SECURE.ZEROIZED) once the buffer has been
closed.
length: number, isClosed: boolean
Length of the backing store and the closed flag.
close(): void
Overwrite the bytes with zeros and mark closed. Idempotent.
clone(): SecureBuffer
Allocate a fresh SecureBuffer with a copy of the current bytes.
slice(start?, end?): Uint8Array
Copy a range out into a new Uint8Array. The result is not registered.
fillRandom(rng?): void
Fill the bytes with CSPRNG output. Honours rng?.nextBytes(n) when
provided; otherwise falls back to crypto.getRandomValues.
toString(), toJSON()
Return [SecureBuffer length=<n> closed=<bool>] and a structurally
equivalent JSON object — never the raw bytes.
zeroizeAll(): void
Walk every live WeakRef<SecureBuffer> in the module-level registry, call
close() on each non-closed instance, and prune the registry.
liveSecureBufferCount(): number
Test helper — prune dead refs and return the number of live entries.
Implementation Notes
- The registry stores
WeakRefs so registered buffers can still be garbage-collected normally. - JavaScript cannot guarantee the GC has not relocated memory before
close(), so eager zeroization is best-effort. Combine with aggressive scoping: allocate inside a function, close, return nothing sensitive. - Memory-locking (
mlock) is intentionally out of scope. A future native binding will plug in via theSecureBufferOptionsbag without breaking callers.