npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ths-csprng

v2.0.2

Published

Temporal Hardening Solution: a multi-source CSPRNG wrapper that remains secure when individual entropy sources are compromised, backdoored, or actively hostile.

Readme

ths-csprng

npm version Node.js License Verify Status Donate

Standard CSPRNGs assume that once a seed is generated, the history of how it was produced is irrelevant. In practice that assumption breaks down in documented, real-world ways — backdoored hardware RNGs, hypervisor entropy starvation, forked process state reuse, and OS-level DRBG compromises have all produced exploitable key material in production systems.

THS (Temporal Hardening Solution) is a multi-source CSPRNG wrapper built around the idea that no single entropy source should be trusted unconditionally. It derives randomness by observing a sequential movie of system state — CPU jitter, scheduler noise, GC pressure, and OS bytes — across many frames, mixing everything into a KMAC256 accumulator and optionally memory-hardening early frames with native crypto.argon2id. The result is a seed whose reconstruction requires an attacker to accurately simulate the full physical evolution of the machine across every frame, not just recover a single snapshot.


The Real-World Threat Model

THS was designed around concrete, documented attack scenarios — not academic abstractions.

  • Backdoored hardware RNG (RDRAND / Dual_EC_DRBG). Early Intel Ivy Bridge errata produced biased RDRAND output. The NSA-influenced Dual_EC_DRBG, standardised in NIST SP 800-90A and shipped in RSA BSAFE and several TLS stacks, contained a deliberately chosen curve point that let its designer predict all future output from 32 bytes of observation. THS never forwards OS CSPRNG output directly — every byte is mixed through SHA3-512 with timing-derived entropy the DRBG cannot observe, and optionally stretched with argon2id.

  • VM / hypervisor entropy starvation. Virtualised environments (AWS EC2, GCP, Azure, Docker) frequently boot with a near-empty kernel entropy pool because the guest OS has no access to physical hardware noise. The 2012 landmark paper “Mining Your Ps and Qs” found tens of thousands of colliding RSA moduli from internet-wide key scans — a direct consequence of early-boot key generation in exactly these environments. THS compensates by harvesting CPU jitter, scheduler noise, GC pressure, and context-switch counters — sources that are genuinely unpredictable even in a VM, even when the kernel pool is nearly empty.

  • Forked process state reuse. fork() duplicates the entire process address space, including any in-memory PRNG state. Two forked children starting from the same state produce identical byte streams. This famously affected OpenSSL in Debian via CVE-2008-0166 and has been rediscovered in Python, Ruby, and Go runtimes. THS re-seeds from fresh OS entropy at the start of every random() call, not just at initialisation. A forked child diverges immediately on its first call.

  • Forward-secrecy compromise. After each random() call, the internal entropy storage is ratcheted forward with post-output frames generated after the output was committed. An adversary who captures the output cannot derive the next seed without also knowing scheduler events, OS reads, and hrtime values that had not yet occurred when the output was produced.

📖 Documentation: Full API breakdowns, edge cases, and advanced configurations are available at harnumaix.github.io/temporal-hardening-solution-csprng.

📦 Full API context optimized for AI assistants and contributors is available in llms-full.txt.


🚀 Getting Started

npm install ths-csprng

Requires Node.js ≥ 24.7.0.


🛠️ Quick Start

Modern ESM (Node.js 24+, Bun)

import THS from 'ths-csprng';

// Standard 256-bit secure random buffer (Level 2, 64 frames)
const entropy = await THS.random(32);

// Long-term master key with maximum hardening
const masterKey = await THS.random(64, {
    layers:          512,          // 512 sequential entropy frames
    harden:          3,            // Level 3: SHA3-512 + native argon2id
    mem:             65536,        // 64 MiB argon2id memory cost
    memoryH:         4,            // Apply argon2id to the first 4 frames
    trng:            true,         // Read directly from /dev/urandom
    sandwichMode:    true,         // Ratchet entropy storage forward post-output
    maxSandwichCount: 5,           // 5 post-output frames for forward secrecy
    label:           'myapp:master-key-v1',  // Domain-separation label
});

CommonJS

const { THS } = require('ths-csprng');

(async () => {
    const entropy = await THS.random(32);
    console.log(entropy.toString('hex'));
})();

💎 Hardening Levels

| Level | Mode | What it captures | | :--- | :--- | :--- | | 0 | Benchmark only | A single process.hrtime.bigint() sample. No OS reads, no entropy mixing. Do not use in production. | | 1 | Process metrics | 17 process metrics: nanosecond hrtime, uptime, user/system CPU time, minor/major page faults, voluntary/involuntary context switches, V8 heap (used/total/external/arrayBuffers), RSS, performance.now(), the module-load anchor, wall-clock time, and the frame index. No OS reads. | | 2 | OS-mixed (recommended default) | Level 1 metrics + 128 bytes of fresh OS entropy per frame, all hashed through SHA3-512. A second hrtime sample brackets the OS read, encoding syscall latency as entropy. Effective even in VMs with a stale kernel pool. | | 3 | Memory-hardened | Level 2 + native crypto.argon2id on frames with index < memoryH. Each brute-force candidate requires mem KiB of RAM — GPU farms are bottlenecked by memory bandwidth, not compute. Gate with a small memoryH to limit latency impact. |


⚙️ Full Configuration Options

const bytes = await THS.random(32, {
    // ── Core ────────────────────────────────────────────────────────────────
    layers:           64,          // Sequential entropy frames (2–32768)
    harden:           2,           // Snapshot hardening level (0–3)
    trng:             false,       // Read OS bytes from /dev/urandom directly
    buffer:           true,        // true → Buffer, false → Uint8Array

    // ── Forward Secrecy (Sandwich Mode) ─────────────────────────────────────
    sandwichMode:     true,        // Ratchet entropy storage with post-output frames
    maxSandwichCount: 3,           // Post-output frames to fold in (1–32768)

    // ── Argon2id (harden: 3 only) ────────────────────────────────────────────
    memoryH:          1,           // Frame indices [0, memoryH) receive argon2id
    mem:              16384,       // memoryCost in KiB (≥ 1024; default 16 MiB)
    passes:           3,           // timeCost / iteration count
    parallelism:      4,           // Degree of parallelism

    // ── Domain Separation ────────────────────────────────────────────────────
    label:            'THS-v2',    // KMAC256 personalisation string; change per key type
});

🧬 Advanced API

Streaming Frames

streamFrames yields a continuous stream of hardened random frames without pre-committing to a fixed layer count. Each yield is an async suspension point that introduces real scheduler-driven jitter. The seed is ratcheted forward using both the emitted bytes and the hrtime at which the consumer resumed the generator — future output depends on when prior output was consumed.

The stream maintains its own seed, entirely separate from the cross-call entropy storage used by random(). Streaming and non-streaming uses cannot interfere with each other's forward-secrecy properties.

import THS from 'ths-csprng';

let count = 0;
for await (const frame of THS.streamFrames(32, { harden: 2, label: 'myapp:session-tokens' })) {
    issueSessionToken(frame);
    if (++count >= 1000) break;  // generator runs indefinitely until stopped
}

streamFrames accepts the same entropy options as random() (harden, memoryH, mem, passes, parallelism, trng, label).

randomBytes Drop-in Replacement

import { randomBytes } from 'ths-csprng';

// Synchronous — WebCrypto getRandomValues, no THS overhead
const nonce = randomBytes(16);                         // → Uint8Array
const buf   = Buffer.from(randomBytes(16));            // safely wrapped

// Asynchronous — full THS sequential frame movie
const key   = await randomBytes(32, true);             // isHardenRNG=true
const key2  = await randomBytes(32, true, { harden: 3, mem: 32768 });

Convenience Aliases

await THS.rand(32);          // alias for THS.random()
await THS.rnd(32);           // alias for THS.random()

Raw OS Entropy

// Raw OS bytes with no THS mixing.
// Caller is responsible for zeroing the buffer when done.
const seed = await THS.raw(32);          // /dev/urandom (default: trng=true)
const seed2 = await THS.raw(32, false);  // crypto.randomBytes

Synchronous WebCrypto Fill

// Routes to window.crypto in browsers, node:crypto webcrypto in Node.js.
// No THS mixing — use when synchronous operation is required.
const u32 = THS.fillRandom(new Uint32Array(8));
const u8  = THS.fillRandom(new Uint8Array(32));

🔬 Design: The Sequential Movie

Entropy is derived by observing a sequential movie of system state, not a single snapshot:

seed ──► frame₀ ──► jitter ──► frame₁ ──► jitter ──► … ──► frameₙ
              ↓                     ↓                     ↓
          KMAC256 update per frame, interleaved with OS bytes

Each frame captures a unique time-slice of CPU jitter, scheduler noise, memory pressure, and GC artifacts. The frame loop is always sequential — never Promise.all — so the inter-frame timing spread is preserved as entropy. Concurrent scheduling would collapse the movie into a near-simultaneous snapshot, discarding the temporal structure entirely.

The cross-call entropy storage carries entropy across invocations. It is re-mixed with fresh OS bytes at the start of every random() call (preventing it from becoming a closed, self-referential loop) and ratcheted forward with post-output frames after each call (forward secrecy).


Use Cases

| Use case | Recommended call | | :--- | :--- | | Nonces, IVs, UUIDs, session tokens | randomBytes(len) or THS.fillRandom(arr) — synchronous, zero overhead | | Application-layer session keys | THS.random(32) — default options (Level 2, 64 frames) | | Long-lived master keys / signing keys | THS.random(64, { harden: 3, mem: 65536, layers: 256 }) | | High-volume token streams | THS.streamFrames(32) — continuous, forward-secret, per-frame ratchet | | Bootstrapping a separate DRBG | THS.raw(64) — raw OS bytes, caller manages mixing | | Browser / WebCrypto contexts | THS.fillRandom(new Uint8Array(n)) — routes to window.crypto |


[!NOTE]

Hardening Level 3 is computationally expensive by design. It is intended for long-term master keys or seeds where latency is acceptable and the threat model includes state-level adversaries. For high-frequency generation (UUIDs, nonces, IVs), use Level 0 or Level 1, or the synchronous randomBytes / fillRandom path.


🛠️ Development & Verification

npm run check        # test + verify checksums
npm run build        # minify + generate docs + checksums
npm test

The project uses:

  • terser for minification
  • SHA-256 checksums for all built files
  • @noble/hashes for KMAC256 (audited, zero-dependency)
  • Native node:crypto argon2id (Node.js 24+)
  • Strict mode + comprehensive test suite

⭐ Contributor(s)

License

Licensed under the Apache License, Version 2.0 — a permissive license with an explicit grant of patent rights and protection against contributor liability.

Copyright © 2026 Aries Harbinger. See the LICENSE file for full details.

🔗 Links