Files
skills/docs/security-stack/crypto/crypto-vault/src/secure/secure-buffer.md
2026-05-29 08:20:56 +01:00

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) on close().
  • Idempotent close (close() after close() is a no-op).
  • Clone isolation — clone() allocates a fresh independent backing store.
  • Slice copy semantics — slice() returns a fresh Uint8Array; the caller cannot mutate the buffer through the result.
  • Random fill via fillRandom(rng?) (uses crypto.getRandomValues by default; honours an injected Rng with nextBytes).
  • toString and toJSON print only length and closed flags.
  • Global zeroizeAll() sweeps every reachable SecureBuffer and 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. A number allocates a fresh zero-filled buffer of that length. A Uint8Array is copied into the internal store — the buffer never aliases caller storage.
  • options?: SecureBufferOptions = { clock?, rng? }. Reserved for future allocation telemetry; fillRandom reads rng directly.

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 the SecureBufferOptions bag without breaking callers.