
Security News
Ruby's Bundler 4.0.18 Extends Cooldown to bundle lock and bundle cache
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.
@quantakrypto/core
Advanced tools
Shared post-quantum readiness library: crypto detectors, vulnerable-dependency database, inventory + SARIF reporting. Zero runtime dependencies.
Shared post-quantum readiness library for the quantakrypto toolchain. It finds classical, non-quantum-safe asymmetric cryptography in a codebase — inline crypto calls, embedded keys/certificates, and quantum-vulnerable npm dependencies — and turns the results into an inventory, a readiness score, and machine- or human-readable reports.
@quantakrypto/qscan (CLI), the MCP server, and the GitHub Action.Shor's algorithm breaks RSA, (EC)DH, ECDSA, DSA and EdDSA. Two threats follow:
hndl: true flag.RS*/PS*/ES*/EdDSA)
can be forged by a quantum attacker. These are hndl: false but still high
severity.@quantakrypto/core flags both, and points each finding at a NIST PQC replacement
(ML-KEM / FIPS 203, ML-DSA / FIPS 204, SLH-DSA / FIPS 205, hybrid
X25519MLKEM768).
npm install @quantakrypto/core
import { scan, formatSummary, toSarif, toJson } from "@quantakrypto/core";
const result = await scan({ root: "./", onFile: (f) => process.stderr.write(`scanning ${f}\n`) });
console.log(formatSummary(result, { color: true })); // human report
const sarif = toSarif(result); // SARIF 2.1.0 for CI
const json = toJson(result); // structured object
scan(options: ScanOptions): Promise<ScanResult>Recursively scans a directory (or a single file) and returns findings, an inventory, file count, timing, and the tool version.
interface ScanOptions {
root: string; // directory or single file
include?: string[]; // restrict the walk to matching paths (substring/prefix)
exclude?: string[]; // extra exclude patterns (substring/prefix)
noDefaultIgnores?: boolean; // disable node_modules/.git/dist/… ignores
source?: boolean; // scan source files (default true)
dependencies?: boolean; // scan manifests/lockfiles across 7 ecosystems (default true)
config?: boolean; // scan PEM/TLS/cert config (default true)
maxFileSize?: number; // bytes; default 2 MiB (manifests are exempt)
scanMinified?: boolean; // scan minified/generated files (default false: skip them)
files?: string[]; // explicit relative file list (incremental scans)
detectors?: Detector[]; // override/extend the built-in detector set
onFile?: (file: string) => void; // progress callback (relative POSIX path)
}
include is now wired into the walker: when set, only paths matching one
of the patterns are scanned.files bypasses the directory walk entirely and scans the given relative
paths (used for incremental / changed-files scans — pair it with
changedFiles). Binary and missing files are skipped; manifests over the size
cap are still read.scanMinified is off by default; machine-minified / generated / bundled
content (*.bundle.js, *.generated.ts, long single-line files, …) is
skipped for speed unless you opt in.scanParallel(options): Promise<ScanResult>A worker-thread pool over the file list with a deterministic result merge
(byte-identical to scan). Falls back automatically to the in-process scan
for small workloads (below ~200 files / ~2 MiB) and whenever worker_threads
is unavailable. Extra options:
interface ParallelScanOptions extends ScanOptions {
concurrency?: number; // worker count; default os.availableParallelism()
parallelThresholdBytes?: number; // serial below this total size (default 2 MiB)
parallelFileThreshold?: number; // serial below this file count (default 200)
chunkBytes?: number; // target bytes per worker chunk (default 4 MiB)
}
changedFiles(root, since?): Promise<string[]>Returns relative POSIX paths that changed in a git work tree (uncommitted +
untracked, plus a since ref/range when provided). Tolerant of non-git
directories — returns []. Feed the result into ScanOptions.files for
incremental scans.
const files = await changedFiles(".", "origin/main...HEAD");
const result = await scan({ root: ".", files });
walkFiles(root, options?): AsyncGenerator<string>Recursive async generator yielding scannable text files as relative POSIX
paths. Skips default-ignored directories (node_modules, .git, dist,
build, .next, out, coverage, vendor, .turbo, .cache), honours
exclude patterns (substring or path-prefix), skips obvious binaries by
extension, and skips files larger than maxFileSize (default 2 MiB). root
may be a single file.
for await (const file of walkFiles("./src", { exclude: ["legacy"] })) {
console.log(file); // e.g. "components/Button.tsx"
}
detectors: Detector[] / defaultRegistry / DetectorRegistryThe built-in, pure detectors. Each declares a scope ("source" | "config"),
a language, appliesTo(filePath), and detect({ file, content }). scan()
drives the source/config scope toggles from the detector's declared scope
(not from ruleId prefixes). Detector families:
| Detector | Scope | Catches |
|---|---|---|
node-crypto | source | generateKeyPair(Sync)('rsa'|'ec'|'dsa'|'dh'|'x25519'|'x448'|'ed25519'|'ed448'), createSign/createVerify, one-shot crypto.sign/verify, createDiffieHellman, getDiffieHellman('modpN'), createECDH, publicEncrypt/privateDecrypt, diffieHellman |
webcrypto | source | subtle.{generateKey,importKey,deriveKey,deriveBits,sign,verify,…} with RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDH, ECDSA |
crypto-libs | source | node-forge (pki.rsa.generateKeyPair, ed25519), elliptic (new EC(...)), jsrsasign, node-rsa, direct secp256k1.* usage |
jwt-jose | source | JWT/JOSE alg strings (RS/PS/ES*, EdDSA) and ECDH-ES* key agreement (HNDL) |
tls-config | config | minVersion/secureProtocol: 'TLSv1'/'TLSv1.1', rejectUnauthorized: false, weak ciphers (RC4/DES/3DES/MD5/NULL/EXPORT) |
pem-material | config | PEM keys/certs in any file: RSA/EC/DSA/PKCS#8/OPENSSH/PGP PRIVATE KEY, PGP MESSAGE, CERTIFICATE |
ssh-cert | config | SSH public keys (ssh-rsa, ssh-ed25519, ecdsa-sha2-*) and X.509 certificate signature algorithms (sha256WithRSAEncryption, ecdsa-with-SHA256, …) |
weak-hash-signature | config | SHA-1/MD5 in a digital-signature or X.509 certificate algorithm (SHA1withRSA, sha1WithRSAEncryption + OID, openssl -sha1 in a cert/sign command) |
pqc-parameter | config | Post-quantum KEM parameter checks: pre-standard round-3 Kyber claimed as FIPS 203 ML-KEM (pqc-prestandard-kem), and an ML-KEM/Kyber byte size that names a different parameter set than the code advertises (pqc-parameter-mismatch) |
The rows above cover JavaScript/TypeScript plus the language-agnostic PEM/SSH/TLS surfaces. Thirteen further language packs apply the same RSA/EC/DSA/DH/Ed25519 detection to other ecosystems — including a smart-contract pack (Solidity/Move/ Cairo on-chain signature verification) — each a single umbrella detector:
| Detector | Scope | Language |
|---|---|---|
python-crypto | source | Python (cryptography, PyCryptodome, pyca) |
go-crypto | source | Go (crypto/rsa, crypto/ecdsa, crypto/ecdh, crypto/ed25519, …) |
java-crypto | source | Java/Kotlin (JCA KeyPairGenerator/Signature/KeyAgreement, BouncyCastle) |
csharp-crypto | source | C#/.NET (RSA, ECDsa, ECDiffieHellman, DSA) |
rust-crypto | source | Rust (rsa, p256/k256 ECDSA, x25519-dalek, ed25519-dalek) |
ruby-crypto | source | Ruby (OpenSSL::PKey::{RSA,EC,DSA,DH}) |
php-crypto | source | PHP (openssl_pkey_new, phpseclib RSA/EC/DSA) |
elixir-crypto | source | Elixir/Erlang (:crypto, :public_key RSA/ECDSA/EdDSA) |
c-crypto | source | C/C++ (OpenSSL EVP_PKEY_* / RSA_* / EC_* / DSA_* / DH_*) |
swift-crypto | source | Swift (CryptoKit + Apple Security SecKey*) |
objc-crypto | source | Objective-C (.m/.mm, Apple Security SecKey*) |
dart-crypto | source | Dart/Flutter (pointycastle, cryptography — RSA/EC/Ed25519/X25519) |
defaultRegistry is a DetectorRegistry preloaded with these built-ins. The
registry is the plugin point:
import { DetectorRegistry, defaultRegistry, scan } from "@quantakrypto/core";
const registry = defaultRegistry.clone().register(myDetector);
const result = await scan({ root: ".", detectors: registry.all() });
DetectorRegistry exposes register(d), get(id), has(id), all() and
clone(). Ids must be unique (duplicate registration throws).
Public plugin surface vs. internal helpers. The stable, frozen way to add a detector from outside this package is the
Detectorinterface plus thescan({ detectors })/DetectorRegistryhook shown above — build your rule metadata with the publicRuleMetatype and returnFinding[]. The lexical conveniences the in-repo detectors use (findingFromRule,eachMatch, the comment maskers,hasExtension, …) are internal and intentionally not part of the frozen API: they churn with the scanner internals. The step-by-step guide below is for in-repo contributors (it editsregistry.ts,detect-utils.ts,comments.ts), who can import those helpers directly.
src/detectors/<lang>.ts exporting one or more Detectors. Set
language (a member of DetectorLanguage in types.ts — add a new one for a new
language pack), scope ("source" | "config"), an appliesTo(path) extension
check, and a pure detect({ file, content }) returning Finding[]. Build findings
with findingFromRule(ruleMeta, at, overrides?) (not makeFinding) so every rule's
metadata lives once in its RuleMeta catalog entry.<LANG>_EXTENSIONS in detect-utils.ts next to the other packs (do not
inline the list in the detector) and gate appliesTo on it.ANALYZABLE_SOURCE_EXTENSIONS and the
name to ANALYZABLE_LANGUAGES_LABEL (detect-utils.ts) so analyzedFiles/coverage
count it; and add them to the comment table in comments.ts (C_LIKE for
//+/* */, HASH_LIKE for #) so commented-out code is suppressed. A config
detector instead masks its own comment lines with maskCommentLines /
maskBlockComments.builtinDetectors (registry.ts) — the single source of truth the
default registry and public detectors export are built from. (Ad-hoc: pass
{ detectors: [...] } to scan.) No edit to scan() is needed; scope is honoured
from the detector's declared scope.test/benchmark/). For a new dependency
ecosystem, also extend VulnerableDependency.ecosystem + a matcher in dependencies.ts.vulnerableDependencies: VulnerableDependency[]Curated database (77 entries) of packages whose purpose is classical
asymmetric crypto, spanning seven ecosystems — npm, PyPI, Cargo, Go modules,
Maven, RubyGems, and NuGet (VulnerableDependency.ecosystem). The npm subset includes
node-forge, elliptic, jsrsasign, node-rsa, ursa, sshpk,
jsonwebtoken, jose, jws, eccrypto, secp256k1, tweetnacl, ed25519,
@noble/curves, @noble/secp256k1, @noble/ed25519, paseto, bcrypto,
ecpair, keypair. scan() matches these against each ecosystem's manifests
and lockfiles — package.json / package-lock.json / yarn.lock /
pnpm-lock.yaml, requirements.txt / pyproject.toml / Pipfile, Cargo.toml,
go.mod, pom.xml / build.gradle, Gemfile / *.gemspec, *.csproj /
packages.config / directory.packages.props — and emits
category: "dependency" findings located at the manifest.
scanAdvisories(root, opts?) / checkProvenance(root, opts?) (opt-in, qscan --audit)Two supply-chain helpers layered on top of the crypto scan (opt-in because they shell out or make a network request):
scanAdvisories(root, opts?): Promise<{ findings; diagnostics }> — shells
out (execFile, bounded timeout + buffer, in a try/catch — the blessed
changed.ts pattern) to each present ecosystem's own audit tool
(cargo audit --json, pip-audit --format json, npm audit --json) and turns
its advisories into dep-advisory (category: "dependency") findings. A
missing tool (ENOENT) or any error degrades to a diagnostic string, never
throws. DEP_ADVISORY_RULE is the generic SARIF catalog entry.checkProvenance(root, opts?): Promise<{ findings; diagnostics }> — reads
the root manifest's declared repository (package.json / Cargo.toml /
pyproject.toml). No repository → provenance-repo-missing (info). With
opts.network and an injected head requester, a declared URL that 404s or
does not resolve → provenance-repo-unresolved (medium). Core stays offline
(ADR-0005): the actual node:https HEAD request is injected by the caller
(qScan). PROVENANCE_RULES are the generic SARIF catalog entries.buildInventory(findings: Finding[]): CryptoInventoryAggregates findings into per-algorithm / per-category / per-severity counts, the
HNDL count, and a readinessScore (0–100, 100 = no classical asymmetric crypto
found). The score starts at 100 and subtracts severity-weighted penalties with
diminishing returns per severity bucket, clamped to [0, 100].
toSarif(result): SarifLog — valid SARIF 2.1.0 ($schema, version,
runs[0].tool.driver { name: "qScan", informationUri, version, rules[] },
results[] with ruleId, level (error/warning/note), message.text,
locations[].physicalLocation with artifactLocation.uri and
region.startLine/startColumn).toJson(result): Record<string, unknown> — clean, JSON-serialisable object.formatSummary(result, { color? }): string — human report with the readiness
score, severity/algorithm breakdown, top findings, and an HNDL note. Colour is
off by default and uses raw ANSI codes when enabled.remediationFor(algorithm): Remediation | undefined / remediationForTier(algorithm, tier?)remediationFor returns the recommended PQC replacement for a classical family.
remediationForTier adds a security tier: "category-3" (default,
commercial — ML-KEM-768 / ML-DSA-65) or "category-5" (CNSA 2.0 / long-lived —
ML-KEM-1024 / ML-DSA-87).
remediationFor("ECDH");
// { algorithm: "ECDH", recommendation: "hybrid X25519MLKEM768 (ML-KEM-768)", detail: … }
remediationForTier("ECDH", "category-5");
// recommendation mentions ML-KEM-1024; detail cites CNSA 2.0 (2030/2033 milestones)
STATEFUL_HBS_NOTE / statefulHbsApplies(algorithm) surface the SP 800-208
stateful hash-based signatures (LMS / XMSS / HSS) guidance for firmware / boot
signing (stateful — use only with rigorous state management).
fingerprintFinding, baselineFromFindings, applyBaseline, loadBaseline, saveBaseline)The single canonical baseline scheme shared by qScan and the Action. A baseline
is { version, fingerprints: string[] }. A fingerprint is the SHA-256 hex of
ruleId|file|normalizedSnippet — line-insensitive (survives line shifts)
and snippet-whitespace-normalized (survives reformatting).
import { saveBaseline, loadBaseline, applyBaseline } from "@quantakrypto/core";
await saveBaseline(".quantakrypto-baseline.json", result.findings); // write
const baseline = await loadBaseline(".quantakrypto-baseline.json"); // read (tolerant)
const { newFindings, suppressed } = applyBaseline(result.findings, baseline);
toCbom(result): CycloneDxBomA CycloneDX 1.6 cryptographic bill of materials (CBOM): one
cryptographic-asset component per distinct (assetType, algorithm, discriminator),
with occurrence evidence and quantumVulnerable / harvestNowDecryptLater flags.
Each finding is classified into its proper CycloneDX assetType — algorithm
(crypto usage), certificate (X.509), related-crypto-material (private/public
key material), or protocol (TLS). Deterministic output.
toOpenVex(result, opts?): OpenVexDocumentAn OpenVEX 0.2.0 document: one statement per rule (a synthetic QK-<ruleId>
vulnerability, since PQC findings have no CVE), every affected file:line product,
status: "affected", the rule's remediation as action_statement, and any
--triage verdict in status_notes. Deterministic output; feeds VEX pipelines.
buildReadinessReport / signReadinessReport / verifyReadinessReportThe ISO/IEC 27001 A.8.24 readiness report (see docs/compliance/iso27001-a8.24-evidence.md):
buildReadinessReport(result, opts?) — bundles the scan result, inventory, CBOM,
and a deterministic attestation.contentHash (excludes the volatile scan time).signReadinessReport(report, { signer?, timestamper? }) — orchestrates an EXTERNAL
signer/timestamper over the contentHash (ADR-0004; the tool implements no crypto).verifyReadinessReport(report) — recomputes the content hash to detect tampering
with any attested field, returning { valid, computedHash, claimedHash, reason? }.Every detector sets a Finding.cwe (CWE-327 broken crypto, CWE-326 weak
strength, CWE-295 cert validation, CWE-798 hardcoded key). toSarif maps it
into rules[].properties, result taxa, and a run-level CWE taxonomies
component; toJson includes it. Constants are exported (CWE_BROKEN_CRYPTO, …).
VERSION: stringTool version surfaced in reports (kept in sync with package.json).
| Export | Kind | Summary |
|---|---|---|
scan | fn | Scan a dir / file / explicit file list → ScanResult |
scanParallel | fn | Worker-pool scan with deterministic merge + serial fallback |
changedFiles | fn | Git-aware changed-files list for incremental scans |
detectFile | fn | Pure per-file detect (used by workers / tests) |
compareFindings | fn | Stable finding comparator (file → line → ruleId) |
walkFiles, isBinaryPath, looksMinified | fn | Walker + file-classification helpers |
detectors | const | Built-in detector array (mirrors defaultRegistry.all()) |
DetectorRegistry, defaultRegistry | class/const | Detector plugin point |
buildInventory | fn | Aggregate findings → CryptoInventory |
vulnerableDependencies | const | Curated quantum-vulnerable dependency DB (7 ecosystems) |
toSarif, toJson, formatSummary | fn | Reporters (SARIF 2.1.0 / JSON / human) |
toCbom | fn | CycloneDX 1.6 CBOM export (assetType-classified) |
toOpenVex | fn | OpenVEX 0.2.0 export |
buildReadinessReport, signReadinessReport, verifyReadinessReport | fn | ISO A.8.24 evidence chain (build / sign / verify) |
remediationFor, remediationForTier, TIER_PARAMS | fn/const | PQC remediation (family + CNSA tier) |
STATEFUL_HBS_NOTE, statefulHbsApplies | const/fn | SP 800-208 LMS/XMSS guidance |
fingerprintFinding, baselineFromFindings, applyBaseline, loadBaseline, saveBaseline, BASELINE_VERSION | fn/const | Canonical baseline |
verifyFix, languageToExtension | fn | Snippet-level fix verification (crypto finding removed?) + language→extension map |
buildContext, renderPreflight | fn | Redacted source-context builder + preflight preview (secrets stripped) |
TRIAGE_RUBRIC, TRIAGE_VERDICT_SCHEMA, buildTriageRequest | const/fn | Deterministic, offline triage-request bundle (rubric + verdict schema) |
REMEDIATE_RUBRIC, FIX_REQUEST_SCHEMA, buildRemediateRequest | const/fn | Deterministic, offline remediation-request bundle (rubric + fix schema) |
checkPatchPolicy | fn | Patch-policy gate — only files with findings + dependency manifests |
withWorktree | fn | Run a callback inside an ephemeral git worktree (isolates writes; no auto-merge) |
codemodRegistry, codemodFor, configToggleCodemod | const/fn | Deterministic codemod registry + per-finding lookup |
remediateFindings | fn | Remediation pipeline: propose → policy + verify gate → verified patches |
loadConfig, ConfigError, CONFIG_FILENAME | fn/const | quantakrypto.config.json loader |
AbortError, BudgetExceededError | class | Scan cancellation / work-budget overflow errors |
CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH, CWE_CERT_VALIDATION, CWE_HARDCODED_KEY, CWE_RISKY_PRIMITIVE | const | CWE identifiers |
VERSION | const | Tool version |
Types: Finding (now with optional cwe), ScanOptions/ParallelScanOptions,
ScanResult, Detector (now with scope/language), DetectorScope,
DetectorLanguage, Baseline, CycloneDxBom, CbomComponent, SecurityTier,
AlgorithmFamily (now includes X448), and the rest of the locked contract in
src/types.ts.
See src/types.ts for the locked contract. Highlights:
type Severity = "critical" | "high" | "medium" | "low" | "info";
type AlgorithmFamily =
| "RSA" | "ECDH" | "ECDSA" | "EdDSA"
| "DH" | "DSA" | "X25519" | "X448" | "ECIES" | "unknown";
type FindingCategory =
| "kem" | "key-exchange" | "signature"
| "tls" | "certificate" | "dependency" | "hash" | "rng";
interface Finding {
ruleId: string;
title: string;
category: FindingCategory;
severity: Severity;
confidence: "high" | "medium" | "low";
algorithm?: AlgorithmFamily;
hndl: boolean; // harvest-now-decrypt-later exposure
message: string;
remediation?: string;
cwe?: string; // e.g. "CWE-327"
location: { file: string; line: number; column?: number; endLine?: number; snippet?: string };
}
interface CryptoInventory {
byAlgorithm: Partial<Record<AlgorithmFamily, number>>;
byCategory: Partial<Record<FindingCategory, number>>;
bySeverity: Record<Severity, number>;
hndlCount: number;
readinessScore: number; // 0–100
}
A runnable example lives in examples/scan-example.mjs:
node examples/scan-example.mjs ./path/to/project
npm run build # tsc -b
npm test # node --import tsx --test test/*.test.ts
Tests use only node:test + node:assert. The package has zero runtime
dependencies.
Apache-2.0
Questions, commercial support, or post-quantum readiness training for your team — visit quantakrypto.com or email hello@quantakrypto.com.
FAQs
Shared post-quantum readiness library: crypto detectors, vulnerable-dependency database, inventory + SARIF reporting. Zero runtime dependencies.
The npm package @quantakrypto/core receives a total of 97 weekly downloads. As such, @quantakrypto/core popularity was classified as not popular.
We found that @quantakrypto/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.

Company News
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.