Only the latest minor release line receives security fixes. Older lines are not patched.
Please report security vulnerabilities to security@theqrl.org.
Do not open public issues for security vulnerabilities. We follow coordinated disclosure: please give us reasonable time to investigate and ship a fix before any public disclosure.
@theqrl/qrl-cryptography is a thin TypeScript layer over audited primitives. The supplied modules are:
| Module | Primitive | Underlying implementation |
|---|---|---|
keccak |
Keccak-224 / -256 / -384 / -512 | @noble/hashes |
argon2id |
Argon2id KDF | @noble/hashes |
random |
CSPRNG | WebCrypto getRandomValues |
aes |
AES-256-GCM | WebCrypto (SubtleCrypto) |
ml_dsa87 |
ML-DSA-87 (FIPS 204) | @theqrl/mldsa87 |
Bugs in the underlying implementations should be reported upstream; this policy covers the wrapper code, parameter handling, encoding, and packaging.
- Prefer the misuse-resistant
seal/openpair:sealdraws a fresh 12-byte IV from the CSPRNG on every call and prepends it to the ciphertext, so the IV-reuse footgun below cannot be hit through that API. The rawencrypt/decryptpair remains available for callers who must manage the IV themselves. - For the raw
encrypt/decryptpair, the IV (12 bytes) must be unique per (key, plaintext) pair. Reusing an IV with the same key destroys confidentiality and authenticity. - When using the raw pair, callers must source IVs from a CSPRNG (e.g.
random.getRandomBytesSync).sealdoes this for you. - AES-GCM has a hard limit of ~2³² messages per key before nonce-collision probability becomes non-negligible — rotate keys for high-volume use.
- Defaults must be tuned for the target environment. The library accepts caller-supplied
t(iterations),m(memory),p(parallelism), and salt; weak parameters produce weak hashes. - Salts must be ≥16 bytes and unique per password; reusing salts undermines the KDF.
- Backed by the platform WebCrypto
crypto.getRandomValuesin both browsers and Node. Where no WebCrypto is available, calls throw — the library does not silently fall back to a weaker PRNG. - Requests are chunked at the 64 KiB per-call WebCrypto quota, so any size up to 2³² − 1 bytes works.
- Requests of ≥16 bytes throw
getRandomValues returned all zerosif the platform RNG returns all zeros (p = 2⁻¹²⁸ from a healthy RNG). This is a tripwire for catastrophically broken platform RNGs, not an entropy measurement.
- The module exposes the four Keccak variants (keccak-224/-256/-384/-512) only — not FIPS-padded SHA-3 or the SHAKE XOFs.
- These are unkeyed hashes, not MACs. Do not use them for authentication without HMAC or a keyed construction.
- Post-quantum digital signature scheme (FIPS 204), NIST Level 5.
- Public key 2,592 bytes, secret key 4,896 bytes, signature 4,627 bytes.
- Signing is hedged by default (FIPS 204 §3.4): fresh CSPRNG randomness enters each signature's nonce, frustrating fault-injection attacks against deterministic signing. Deterministic signing (
randomizedSigning: false) is an explicit opt-in for byte-reproducible use cases only. - Seeds are secret keys: anything that can regenerate the keypair has the same power as the secret key. When
keygen()draws its own seed, the wrapper wipes that buffer (best-effort) after key generation. When the caller supplies a seed, the caller owns its lifecycle and should wipe it after use. - Zeroization in JavaScript is best-effort only: the runtime may hold copies (GC, JIT, swap) that no library can erase. Returned key buffers (
publicKey,secretKey) are caller-owned; callers handling long-lived keys should overwrite them when done and minimize copies.
- ML-DSA-87 signing is not constant-time. The underlying
@theqrl/mldsa87documents this candidly: rejection sampling is inherently key-dependent and JavaScript arithmetic carries no constant-time guarantee (measured cross-key spread is documented in that package's SECURITY.md). Verification uses a constant-time challenge comparison. Deployments exposing a signing oracle should rate-limit and prefer hedged signing; for strict constant-time requirements use go-qrllib. - AES-256-GCM is delegated to WebCrypto, which uses platform-provided constant-time implementations on supported hardware (AES-NI / ARMv8 Crypto Extensions). On platforms lacking hardware AES, software fallbacks may not be constant-time.
- The wrapper code in this package contains no secret-dependent branches or table lookups, but auditors should verify before deploying in side-channel-sensitive environments.
The CommonJS build (dist/cjs) vendors a frozen copy of @noble/hashes, because noble is ESM-only and CJS consumers must be able to require() this package. Consequences:
- ESM consumers (
dist/mjs) resolve@noble/hashesthrough npm normally and see upstream patches via their own dependency tree. - CJS consumers run the snapshot bundled at our build time, invisible to their
npm audit. An upstream@noble/hashessecurity fix reaches CJS consumers only through a new@theqrl/qrl-cryptographyrelease. - The patch playbook is therefore: upstream noble fix → bump the pinned
@noble/hasheshere →npm run build(re-vendorsdist/cjs) → land asfix:so semantic-release ships a patch the same day. @theqrl/mldsa87is not bundled — it is resolved through npm by both build flavours and patches flow normally.
Publishing uses npm trusted publishing (OIDC): no long-lived npm token exists; the publish is gated by the npm-publish GitHub environment and runs only after CI passes. Each release ships:
- npm provenance attesting the GitHub Actions build (generated automatically under trusted publishing)
- SBOMs in SPDX and CycloneDX format, attached to the GitHub release
- SHA-256 / SHA-512 checksums of
package.json,package-lock.json, and source files - GitHub build provenance attestations (
actions/attest-build-provenance) for checksums and SBOMs - SLSA Level 3 provenance (
slsa-framework/slsa-github-generator) attached to the release asprovenance.intoto.jsonl
To verify a release, see the artefacts on the corresponding GitHub release page and use gh attestation verify or slsa-verifier.
Advisory gating. CI gates releases on the shipped (runtime) dependency tree only (npm audit --omit=dev): a real advisory in code that reaches consumers blocks the build. Development- and build-only advisories carry no consumer-facing risk (nothing under devDependencies is published — files ships only dist and src), so they are tracked and remediated out of band by Dependabot (weekly version updates plus GitHub Dependabot security updates) rather than gating consumer-safe releases.
- Report received → acknowledgement within 72 hours
- Triage and reproduction → within 7 days
- Fix developed and reviewed → typically within 30 days for high/critical
- Coordinated release with reporter
- Public advisory via GitHub Security Advisories