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

2.4 KiB

zeroize

(no submodules)

Purpose

Best-effort overwrite of a Uint8Array with zero bytes. Secret material — private keys, AEAD keys, plaintext buffers, intermediate KDF state — should be zeroized as soon as it is no longer needed so that its remnants become unreadable if the process is later snapshotted, dumped, or forked into an attacker-controlled context.

JavaScript does not let us pin memory or guarantee the garbage collector has not already copied the underlying ArrayBuffer into a new region. zeroize is therefore a mitigation, not a guarantee. Use it in combination with narrow scoping (allocate the buffer inside a function, zeroize, return nothing sensitive) to minimise the residency window.

Features

  • In-place; no allocation.
  • Backed by Uint8Array#fill(0), which is inlined to a tight memset on every modern engine.
  • Zero dependencies.

Usage: Basic Example

import { zeroize } from "@suaveplan/crypto-utils/zeroize";

const key = new Uint8Array(32);
// …use the key…
zeroize(key);

Usage: Advanced Example

import { zeroize } from "@suaveplan/crypto-utils/zeroize";
import { concatBytes } from "@suaveplan/crypto-utils/bytes";

function withScopedSecret<T>(
    secretBuilder: () => Uint8Array,
    fn: (secret: Uint8Array) => T
): T {
    const secret = secretBuilder();
    try {
        return fn(secret);
    } finally {
        zeroize(secret);
    }
}

withScopedSecret(
    () => concatBytes(new Uint8Array(16).fill(0xaa), new Uint8Array(16).fill(0xbb)),
    (key) => {
        // key is live inside this block only
        return key.length;
    }
);

API Reference

  • zeroize(buf: Uint8Array): void — fills buf with 0 in place. Returns nothing. Never throws.

Implementation Notes

Uint8Array#fill(0) is the minimum the runtime can do without dropping into node:buffer or platform-specific APIs. Some environments (OpenSSL-linked node:crypto) expose sodium_memzero-equivalents that are marked volatile to prevent compiler dead-store elimination; we do not rely on those here because they are not available in browsers or edge runtimes, and cross-runtime consistency is more valuable than the marginal hardening on Node. Engines do not currently eliminate Uint8Array#fill as dead, but a future JIT optimisation could; revisit this helper if dudect-style analysis reports leakage.