Skip to content

Latest commit

 

History

History
1417 lines (1223 loc) · 86.9 KB

File metadata and controls

1417 lines (1223 loc) · 86.9 KB

Changelog

All notable changes to this project are documented here. The format is based on Keep a Changelog, and the project aims to follow Semantic Versioning from 1.0.0.

[0.12.0] - 2026-08-14

Minor: additions only. No existing shape changes, no exit code moves, and the Action stays on @v1 because nothing here is breaking.

Added - a real crypto inventory, not a list of what is wrong

What the tool called an inventory was a findings list under another name. buildInventory counted findings, toCbom iterated findings, and all 47 detectors fire only on cryptography that is WRONG. ML-KEM appeared in the entire codebase solely inside remediation strings.

So a repository that had migrated correctly was indistinguishable from one that uses no cryptography at all: both reported an empty inventory and 100/100. You could prove nothing was broken; you could not prove you had done the work.

inventory.assets now carries every algorithm found, grouped with a count and a few example sites. The classical half is derived from the findings, which already carry algorithm, file and line, so it can never disagree with the findings list. The other half is a new pass over PQC families, hybrids, the pre-standard CRYSTALS names (kept separate, because a Kyber768 build is not an ML-KEM-768 build) and the symmetric and hash primitives.

Symmetric is classified not-quantum-relevant rather than quantum-safe. Grover halves the effective key length, which matters at 128 bits and does not break 256; calling AES quantum-safe beside ML-KEM would flatten a real distinction.

Detection is lexical, like the rest of the engine, so a mention in prose counts. Use --ignore for content that describes cryptography without using it.

Added - findings carry their baseline fingerprint

The same id qscan --write-baseline writes, so CI and any consumer hold one identity for a finding rather than two that agree until one is edited. It excludes line and column, so an edit that shifts code down a file does not resurface a finding as new.

Fixed - the JSON reporter dropped the inventory

It names its inventory fields explicitly, so assets was computed and then silently discarded. The third field to be lost at a hand-written boundary, after the remediation and the fingerprint.

Fixed - an algorithm named in key position is not a use of it

.cargo_vcs_info.json carries {"sha1": "<commit>"} for the git revision, and the first inventory run read that as "this project uses SHA-1". A name in key position is metadata now. The guard initially also dropped ml_kem_768::keypair(), because Rust's path separator is a colon too; the tests caught it.

[0.11.0] - 2026-08-10

Minor: user-facing fixes to the Action, and the runtime it declares. Nothing existing changes shape, and no exit code moves.

Fixed - a URL in the Action's probe-target was refused, despite the docs

Both action.yml files have always said "A full URL is accepted and reduced to its host". It was not. normalizeProbeTarget existed, was exported, was documented, and had its own passing tests, and nothing ever called it: it was left behind when the target was routed through qProbe's own parser to close an attestation bypass. So probe-target: "https://example.com" reached qProbe raw and was refused, and the run reported as a generic failure.

It is now called, and narrowed so that calling it is safe. Only a string that already carries a scheme is treated as a URL, and only when its authority has no userinfo; everything else goes to parseTarget unchanged. That keeps our-api.example.com@evil.test refused rather than silently resolving to evil.test under a manufactured i-own-this.

Fixed - qProbe reported the wrong tool version in every JSON report and CBOM

packages/qprobe/src/version.ts carried 0.7.0 while the package was at 0.10.0. The only thing keeping it honest was a comment. Every qProbe JSON report and endpoint CBOM since 0.8.0 has carried a toolVersion that lies about the build, which is evidence data. Fixed, and the lockstep test core has always had is now on qProbe too.

Added - the result payload carries each finding's remediation

Every detector produces a remediation, explicitly or derived from the algorithm family, and it rides in the JSON report and the SARIF help text. It was not in the payload posted to quantakrypto.com, so the platform received a list of problems and no next step while the tool that found them knew one. The unrunnable-conformance finding splits accordingly: message says what happened, remediation says what to do.

Changed - the Action runs on Node 24

Both action.yml files declared using: "node20". GitHub deprecated that runtime and already force-runs node20 actions on 24, warning on every run. Declaring what already happens removes a scheduled break. The re-bundled dist/index.js is byte-identical, so nothing needed downleveling. CI gains a node 24 leg, which found a real bug on its first run: the setOutput fallback wrote ::set-output, a workflow command GitHub removed in 2023 and which it rejects, from unit tests onto the real runner's stdout. Whether the runner parsed it came down to output interleaving.

Docs

The ready-to-copy examples/quantum-readiness.yml was syntactically broken (continue-on-error: true to code scanning) and nothing checked it; a new supply-chain gate now validates the examples and requires our own action to be pinned to a bare moving major. The Action README documents checks, ignore/include, and what the platform callback does. Every path example is .quantakrypto/, which docs/CONFIG.md now states as the convention. The short-lived v2 Action tag is deleted: checks defaults to scan, so it was never a breaking change and never earned a new major.

[0.10.0] - 2026-08-08

Minor: three additive features and two user-facing bug fixes. Nothing existing changes shape, and no exit code moves.

Fixed - the SARIF we produced was rejected by GitHub code scanning, in full

A result's taxa[] holds reportingDescriptorReference objects directly. We wrapped them in target, which is relationship shape, and GitHub rejected the entire file: "taxa[0] is not allowed to have the additional property target". Every consumer following the workflow we document - action writes SARIF, upload-sarif publishes it - had been getting nothing in their Security tab.

Two safeguards agreed with the bug and are fixed with it: validate-sarif checked that taxa was an array without looking inside it, and the unit test asserted the broken shape. Found by running our own scanner against our own repository for the first time.

Added - ignore and include inputs on the action

The CLI had both; the action had neither, so anything reachable only through the action could not exclude a path. Content, fixtures and docs that DESCRIBE cryptography match the detectors and become findings nobody wanted scanned. A baseline was the only workaround and it is the wrong tool: it records a finding as known debt, and a blog post mentioning RSA is not debt.

Added - action: one workflow runs any combination of scan, conformance and probe

The action takes a checks input (any subset of scan, conformance, probe) and owns reporting results back to quantakrypto.com. It replaces three separate workflow files that each shelled out to npx and turned the JSON into a result payload with inline jq.

That placement was the defect: the payload logic lived in repositories we do not control, so fixing it fixed nothing already committed. A conformance run whose implementation could not start was recorded as ~35 high-severity crypto defects, and correcting the jq only changed what NEW repositories would generate.

SemVer: minor (additive). checks defaults to scan, which is exactly what the action did before, so an existing workflow keeps working unchanged.

  • New inputs: checks, probe-target, i-own-this, conformance-impl, conformance-param.
  • qProbe and Sieve are imported rather than spawned, which removes the shell layer entirely: no quoting, no > file.json, no re-parsing a report already held as a typed object.
  • Probe targets go through qProbe's own parseTarget, so the refusals it enforces (CIDR, ranges, wildcards, lists, URLs, embedded credentials) apply on this path too. i-own-this is a required input, never manufactured by the action: the CLI makes an operator state it, and so does this.
  • Results are posted only to https://quantakrypto.com over https, redirects refused, with a 10s timeout, and only for a repository_dispatch. The callback token is masked from the job log.
  • A conformance run that could not execute is posted as failed, not as a verdict, so it stays out of badges and the posture series.

Fixed - qProbe: a URL target is refused as a URL, not as a CIDR block

qprobe --i-own-this https://example.com failed with refusing CIDR block "https://example.com" — qProbe probes one host at a time, not ranges. The slash check ran before anything else, so every URL was reported as a range sweep: an error describing a mistake the operator had not made, and one that gave no hint about the fix. SemVer: patch (message and classification only; nothing newly accepted, nothing newly refused).

URLs are still refused — a target is one named host — but now for the right reason, and the message names the host to use:

refusing URL "https://example.com/health" — qProbe takes a host, not a URL. Try: example.com
  • A slash is only reported as a CIDR block when it is actually a prefix length (10.0.0.0/24). A pasted path (example.com/blog) says so instead.
  • A target carrying credentials (mine.com@theirs.com) is refused explicitly, and the suggestion names the host it would really have connected to. --i-own-this is an ownership attestation, so a target that reads as one host and resolves to another cannot be accepted quietly.
  • The refusals that are security controls are unchanged: CIDR blocks, IP ranges, wildcards and lists still throw before any network I/O.

Fixed - Sieve: an implementation that cannot be run is no longer reported as a failing implementation

Pointing --impl at a command that does not exist produced a FAIL report with ~35 high-severity checks, each tagged with a bug class that was never exercised, and each detailing only SUT exited with code 1. That is a confident verdict about code that never executed, and it says nothing about how to fix it.

SemVer: minor (additive). SieveReport.overall gains a third value and two new symbols are exported; no existing field changes shape and no exit code moves.

  • New ERROR verdict. If the SUT never returned a single protocol response, the report carries one harness category and overall: "ERROR". ERROR outranks FAIL: if the implementation never ran, nothing else in the report is a statement about it. A SUT that starts and replies {"ok": false} is not an ERROR — it is alive and speaking the protocol, so the full battery runs — and neither is one that dies part-way, whose recorded failures are real. The signal is a response counter the runner already maintains, so this costs no extra requests on a healthy run.
  • The reported cause is now actionable. SutCrashError already captured the child's stderr, but every category discarded it and reported (err as Error).message. New exported describeSutError quotes the diagnostic part of the dump, anchoring on the first and last lines that name a failure — Node buries Error: Cannot find module between an internal loader frame and its version banner, Python puts the exception last. All 14 category call sites now report through it, so a run says Error: Cannot find module '/repo/my-impl.js' instead of exited with code 1.
  • Exports: describeSutError, HARNESS_CATEGORY, and the Verdict type.
  • CLI exit codes are unchanged: only PASS exits 0, so ERROR still exits 1 and existing CI gates behave as before. Read .overall to distinguish them.

[0.9.0] - 2026-08-05

Added - machine-readable mandate output + --policy composition

The --mandate compliance gate becomes CI-consumable, and an org cryptography policy can compose with it. SemVer: minor (additive features). Two exit-code behavior changes are called out under Changed below - read them before upgrading a --mandate pipeline.

  • Machine-readable mandate verdicts. The mandate evaluation is no longer confined to the human report. qscan --mandate --format json adds a top-level mandateMapping block; --format sarif carries the same under run.properties.mandate (so an uploaded SARIF surfaces it in code scanning); and --format evidence embeds a date-pinned, hashed mandateMapping in the ISO/IEC 27001 A.8.24 attestation - reproducible per commit per day and tamper-evident via the attestation content hash, mirroring policyMapping. The GitHub Action threads the same verdicts into the SARIF it uploads. New optional ReportOptions.mandate / ReadinessReportOptions.mandate and ReadinessReport.mandateMapping (all additive; no export renamed or removed).
  • --policy composes with --mandate. Pass the org crypto-policy file (the same one the evidence report's §4 verdicts use) alongside --mandate. Families the policy explicitly permits or is transitioning are annotated in every output (policyVerdict / acknowledged) and exempted from the early gate (--fail-now / --lead-months). A passed disallow deadline still fails regardless - an org cannot self-exempt from a dated regulatory prohibition, and prohibited always wins over permitted. Exposed on the Action as a new policy input.
  • Two forward-looking roadmap notes under docs/roadmap/: attestation as a procurement/underwriting primitive, and a harvest-tripwire (HNDL canary) research note.

Changed

  • Exit-code loosening (opt-in): with --policy and --mandate --fail-now/--lead-months, a family the org lists as permitted or inTransition no longer trips the early gate (it did in 0.8.0, which had no composition). This only affects runs that pass both flags; --mandate alone is unchanged. The Action side is opt-in (its policy input is new).
  • Exit-code tightening: the CLI now evaluates the mandate gate on pre-baseline findings (kept + suppressed), matching the GitHub Action. A --baseline accepts a finding for the severity gate but no longer waives a regulatory deadline, so a baselined finding past a disallow date now fails where it silently passed in 0.8.0. Closes a self-service deadline-waiver (--write-baseline then --baseline).
  • Pre-1.0 shape change: MandateEvaluation and MandateFindingVerdict gained required fields (policyName/acknowledged, and policyVerdict/acknowledged respectively). Runtime behavior for existing 3-arg evaluateMandates callers is identical; only TypeScript code that constructs one of these by hand needs the new fields.
  • --mandate cnsa-2.0 disallow date moved 2035 → 2033. CNSA 2.0 now encodes its OWN exclusive-use timeline (deprecate after 2030, disallow after 2033 — CNSA's general NSS milestone) from a new PQC_STANDARDS.cnsaTimeline, instead of borrowing NIST IR 8547's 2035. nist-ir-8547 is unchanged (2035). Behavior change: a --mandate cnsa-2.0 build with prohibited classical crypto now reaches violation (and, past 2033, fails) ~two years earlier than in 0.8.0.

Fixed

  • Evidence hash reproducibility. evaluateMandates now pins now to UTC midnight of its date before any arithmetic, so the monthsUntil / monthsUntilDisallow counters (and therefore the attested evidence hash) are identical for any two runs on the same day. Previously the counters were computed from the full scan timestamp while now was date-pinned, so two same-day runs diverged on ~7% of days (month-rounding boundaries).

[0.8.0] - 2026-07-31

(There is a v0.7.0 git tag but no separate 0.7.0 section: its changes were consolidated into this 0.8.0 entry — same convention as the 0.3 skip.)

Added - supply-chain checks (qscan --audit: dependency advisories, PQC parameter verification, provenance)

Three new supply-chain capabilities. SemVer: minor (additive API + CLI surface; the Finding / category / severity contract and every existing report format and exit code are unchanged). The Finding.category reuses the existing dependency (advisories, provenance) and kem (PQC parameter) categories - no new public type.

  • Dependency-advisory scanning (opt-in via --audit). New @quantakrypto/core scanAdvisories(root, opts) shells out - execFile, a timeout, a bounded maxBuffer, all in a try/catch (the blessed changed.ts pattern) - to each present ecosystem's own audit tool: Cargo.toml/Cargo.lock → cargo audit --json, requirements*.txt/pyproject.toml → pip-audit --format json, package-lock.json → npm audit --json. Advisories become dep-advisory (category: "dependency") findings - severity from the advisory, message <pkg>@<ver>: <summary> (<ID>), remediation = the patched version, located at the manifest. A missing tool (ENOENT) or any error degrades to a diagnostic (cargo audit not available, skipped), never a failure. Complements the built-in quantum-vulnerable-dependency database (package purpose) with known-CVE coverage. DEP_ADVISORY_RULE is the generic SARIF catalog entry (merged in like DEP_VULNERABLE_RULE).
  • PQC parameter / size verification - a new default detector (pqc-parameter, config scope) that inspects code which has already reached for a post-quantum KEM: pqc-prestandard-kem (medium) flags pre-standard round-3 CRYSTALS-Kyber (pqc_kyber, pqcrypto-kyber, Kyber768, the reference crypto_kem_kyber768_* API, …) claimed as FIPS 203 ML-KEM, with confidence raised when a FIPS-203/ML-KEM/NIST claim sits in the same file; pqc-parameter-mismatch (medium, deliberately conservative, low confidence) fires only when a distinctive ML-KEM/Kyber byte size (pk/sk/ct) names one parameter set while the code advertises a different one (e.g. 1184 present but the code says ML-KEM-1024). Both are category: "kem", hndl: false. Full positive/negative/gating tests.
  • Provenance / declared source repository. New @quantakrypto/core checkProvenance(root, opts) reads the root manifest's repository URL (package.json repository, Cargo.toml repository, pyproject.toml [project.urls]). No repository declared → provenance-repo-missing (info, "builds cannot be verified against source"); under --audit, a declared URL that 404s / does not resolve → provenance-repo-unresolved (medium). Per ADR-0005 core stays offline: the node:https HEAD request is injected by qScan (the networked plane), so the core module imports no outbound network module. PROVENANCE_RULES are the generic SARIF catalog entries.
  • @quantakrypto/qscan: the new --audit flag wires the advisory + provenance checks into runQscan - findings merge into the report and the inventory is rebuilt so counts stay consistent, and they count toward the exit code (a known-vulnerable dependency can gate CI). Diagnostics surface on stderr (qscan: audit: …). New public API: scanAdvisories, DEP_ADVISORY_RULE, checkProvenance, normalizeRepoUrl, PROVENANCE_RULES, and their types.

Added - compliance mandate gate (qscan --mandate, deadline-aware policy-as-code)

Enforceable, dated compliance mandates: the two PQC regimes that carry hard algorithm deadlines - CNSA 2.0 and NIST IR 8547 - become a CI gate. qscan ./ --mandate cnsa-2.0 (repeatable; also nist-ir-8547) evaluates every finding against the mandate's dated, named clauses and reports each prohibited classical-public-key finding (RSA / ECDH / ECDSA / EdDSA / DH / DSA / ECIES) with its clause, deadline, and citation. The gate is deadline-aware: a prohibited finding is a warning once the deprecation date (2030) passes and fails the build only once the disallow date (2035) passes. --lead-months <n> fails early when a deadline is within n months; --fail-now fails on any prohibited finding immediately. SemVer: minor (additive API + CLI surface).

  • @quantakrypto/core: a new mandates.ts evaluator - the bundled mandate catalog (MANDATES, keyed cnsa-2.0 / nist-ir-8547, each clause named, dated, and cited), evaluateMandates (pure and deterministic - the caller supplies now, so qScan and qProbe can drive it off the same Finding[]), and mandateGateFails (the exit-code decision), plus getMandate, mandateIds, and the Mandate* types. The clause deadlines derive from the dated PQC_STANDARDS snapshot (single source of truth), so the mandate catalog cannot silently diverge from the documented standards.
  • @quantakrypto/qscan: --mandate <id> (repeatable), --lead-months <n>, --fail-now; the human report gains a compliance block (per-finding clause + deadline + citation, a due/violation summary, and the next deadline).
  • @quantakrypto/action: mandate / lead-months / fail-now inputs pass through to the scan, so CI can enforce a mandate's deadlines directly.
  • Scope (deliberate): a family-level dated gate over classical public-key crypto. X25519/X448 are intentionally not flagged - they are the classical half of the recommended hybrid (X25519MLKEM768), so hybrid deployments do not false-positive. It does not assert PQC parameter levels (e.g. ML-KEM-768 where CNSA 2.0 requires 1024); that parameter-level check stays on the roadmap (docs/COMPARISON.md §2.4). DORA / NIS2 / PCI DSS set no independent algorithm date - they inherit these timelines (docs/COMPLIANCE.md §4).
  • Deferred to a fast-follow: (1) machine-readable mandate output in the SARIF / JSON / evidence reports (the gate currently surfaces in the human report + exit code), and (2) composing the mandate gate with a custom --policy file.

[0.6.0] - 2026-07-29

Bumps every published package 0.5.x -> 0.6.0. Ships everything accumulated below since 0.5.2 - headline features: the HNDL data-risk quantifier and the crypto-agility manifest emitter + local validator.

Added - crypto-agility manifest (emitter + local validator)

A well-known-URL JSON document (/.well-known/crypto-agility.json) that publishes a project's cryptographic posture so any agent, scanner, or CI bot can read it the way it reads security.txt. Roadmap frontier initiative 13; MVP. Spec: docs/CRYPTO-AGILITY-MANIFEST.md.

The manifest is a compact summary derived from a scan: version, generatedAt, a posture block (readiness score, hybrid-KEX assertion, quantum-vulnerable findings by severity, HNDL-exposed count), a cbomSummary (algorithm families in use, asset count, and the full CBOM's serial number), an optional attestation URL, and a policy block (the NIST IR 8547 2030/2035 deadlines by default, or an operator-declared transitionDeadline via --policy).

New CLI surface:

  • qscan crypto-agility emit [path] (or the qscan . --crypto-agility flag) derives and writes the manifest to stdout or -o <file>. It is additive: it runs a scan but always exits 0 and never consults the severity threshold, so publishing a posture manifest cannot fail CI. --attestation <url> records a credential link (verbatim, never fetched), and --hybrid-kex / --no-hybrid-kex assert hybrid key exchange (default: null, undetermined by a static scan).
  • qscan crypto-agility validate <file> checks a local manifest against the schema (required fields, types, version), exiting 0 valid / 1 invalid (each problem printed) / 2 on an I/O error. It is strictly offline and never fetches a URL; a network fetch-and-validate of a remote manifest is a website-side follow-up.

New public API in @quantakrypto/core: buildCryptoAgilityManifest, validateCryptoAgilityManifest, CRYPTO_AGILITY_MANIFEST_VERSION, CRYPTO_AGILITY_WELL_KNOWN_PATH, and their types; in @quantakrypto/qscan: runCryptoAgilityEmit, runCryptoAgilityValidate. Purely additive - detectors, the detection F1 = 1.000 benchmark, existing report formats, and scan exit codes are unaffected. SemVer: minor.

Fixed - HNDL exposure of unbound findings was always 0

The defaults.classification fallback documented for findings that bind to no declared hndl.yml asset never produced a non-zero, rankable score. An unbound finding was scored with retention = secrecy lifetime = 0, so its protection horizon X = 0; under any horizon where Z >= Y (including the defaults Y = 5, Z = 15) the Mosca factor M = (0 + Y - Z)/(0 + Y) clamps to 0, driving the exposure to 0 for every unbound finding regardless of classification. The fallback was effectively dead.

Unbound findings now assume the minimum-concern horizon: an unknown data lifetime is taken to be at least the quantum-threat horizon (X = Z), the shortest lifetime for which HNDL is a concern at all. This yields M = Y / (Y + Z) - a small but non-zero, rankable exposure that never exceeds a declared long-lived asset's and self-adjusts to any per-org horizon override (no magic constant). Retention stays 0 (genuinely unknown); the assumed secrecy lifetime carries the horizon and is surfaced in the finding rationale (secrecyLifetimeYears, moscaMarginYears, moscaBreach) so it is visible and contestable. The Mosca math itself was already correct and is unchanged. Bound findings, the public API surface, the detection F1 = 1.000 benchmark, and scan exit codes are unaffected; this is a purely additive scoring fix. See docs/HNDL.md §4.1. SemVer: patch.

Added - detection quick-wins (PHP composer, JS recall edges, npm lockfile depth)

Four post-1.0-roadmap accuracy/perf items on the benchmark-guarded detection surface. The F1 = 1.000 precision/recall benchmark is unchanged (still zero false positives, zero false negatives). SemVer: minor (additive detection).

  • PHP composer.json / composer.lock dependency scanning: a whole ecosystem was invisible. Adds composer to DependencyEcosystem, a curated DB of quantum-vulnerable PHP packages (phpseclib, paragonie/*, firebase/php-jwt, lcobucci/jwt, web-token/jwt-framework, mdanter/ecc, simplito/elliptic-php), the composer.json / composer.lock manifest mapping, and a generous vendor/package extractor (both files are JSON, so the DB filter keeps it safe).
  • JS home-turf recall edges: the Node crypto detector now catches bracket (computed-member) access, e.g. crypto['createSign'](…) / crypto["createECDH"](…) / crypto['generateKeyPairSync']('rsa'), and generateKeyPair(kind, …) where the key type is a variable (emitted as the generic keygen rule, unknown family, HNDL-conservative). No double-counting with the existing dotted-call and literal-argument rules.
  • npm lockfile v1 nested tree + npm: alias resolution: scanNpmManifest now walks the legacy package-lock v1 nested dependencies tree (transitive deps that never appear at the top level) and resolves npm:-aliased packages from package.json values, v1 entry version fields, and v3 packages entry name fields, so a vulnerable package hiding behind an alias is still flagged.
  • Parallel-path double-stat elimination (perf): the directory walker now surfaces the byte size it already stat'd (walkFilesSized), so the parallel scanner no longer re-stats every file for byte-balanced chunking. Pure perf; the file set and detection output are byte-identical.

Added: stable finding fingerprints in JSON and SARIF output

  • Every finding in the --report json output now carries a top-level fingerprint field, and each SARIF result mirrors the same value in properties.fingerprint (alongside the existing partialFingerprints). It is the line-INSENSITIVE identity already used by the baseline: sha256(ruleId | normalized-POSIX-repo-relative-path | normalized-snippet), with the volatile line number deliberately excluded and a rule+path fallback when no snippet context exists. A line move does not change it, so downstream consumers can key finding identity across runs (posture drift, migration tracking) rather than re-reading a shifted finding as new-plus-resolved. Reuses fingerprintFinding from the baseline module (no new public API), so JSON identity, SARIF partialFingerprints, and the baseline suppression set are one value. Additive and backward compatible.

Added - HNDL data-risk quantifier (hndl.yml, qscan --hndl, qscan hndl init)

Roadmap initiative 2.a/2.b: quantify harvest-now-decrypt-later exposure so the migration backlog ranks by real risk, not finding counts. Exposure per finding = crypto-vulnerability x data-sensitivity x Mosca-factor (retention + secrecy lifetime vs the quantum-threat horizon; Mosca's inequality made concrete). SemVer: minor (additive public API + CLI surface; no breaking change).

  • @quantakrypto/core: a new hndl.ts engine - a hand-rolled, zero-dependency hndl.yml parser (data assets, public|internal|confidential|regulated classification, retention_years, secrecy_lifetime_years, path-glob + detector- scope bindings), finding→asset binding, and the exposure computation (computeHndl, parseHndlMap, loadHndlMap, scaffoldHndlYaml, plus the exported weight/horizon constants so the score is contestable, not magic). The reporters (toJson / toSarif) gain an optional hndl field: per-finding exposureScore / dataAsset / rationale and a repo-level HNDL summary.
  • @quantakrypto/qscan: qscan ./ --hndl emits exposure per finding + a repo summary in the JSON/SARIF properties and a section in the human report (additive, never changing the exit code); qscan hndl init scaffolds hndl.yml seeded with detected data-adjacent findings.
  • Docs: docs/HNDL.md documents the exposure model (formula, weights, Mosca worked example) so the score can be argued with.
  • Fingerprint integration point: exposures key by finding.fingerprint when present, else the canonical fingerprintFinding() hash; switches transparently once Finding.fingerprint lands from the parallel workstream.

Added — new detection surfaces (Solidity/blockchain, WebAuthn, proxy/gRPC TLS, code-signing, weak-hash)

Five new detectors from a fresh coverage-gap research pass (52 detectors / 297 rules):

  • Smart-contract / blockchain (.sol / .move / .cairo) — a 14th source language pack flagging on-chain classical signature verification: Solidity/EVM ecrecover + OpenZeppelin/Solady ECDSA.recover/SignatureChecker (secp256k1), Move ed25519_verify / ecdsa_k1::secp256k1_verify, Cairo check_ecdsa_signature. severity: high — on-chain keys often ARE asset custody.
  • WebAuthn / FIDO2 / passkeys — catches the numeric COSE algorithm ids (alg: -7 ES256, -257 RS256, -8 EdDSA) and enum identifiers that the quoted- string JWT rule misses, in @simplewebauthn / py_webauthn / go-webauthn / webauthn4j.
  • Reverse-proxy / gRPC TLS — standalone Envoy / Nginx / HAProxy / Traefik TLS config + in-code gRPC channel credentials (Python/Node/Java/Go): the classical-cert ECDHE termination that the k8s/mesh detectors don't see (HNDL).
  • Code-signing CLIs in build scripts (not just CI) — Authenticode signtool/ osslsigncode, Android apksigner/Gradle signing, rpmsign/dpkg-sig, nuget sign, Apple codesign/notarytool.
  • Weak hash in signatures/certs (quantum-adjacent) — activates the hash category: SHA-1/MD5 in a signature or X.509 certificate context (SHA1withRSA, sha1WithRSAEncryption + its OID, openssl -sha1 in a cert/sign command). NIST retires SHA-1 by 2030 — the same window as the PQC migration. Narrowly scoped to signature/cert contexts; password hashing is deliberately out of scope.

Fixed — pre-release hardening (adversarial audit)

  • Offline-boundary guard was bypassable by ordinary formatter-produced code (multi-line imports, side-effect/re-export imports, // inside a URL string, fetch() in a template interpolation, bracket/destructured process.env key reads, outbound Node modules). Rewritten with a single-pass code/string/comment lexer and whole-file scanning; workflow auto-merge coverage added.
  • SSH-CA now flags the canonical TrustedUserCAKeys/HostCertificate/ssh-keygen -s deployment (previously zero findings) and no longer fires on program source files. SPIRE matches unquoted-YAML and JSON forms (missed every Helm/JSON deployment). DKIM no longer false-positives on a bare k=rsa in prose.
  • CBOM schema validity: quantum-posture flags moved out of cryptoProperties (which is additionalProperties:false) into component properties[]; the invalid keyagree cryptoFunction is now other; the certificate category defaults to assetType: certificate (ACM/Vault-PKI/SPIFFE-SVID were mislabelled).
  • OpenVEX product @id is now an IRI (file:…#L…).

Added — coverage: Objective-C, Dart, DKIM, SSH-CA, SPIFFE/SPIRE

Five new detectors close the coverage gaps that were tracked as post-1.0:

  • Objective-C (objc-crypto, .m/.mm) — Apple Security-framework SecKey* RSA/EC keygen, RSA/ECDSA signing, RSA encryption, ECDH key agreement. Sibling of the Swift pack; gated to .m/.mm so it never double-scans C/C++ .h headers.
  • Dart / Flutter (dart-crypto, .dart) — pointycastle and package:cryptography RSA/ECDSA/ECDH/Ed25519/X25519.
  • DKIM (dkim-crypto) — classical email-signing keys/algorithms in DNS zone files and OpenDKIM config (k=rsa/k=ed25519, a=rsa-sha256, RFC 6376/8463).
  • SSH certificate authority (ssh-ca-crypto) — the OpenSSH *-cert-v01@openssh.com CA-signed host/user certificate surface (distinct from SSH key-exchange).
  • SPIFFE / SPIRE (spire-crypto) — classical ca_key_type/svid_key_type (rsa-*, ec-p256/384) for X.509-SVID workload identities.

Objective-C and Dart bring the analyzable source languages to 13. All five add positive + negative + comment/doc-suppression tests; the precision/recall benchmark is unaffected (F1 = 1.000, zero false positives on the negative set).

Added — CI enforcement of the offline/agent boundary (ADR-0005)

  • New guard scripts/check-offline-boundary.mjs (wired into ci.yml and supply-chain-audit.yml) enforces the two-plane architecture that was previously convention-only: core/mcp/sieve stay strictly offline and key-free (no @quantakrypto/agent import, no outbound fetch(/WebSocket/XHR, no LLM API-key read), qscan reaches the agent only via a dynamic import(), and no package or workflow auto-merges (gh pr merge / --admin). Uses comment/string masking so a token that only appears inside a regex or comment (e.g. core's own redaction patterns) is not a false positive. Its reject logic is covered by known-bad fixtures in the guard-script tests.

Changed — CBOM asset-type refinement

  • The CycloneDX CBOM now classifies each finding into its proper assetType instead of labelling everything algorithm: X.509 findings become certificate, private/public key material becomes related-crypto-material (typed private-key / public-key), and TLS findings become protocol (protocolProperties.type: "tls"). Algorithm-usage components are unchanged. Every asset still carries quantumVulnerable / harvestNowDecryptLater. (Completes the "future refinement" noted in the CBOM audit.)

Added — OpenVEX export (--format vex)

  • qscan --format vex emits an OpenVEX 0.2.0 document so the quantum-readiness posture flows into the same supply-chain pipeline that ingests CVE-based VEX. One statement per rule (a synthetic QK-<ruleId> vulnerability), every affected file:line product, status: "affected", remediation as action_statement, and any --triage verdict in status_notes. Deterministic output. New @quantakrypto/core API: toOpenVex, OpenVexDocument, OpenVexStatement, OpenVexOptions; qScan re-exports renderVex.

Added — evidence verification (verifyReadinessReport)

  • New @quantakrypto/core API verifyReadinessReport(report) closes the build → sign → verify loop for the ISO A.8.24 evidence chain. It recomputes the deterministic content hash over the report's own body and returns { valid, computedHash, claimedHash, reason? }, detecting tampering with any hashed field (findings, inventory, policy verdicts, subject/tool metadata) while ignoring the excluded scan time / CBOM envelope / attestation block. Integrity only — the detached signature/timestamp remain the external signer's to verify (ADR-0004).

Changed — test hardening

  • De-stubbed the qScan CLI and MCP tool tests that predated a real core.scan and tolerated every exit code / both if-else branches; they now assert deterministic outcomes against real fixtures.
  • Added coverage for previously-untested failure paths: the sieve runner's timeout/crash/spawn-failure rejections, the stateful-HBS detector (its first unit test), the CI guard scripts' reject logic (via known-bad fixtures), and the agent BYOK adapters' timeout / network-error / key-only-in-header invariants.
  • Fixed a latent bug surfaced by the guard tests: scripts/validate-sarif.mjs ran its CLI main() at import time (no import.meta.url guard), so importing it triggered a scan and process.exit(). CLI behaviour is unchanged.

Added — detection coverage (SAML/XML-DSig, PKCS#11, TDE, CDK/Pulumi/GCP/Azure KMS)

Closes the real coverage gaps the pre-1.0 audit found:

  • XML-DSig / XML-Enc (SAML, WS-Security) — a new detector flags classical XML signature algorithm URIs (xmldsig-more#rsa-sha256, #dsa-sha1, #ecdsa-sha256) and XML encryption key transport (xmlenc#rsa-oaep) — the algorithm layer under enterprise SSO, whose long-lived IdP signing keys are a prime forgery surface.
  • PKCS#11 / HSM — a new detector flags classical keys behind a token: the OpenSC pkcs11-tool --key-type rsa:2048 | EC:* keygen and the PKCS#11 mechanism constants (CKM_RSA_PKCS_KEY_PAIR_GEN, CKM_ECDSA_*, CKM_DSA*, CKM_DH_PKCS_DERIVE). HSMs hold an org's longest-lived roots — the keys a migration must find first.
  • SQL Server TDE — the database detector now flags CREATE ASYMMETRIC KEY … WITH ALGORITHM = RSA_* (Transparent Data Encryption protecting the DEK with a classical RSA key — at-rest data is HNDL-exposed).
  • Cloud KMS breadth — cloud-kms now covers the AWS CDK enum form (kms.KeySpec.RSA_2048, acm.KeyAlgorithm.EC_prime256v1) and Pulumi camelCase props (previously zero findings), plus GCP (RSA_SIGN_* / EC_SIGN_*) and Azure Key Vault (createRsaKey, KeyType.Rsa) runtime SDK forms.

All added with positive + negative + doc-suppression tests; benchmark unaffected.

Added — selectable standards regimes (--profile)

  • qscan --profile <id> tailors the migration guidance to a standards regime instead of a single hardcoded NIST/CNSA worldview: nist (default), cnsa-2.0, bsi-tr-02102, anssi, uk-ncsc. Each profile carries its parameter sets, deprecate/disallow deadlines, and — the key fix — a hybrid stance. The pre-profile code told everyone "hybrids optional" (CNSA's position), which is wrong for an ANSSI or BSI audience where hybridization is required; now the report says "hybrid required" for ANSSI/BSI and "hybrids optional" for CNSA 2.0, with the regime's own citation. --tier category-5 is now an alias for --profile cnsa-2.0, and --policy still composes an org's exceptions on top. New @quantakrypto/core API: StandardsProfile, STANDARDS_PROFILES, getStandardsProfile, standardsProfileIds, defaultStandardsProfile, remediationForProfile, formatProfileGuidance. A drift test keeps the profile parameter sets aligned with PQC_STANDARDS.

Added — evidence signing orchestration (A.8.24)

  • qscan --format evidence --sign <cmd> / --timestamp <cmd> complete the A.8.24 evidence chain: the readiness report's deterministic contentHash is piped to an operator-provided external signer (openssl / cosign / an RFC-3161 TSA client) on stdin, and its stdout is recorded as the detached signature / timestamp token in attestation.signature / attestation.timestamp, alongside a non-sensitive signedWith / timestampedWith provenance label (the program name — never the argument list, so a key path can't leak). The payload is never interpolated into the command, and signing never changes contentHash (attestation is excluded from the hashed body). Per ADR-0004 the tool implements no cryptography — it orchestrates the signer; per ADR-0005 this is a qscan CLI feature only (the MCP server stays offline and key-free). New @quantakrypto/core API: signReadinessReport, EvidenceSigner, SignEvidenceOptions. Both flags require --format evidence (a loud error otherwise), and a non-zero signer exit aborts with a clear message.

Added — frozen public API surface + generated reference (1.0 gate)

  • docs/API.md (human reference) and docs/api-surface.json (the machine-readable contract) are generated from each package's public entry point by npm run api:docs (scripts/gen-api-reference.mjs, zero-dep). They enumerate every SemVer-covered symbol across @quantakrypto/core · qscan · mcp · sieve · agent · qprobe (318 symbols today).
  • npm run api:check — a new CI gate (in the lint job) that fails if a package's real exports drift from the frozen snapshot, so adding or removing a public symbol is a deliberate, reviewed change rather than an accident. Closes the VERSIONING.md "generated API reference + frozen surface" 1.0 requirement.

[0.5.2] - 2026-07-23

@quantakrypto/mcp metadata-only patch. Adds an mcpName field to the package and a server.json, so the server can be listed on the official MCP Registry (registry.modelcontextprotocol.io) under io.github.quantakrypto/pqc-tools. No code or behavior change; the other packages stay at 0.5.0.

[0.5.1] — 2026-07-23

@quantakrypto/mcp patch release only — the other @quantakrypto/* packages remain at 0.5.0 (see Unreleased for the pending, still-unpublished core work).

Fixed — MCP protocol/schema compliance (surfaced by MCP directory tooling)

  • resources/templates/list now returns an empty { resourceTemplates: [] } instead of -32601 method not found. The server advertises the resources capability, so spec-compliant clients — and MCP directory health checks like Glama's Inspector — call this during discovery; the missing handler tripped them.
  • Tool input schemas — array-typed fields (triage_findings / apply_triage / remediate_findings findings, apply_triage verdicts, and score_delta before / after) now declare an items object schema describing the element shape (a finding, or a { fingerprint, exposureScore, priority, rationale } verdict) instead of a bare type: "array". MCP inspectors flag the bare form ("missing items definition") and it lowered tool-definition-quality scores.

[0.5.0] — 2026-07-20

Fixed — detector precision (cross-detector double-counts)

Several config detectors emitted a second finding for a line another detector already owned, inflating counts and depressing the readiness score. Ownership is now single-source:

  • source.ts owns transport tokens. Removed the redundant vpn net-sshd-classical-kex (sshd KexAlgorithms) and mesh mesh-istio-classical-cipher (ECDHE-RSA suites) rules — source.ts's ssh-kex-classical / tls-classical-kex token detectors already cover them.
  • cloudformation.ts owns crypto inside templates. jwk and cloud-kms now defer inside a CloudFormation/ARM template (new isCloudTemplate gate), and the CFN KMS rule matches all three spec-key spellings (KeySpec / KeyPairSpec / CustomerMasterKeySpec) so nothing is missed.
  • jwk skips docs (README examples) and is now usage-aware: an RSA/EC signing JWK (use:"sig", or RS/PS/ES alg) is classified as a signature (hndl:false) instead of an HNDL-exposed key; encryption keys stay hndl:true.
  • jose defers to jwk when the alg belongs to a JWK object (a kty is in the same object), so a JWK's declared alg is not counted twice.

Added — A.8.24 evidence policy mapping (roadmap 🟡)

  • qscan --format evidence --policy <file> completes §4 of the ISO/IEC 27001 A.8.24 evidence report: an org supplies a machine-readable cryptography policy (a permit-list of algorithm families — prohibited / inTransition / permitted, plus a transitionDeadline and defaultVerdict), and every finding is flagged conformant / violation / transition-pending against it, with a per-verdict summary. The verdicts are folded into the attested (hashed) body, so this policy judgment over this scan is reproducible and tamper-evident. A malformed policy fails loudly (parseCryptoPolicy throws on an unknown family) rather than silently dropping the verdicts. New @quantakrypto/core API: buildPolicyMapping, parseCryptoPolicy, verdictForAlgorithm, CryptoPolicy. See docs/compliance/example-crypto-policy.json.

Added — standards-currency cadence (roadmap 🟠)

  • A single, dated, cited source of truth for the PQC standards the tool tracks (@quantakrypto/core PQC_STANDARDS): FIPS 203/204/205, the CNSA 2.0 tiers, SP 800-208, the NIST IR 8547 2030/2035 timeline, and the emerging (HQC, FN-DSA, X-Wing) / hybrid (X25519MLKEM768, SecP384r1MLKEM1024) targets — each with a source and an asOf date, plus lastReviewed / nextReview (quarterly).
  • A drift test (test/standards.test.ts) that fails the build if the runtime remediation constants (TIER_PARAMS, PQC_TRANSITION_NOTE, STATEFUL_HBS_NOTE) fall out of sync with the manifest — code and the documented standards can no longer silently diverge.
  • An advisory cadence check — npm run standards:check (scripts/standards-check.mjs, wired into CI) prints the sources to re-verify and warns (never fails) when the quarterly review is due; standardsReviewStatus(now) is the pure predicate behind it. Runbook: docs/standards/pqc-standards.md.

Fixed (from a real-repo precision audit)

  • ssh-public-key false positives on i18n / label strings. The rule matched the bare key-type token (ssh-rsa, ssh-ed25519, …), so it fired on UI labels and translation values like "ssh-rsa": "ssh-rsa" — 124 false positives on a single real repo (activepieces, 22.8k files). A bare token now counts only when it is either followed by base64 key material (a real authorized_keys / known_hosts entry) or is one of ≥2 distinct ssh key/host-key algorithm tokens on the line (a HostKeyAlgorithms … preference list) — the two genuine SSH surfaces. The line window is bounded so detection stays linear-time. Both benchmarks unchanged (tuned P/R/F1 1.000, recall 0.847); the real repo drops from 161 → 37 findings with the SSH FPs gone.

Added — new detector surfaces (Bicep, Swift, Pulumi)

  • Azure Bicep (.bicep) — the native Azure IaC DSL, distinct from the ARM/ CloudFormation JSON already covered (Bicep is the source, ARM JSON its compiled output). Flags Microsoft.KeyVault kty: 'RSA'/'EC' keys (gated to the Key Vault marker) and legacy minimumTlsVersion: 'TLS1_0'/'TLS1_1'.
  • Swift / CryptoKit + Security framework — P256/384/521 Signing vs KeyAgreement (ECDSA / ECDH), Curve25519 Signing/KeyAgreement (Ed25519 / X25519), and SecKeyCreateRandomKey kSecAttrKeyTypeRSA/EC. .swift added to the C-style comment table and to DetectorLanguage.
  • Pulumi — the pulumi-tls provider's tls.PrivateKey across TS/JS/Python/Go, gated to a pulumi-tls marker, classifying the algorithm value (RSA/ECDSA/ED25519).
  • All three validated against real OSS repos with zero false positives, and pinned by new labeled benchmark corpus cases.

Fixed — false-positive classes (comment/prose masking)

  • Commented-out code fired detectors. PHP (.php/.php3-5/.phtml), Scala (.scala/.sc), Ruby/Elixir (.rb/.ex/.exs) and Swift were absent from the comment tables, so a commented // openssl_pkey_new(...) scanned as live code.
  • Config-format comments now masked per detector: SQL --//* */, ini ;, zone-file ;, named.conf //, YAML/HCL #///, and Jenkinsfile // — the central stripper covers no config extension, so mesh, dnssec, cloudformation, database, supply-chain, terraform, ansible, vault now mask their own comment lines.
  • Bare PEM header string literals (PEM_HEADER = "-----BEGIN RSA PRIVATE KEY-----") and a parser's paired header/footer constants no longer report as embedded keys — a real block now needs a base64 body, an escaped-newline body (GCP-style single-line "…\n…" keys), or a matching -----END----- with no quote between the markers.
  • Transport-KEX names in a doc string. From a real-repo sweep (terraform-aws-eks): ssh-kex-classical/tls-classical-kex fired on algorithm names listed inside a Terraform/Packer description = "…" (6 hits on one line). A description/help/doc/comment field value is now treated as prose.
  • String-aware object scoping. enclosingObject (used by the JWK/JOSE per-key analysis) now honours JSON string values, so a brace inside a string can't mis-scope a key; its brace-less fallback is a bounded ±window so a distant "kty" can't over-suppress a standalone JWT/JOSE finding.

Fixed — cross-detector double-counts & language precision

  • webcrypto × jose / jwk × jwt. A quoted RSA-OAEP/ECDH-ES next to a subtle.*( call is owned by the WebCrypto detector; an alg inside a JWK object (a "kty" in scope) is owned by the JWK detector — the jwt-jose detector defers in both cases (mirroring the existing jose→jwk guard). RSA1_5 is still reported (WebCrypto doesn't match it).
  • Java: ECIES (EC encryption, HNDL); SSLContext.getInstance("TLSv1.1"/"SSLv2"); the new X448KeyPairGenerator()/Ed448KeyPairGenerator/X448PrivateKeyParameters forms. C#: SecurityAlgorithms.*Sha256Signature constants; certificate pinning no longer flagged as a disabled validator, while => true, => { return true; }, and delegate { return true; } still are. Rust: qualified x448::Secret. Elixir: :crypto.compute_key (the (EC)DH agreement op) and JOSE OKP X25519/X448 as key agreement (not an EdDSA signature).
  • C: EVP_PKEY_derive on an HKDF/scrypt/TLS1-PRF context and EVP_DigestSign* keyed by HMAC/CMAC are no longer flagged as asymmetric crypto (per-match, so a real RSA/ECDSA operation in the same file still fires). Widened the legacy-TLS method forms (TLSv1_1_method, _client/_server, SSLv2_method).
  • tls-weak-cipher now catches a weak cipher inside a hyphenated suite name (ECDHE-RSA-RC4-SHA) without flagging a hardened full-suite exclusion (HIGH:!ECDHE-RSA-RC4-SHA). Identifier-form JWT alg constants (Java/C#/Rust) are string-literal-suppressed like the Go rule.
  • False negatives closed: DNSSEC lowercase mnemonics (BIND dnssec-policy); terraform tls_private_key ED25519 and the modern aws_kms_key key_spec alias; database PGSSLMODE/MySQL ssl-mode/YAML sslmode: forms; ansible X25519/X448/Ed25519/DSA; CloudFront SSLv3; Consul agent config in .json; RFC 9580 (v6) OpenPGP algorithm ids 25–28 (X25519/X448/Ed25519/Ed448); PKCS#12 BER indefinite-length (0x80, NSS/Firefox .p12 exports).

Fixed — engine correctness

  • Triage cap now selects the top findings by severity, not file-path order — a critical in a late-sorting file is no longer dropped from triage and sunk to the bottom of the report.
  • CBOM merge no longer crashes on a legal CBOM with no components, nor on a duplicate bom-ref whose copy lacks cryptoProperties. CBOM serialNumber is now content-addressed over the finding set / occurrence evidence (was derived from the finding count, so distinct scans could collide).
  • Readiness score is independent of file order and real/test interleaving — the diminishing-returns counter is bucketed per (severity × test-path), so a directory rename can no longer move the score.
  • CLI: an ENOENT reports the actual missing path (a --policy / --write-baseline failure is no longer blamed on the scan path); --merge without --format cbom is now a loud error instead of silently dropping the merge files.
  • sieve KAT: an unverifiable vector (no seed/coins) is a skip, not a match — it no longer inflates the "N/N matched" count into a false conformance pass.

Changed — dead-code removal & API surface

  • Removed three unused exported symbols (ToolInputSchema, SuccessResponse, writeLine) and the dead JsonValue type; fixed two unreachable/redundant branches (cosign sign-blob alternation, a redundant CustomerMasterKeySpec fast-reject conjunct). Narrowed the visibility of 71 internal-only symbols (used within a single file, not part of any package's public API). No behavior change; the build's declaration emit + noUnusedLocals verify nothing external depended on them.

Testing

  • Real-repo validation sweep (gin, gorilla/mux, flask, terraform-aws-eks, express, helm/charts): the only false-positive class found (transport-KEX in a doc string) is fixed above; everything else was legitimate. Benchmark corpus expanded with 11 labeled cases (positives + negative baits) for the new surfaces; suite at 916 passing, precision/recall gates and the zero-FP negative set held throughout.

[0.4.4] — 2026-07-19

Added — infrastructure post-quantum readiness

  • Committed-keystore detection (keystore) — JKS/JCEKS (magic 0xFEEDFEED / 0xCECECECE), PKCS#12 (.p12/.pfx, DER SEQUENCE), and BouncyCastle (.bks). Keystores are binary, so the scan pipeline now reads keystore extensions byte-preserving (latin1) and exempts them from the minified skip (walk.ts isKeystorePath, serial + parallel read paths); the match is sensitive key material so the snippet is dropped. Other binaries stay skipped.
  • Binary OpenPGP detection (openpgp) — building on the byte-preserving read, a new detector identifies binary .gpg/.pgp OpenPGP packets by tag: committed SECRET keys (sensitive, RSA/DSA/ElGamal/EC — the sharp finding), public keys, binary PGP-encrypted messages (PKESK → HNDL), and GnuPG keyboxes (.kbx). The public-key algorithm is read from the packet; the parser is bounds-checked and fuzzed. Armored (-----BEGIN PGP …-----) blocks remain handled by PEM/secrets.
  • qscan --cbom --merge <cbom.json> — wires core's mergeCboms so a scan CBOM and an external CBOM (e.g. a qProbe endpoint CBOM) fuse into one combined code + infrastructure CycloneDX bill of materials.
  • qProbe definitive hybrid negotiation — the TLS probe now sends a WELL-FORMED X25519MLKEM768 key_share (a real X25519 public from node:crypto + a valid-range ML-KEM-768 encapsulation key, ML-KEM-768.ek || X25519.pk per draft-ietf-tls-ecdhe-mlkem), so a supporting server selects the hybrid group DIRECTLY in its ServerHello — catching servers that support-but-don't-prefer it, which the old HelloRetryRequest-only inference missed. No full ML-KEM keygen is needed (qProbe never completes the handshake): a ByteEncode₁₂ of in-range coefficients passes the server's encaps modulus check; the throwaway secret is never computed. New mlkem768.ts (pure, unit-tested encode/decode).
  • qProbe protocol coverage — added --imap (STARTTLS :143), --pop3 (STLS :110), and --postgres (SSLRequest :5432) probes, auto-selected by well-known port. IMAP/POP3 reuse the line-based STARTTLS upgrade; PostgreSQL sends the 8-byte libpq SSLRequest and upgrades on S. DNS-over-TLS (:853) and IMAPS (:993) already work as direct-TLS probes. All reuse the same negotiated- parameter + certificate inspection and the clean-close hang guard.
  • MCP probe_endpoint tool — exposes qProbe to AI agents; the qprobe plane is dynamically imported so the server stays offline until invoked, the ownership attestation is enforced (refuses unless i_own_this=true, refuses CIDR/ranges), errored endpoints can't read as a false clean score, and the tool is disabled by default on the HTTP transport (opt-in via QUANTAKRYPTO_MCP_ALLOW_NETWORK=1).
  • Infrastructure GitHub Action recipe — IaC scan + weekly scheduled qProbe of owned endpoints + a merged code+infra CBOM artifact.
  • Detector-audit fixes — bounded the messaging/cicd ReDoS regexes (+ config inputs in redos.test.ts), added #/-- comment-masking to k8s/secrets/database, and corrected mq-classical-cipher algorithm labelling / legacy-TLS hndl messages.
  • Six new config-scope detectors in @quantakrypto/core, surfaced automatically by qScan, the Action and MCP (no new install) — each gated and tested with clean-negative cases to hold the precision bar:
    • cicd — classical artifact/code signing in CI/CD pipelines (cosign/ECDSA, GPG/RSA, jarsigner, codesign, minisign/Ed25519). Signature-side exposure (hndl:false): forgeable once a CRQC exists.
    • secrets — secrets wrapped at rest with classical asymmetric crypto: SOPS/age recipients (X25519), PGP MESSAGE blocks (RSA/ElGamal), Bitnami Sealed Secrets (RSA-OAEP). The sharpest harvest-now-decrypt-later story (ciphertext committed to git is retroactively un-fixable). Symmetric ansible-vault is intentionally out of scope.
    • jose — JWE key-management algorithms (RSA-OAEP, ECDH-ES) — confidentiality, HNDL-exposed; complements the JWK detector.
    • k8s — cert-manager privateKey.algorithm (RSA/ECDSA/Ed25519) and Istio legacy TLS floors, gated by a cert-manager / Istio marker.
    • messaging — Kafka/MQTT legacy TLS protocols and classical (EC)DHE cipher suites in broker config.
    • database — pgcrypto public-key encryption (pgp_pub_encrypt) and libpq sslmode without certificate verification.
  • @quantakrypto/qprobe — a new package for active post-quantum readiness probing of live TLS/SSH endpoints you own. The only package that opens sockets, isolated like @quantakrypto/agent and hard-gated behind an ownership attestation (--i-own-this / --owned-hosts) enforced in code before any network I/O; refuses CIDR/ranges/wildcards/lists. Detects PQC-hybrid TLS support (X25519MLKEM768) via a hand-rolled raw ClientHello, reads SSH KEXINIT algorithms, and reports the negotiated reality without ever modifying an endpoint ("engine disposes"). Ships a THREAT-MODEL.md. Zero runtime dependencies.
  • qProbe SARIF + CBOM output (--sarif / --cbom / --format). Live-endpoint findings now emit the same SARIF 2.1.0 and CycloneDX 1.6 CBOM as qScan (the core reporters are findings-based, so no registry entry is needed for the qprobe-* rules). This unifies the three planes — code, infrastructure-as-code/config, and live endpoints — into one post-quantum posture: the CBOMs are all CycloneDX 1.6 cryptographic-asset documents that merge via bom-link, and every readiness score comes from the same buildInventory math.
  • mergeCboms(boms) in @quantakrypto/core — merges multiple CycloneDX 1.6 CBOMs into one combined bill of materials, unioning components by their deterministic bom-ref (same algorithm+primitive collapses to one asset whose occurrence evidence spans every plane) and OR-ing the harvest-now-decrypt-later flag. Turns the "code + infra + live-endpoint" story into a single artifact: qscan . --cbom and qprobe … --cbom, then mergeCboms([code, endpoints]).
  • Three more infra detectors in @quantakrypto/core (surfaced automatically by qScan / Action / MCP): cloudformation (AWS CloudFormation / Azure ARM / Bicep-JSON: AWS::KMS::Key KeySpec RSA/ECC, ACM KeyAlgorithm, CloudFront/ELB legacy TLS policies, ARM Key Vault key type), mesh (Linkerd ECDSA identity issuer, Consul Connect CA key type, Istio classical cipherSuites), and dnssec (classical DNSSEC signing algorithms — RSASHA*, ECDSAP*, ED25519/448, DSA — in zone files and signer config). Each gated + clean-negative-tested.
  • qProbe protocol depth: X.509 signature-algorithm extraction (a hand-rolled DER parse reads how the leaf certificate is signed — the CA's algorithm, the forgeable-at-Q-day part — which node:tls does not expose), and an SMTP STARTTLS probe mode (--smtp, auto on :25/:587) that upgrades the mail session to TLS and inspects the negotiated posture. (Real ML-KEM keygen remains intentionally NOT implemented — it would violate the repo's "implements no crypto itself" principle, and it isn't needed: the HelloRetryRequest-based detection works without it, and the definitive hybrid negotiation above gets a direct ServerHello selection from an encoded-but-throwaway key_share whose secret is never computed.)
  • Network transport / VPN detector (vpn) in @quantakrypto/core — classical key exchange in the tunnels carrying communication between things: WireGuard ([Interface]/[Peer] Curve25519 keys — a sharp finding, since WireGuard has no standard PQC KEM; the private key is treated as sensitive material), IPsec / strongSwan IKE/ESP proposals naming classical DH groups (modp* = finite-field DH, ecp* = ECDH), and sshd_config / ssh_config KexAlgorithms lines that offer no PQC hybrid KEX — a server that already lists sntrup761x25519 / mlkem768x25519 stays silent. Each rule is gated to its own config shape.
  • Four more infra detectors in @quantakrypto/core, closing audited gaps (each comment-masked and gated; clean-negative tested):
    • ansible — Ansible community.crypto openssl_privatekey/csr type: RSA/ECC.
    • age — a committed AGE-SECRET-KEY-1… identity (X25519 private key, marked sensitive) — worse than a recipient; closes the secrets-detector gap.
    • supply-chain — classical container/artifact signing beyond cosign/GPG: Docker Content Trust (Notary v1), CNCF Notation, in-toto.
    • vault — native HashiCorp Vault HCL: transit key types (rsa-*, ecdsa-p*, ed25519) and pki role key_type (Terraform-provisioned Vault stays with the terraform detector; this covers Vault's own .hcl).
  • qprobe: fixed a probe hang on clean connection close — the raw net probes (SSH, TLS-hybrid, SMTP) added no close/end handler, so a peer that accepted then cleanly closed before a complete response left the run wedged (the socket timeout does not fire post-close). Each now resolves on close. Also synced version.ts so JSON/SARIF/CBOM report the correct tool version.

Added / Changed (standards currency + guidance wiring)

  • Elixir language pack — a 10th source language (the BEAM / Phoenix ecosystem). Detects Erlang :crypto.generate_key (:rsa / :dh / :ecdh, with the curve atom disambiguating X25519/X448 from a NIST-curve ECDH) and :crypto.sign/:crypto.verify (:rsa / :ecdsa / :eddsa), the X509 hex package (X509.PrivateKey.new_rsa / new_ec), and erlang-jose (JOSE.JWK.generate_key({:rsa|:ec|:okp, …})). Symmetric/MAC :crypto calls and non-asymmetric type atoms (e.g. :srp) stay silent; precision/recall hold at 1.000/0.847.

  • Scala coverage on the JVM pack. Scala (.scala) and Scala scripts (.sc) compile against the same JCA (KeyPairGenerator / Signature / KeyAgreement) and BouncyCastle APIs the Java/Kotlin rules already match, so the JVM detector now reads them too — a major JVM language covered with no new rules. Added to the analyzable-language set (label now reads "Java/Kotlin/Scala").

  • PHP language pack — a 9th source language (one of the most-deployed backends, previously uncovered). Detects ext/openssl (openssl_pkey_new classified by its OPENSSL_KEYTYPE_*, defaulting to RSA; openssl_public_encrypt / _private_decrypt; openssl_sign / _verify), phpseclib3 RSA/EC/DSA/DH ::createKey, and libsodium sodium_crypto_box/kx (X25519) + sodium_crypto_sign (Ed25519). The key-type window is bounded to the current statement so one openssl_pkey_new can't inherit the next call's key type; symmetric AEAD stays silent. Adds PHP to the analyzable-language set; precision/recall hold at 1.000/0.847.

  • Import-alias resolution (JS/TS + Python + Rust). Detectors now follow renamed imports so an aliased call still detects, precision-safe (the alias is only ever bound to a known crypto symbol, and the alias regexes run on the original text so locations stay exact):

    • JS/TS — import { generateKeyPairSync as gk } from 'node:crypto' and the CommonJS const { createECDH: mk } = require(...) destructure-rename, for the keygen / ECDH / DH constructors.
    • Python — module aliases (from ...asymmetric import rsa as _rsa, comma-separated specifiers, and PyCryptodome import ...RSA as _R), resolving _rsa.generate_private_key( / _ec.ECDSA( / _ec.ECDH( / _R.generate(.
    • Rust — braced/renamed use x25519_dalek::{EphemeralSecret as MontgomerySecret} (and x448 / ed25519_dalek), resolving the aliased type's construction call. Also adds the x448 crate to the catalog.

    Together these lift recall's aliased bucket 0.32 → 0.47 and overall recall 0.824 → 0.847, with precision held at 1.000.

  • Cloud KMS SDK detection (AWS KMS) — a config-scope detector for classical keys minted at runtime via the AWS KMS SDK (the app-code counterpart to the Terraform IaC detector): CreateKey / GenerateDataKeyPair with a KeySpec / KeyPairSpec / legacy CustomerMasterKeySpec of RSA_* or ECC_*. One lexical rule catches every SDK language (JS/TS, Python/boto3, Java, Go, CLI, JSON) and both the key: val and quoted-JSON "key": val forms; the case- sensitive PascalCase field name means it never double-counts with Terraform's snake_case customer_master_key_spec. Symmetric (SYMMETRIC_DEFAULT) keys stay silent.

  • Terraform / OpenTofu (IaC) detection — a new config-scope detector for the classical keys and CMKs that infrastructure code provisions (never visible to the language packs): hashicorp/tls tls_private_key (algorithm = "RSA"/"ECDSA"), AWS KMS customer_master_key_spec (RSA_* / ECC_*), Google Cloud KMS RSA_SIGN_* / EC_SIGN_* algorithm strings, and Azure Key Vault key_type (RSA/EC, incl. -HSM). Matches both HCL and .tf.json syntax; gated to .tf / .tf.json so it never fires on arbitrary files.

  • JSON Web Key (JWK / JWKS) detection — a new config-scope detector finds classical key material in JSON (.json / .jwks, OIDC discovery docs, config): "kty":"RSA" → RSA; "crv":"P-256/384/521/secp256k1" → EC (ECDSA+ECDH); "crv":"Ed25519/Ed448" → EdDSA; "crv":"X25519/X448" → key agreement. Keys EC/OKP off crv so a single key is counted once; symmetric oct keys and ordinary JSON don't fire. A real key-material surface the source packs and the PEM detector both missed; precision/recall stay 1.000 on the tuned corpus.

  • SARIF results now carry partialFingerprints (quantakrypto/v1 = the same line-insensitive sha256 the baseline uses). GitHub code scanning keys alert identity and dedup off this, so a finding survives line shifts and reformatting instead of re-alerting as "new" every time code moves above it.

  • Partial-coverage honesty caveat on the readiness score. When the analyzable subset is only a small slice (<25%) of the files scanned, the human report now notes that the score covers only that slice — a high score on a mostly-unsupported tree no longer reads as a clean bill of health. (Complements the existing zero-analyzable guard.)

  • Worked liboqs / OQS composition example (examples/liboqs-migration/) + a README section. Makes the intended positioning concrete — quantakrypto is the scanner / CI gate / conformance harness around a real PQC library, not a replacement for one — with a full scan → migrate (hybrid X25519MLKEM768 via liboqs) → verify (sieve conformance) → gate walkthrough. Directly answers the "no full PQC library — use alongside liboqs" framing from external reviews.

  • Embedded C crypto coverage (Mbed TLS + wolfSSL/wolfCrypt). The C/C++ detector previously covered only OpenSSL and libsodium; it now also detects the two dominant embedded libraries — mbedtls_rsa_gen_key / mbedtls_ecp_gen_key / mbedtls_ecdsa_* / mbedtls_ecdh_* / mbedtls_dhm_*, and wc_MakeRsaKey / wc_ecc_* / wc_DhAgree / wc_curve25519_* / wc_ed25519_* — with the same HNDL classification as the OpenSSL rules. Directly closes the "embedded / IoT / firmware" scanning-depth gap; distinctive mbedtls_* / wc_* prefixes keep false positives near zero (precision/recall stay 1.000 on the tuned corpus).

  • GitHub Action now writes a job summary ($GITHUB_STEP_SUMMARY) on every run — the readiness score and the findings table (or the migration plan in comment-plan mode) render on the workflow run's summary page with no PR context and no token, so push builds and fork PRs surface results too. The PR comment path is unchanged. Best-effort: a summary-write failure never breaks the build.

  • qscan human report now carries a "Standards & timeline" footer whenever findings exist: the NIST IR 8547 deprecation deadlines (classical public-key crypto deprecated after 2030, disallowed after 2035) plus the standards worth tracking (HQC, FN-DSA/Falcon, X-Wing). Signature findings additionally get the SP 800-208 stateful-HBS note. This wires up guidance that already lived in core (PQC_TRANSITION_NOTE / STATEFUL_HBS_NOTE) but was never surfaced.

  • Stateful-HBS detector broadened to the full SP 800-208 parameter space — LMS/XMSS/XMSSMT rules now match the SHAKE256 hash variants and the 192-bit (M24/N24, _192) parameter sets, not just RFC 8554/8391's SHA-256 sets.

  • Sharper remediation copy — embedded EC private keys now point at ML-DSA (FIPS 204) for signatures or hybrid X25519MLKEM768 for key agreement; the CIRCL dependency note clarifies you migrate the classical usage, not the package (CIRCL itself already ships PQC).

Testing

  • Remediation-correctness benchmark — the counterpart to the detection benchmark: it measures whether the deterministic codemod layer fixes what qScan finds. For a labeled corpus it scores every produced patch on four properties — applied, cleared (re-scan confirms the classical crypto is gone, via the same verifyFix the pipeline uses), no-regression, and idempotent — and, on a second corpus, asserts the layer declines findings with no safe mechanical fix (RSA keygen, ECDH handshake) rather than emitting a wrong patch. Deterministic → gated at 1.000. Documented in docs/validation/remediation-benchmark.md; it's the harness future codemods (and, report-only, LLM fixes) plug into.
  • Triage exit-code invariant is now regression-tested. Added a triageFn test hook to runQscan so the --triage path runs offline, and a test that proves triage can re-rank/annotate a blocking finding but never drops it or changes the exit code (the exit code is computed from raw severities before triage runs — a prompt-injected "de-prioritize" verdict can't sneak a finding past CI).
  • MCP FIX_EXAMPLES meta-test. Every canned before/after example the get_fix_examples tool serves is now asserted well-formed (non-empty, before ≠ after) and required to name a post-quantum target — so no example can silently hand an agent classical crypto — with coverage pinned for every HNDL-critical and signature family.

Fixed (security & correctness — from a post-release 5-lens audit)

  • Agent-line exfiltration-guard bypass — the qremediate blast-radius guard (NEW_SINK_RE) matched only bare require("http") / dynamic import("http"), so a hostile or prompt-injected LLM patch adding require("node:https") or a static import { request } from "node:net" sink was accepted (verified end-to-end). It now matches the node: prefix, static-import forms, and tls/http2, and rejects any sink on a newly-added line (robust to sink-swaps), covered by a red-team fixture suite.
  • Comment/string lexer bugs (introduced in 0.4.3) — the filter now handles Go raw strings (`C:\` no longer swallows the code after it, which had hidden a real finding) and Rust lifetimes ('a no longer starts an unterminated char-literal scan that silently disabled comment-based false-positive suppression for the rest of the file). Both verified and regression-tested.
  • secp256k1 key agreement mis-classified — secp.getSharedSecret() / .ecdh() were flagged as non-HNDL ECDSA signatures; they are now ECDH key agreement (hndl: true), so a genuine harvest-now-decrypt-later surface is no longer deprioritized.
  • MCP score_delta NaN — validates its before/after findings arrays (mirroring the triage/remediate tools) instead of emitting NaN readiness scores on malformed input.

[0.4.3] — 2026-07-15

The 2026-07-15 5-lens audit and its remediation, plus the detection-depth work that followed — the false-negative recall benchmark and the precision/recall fixes a real-repo validation run surfaced. Build clean; tuned benchmark precision/recall 1.000 throughout; still zero runtime dependencies.

Detection depth + real-repo validation

  • Recall (false-negative depth) benchmark — the last open item from the 2026-07-15 audit backlog. A deliberately-hard, real-world crypto corpus across all eight languages (packages/core/test/benchmark/recall/, 85 files / 166 occurrences), labeled by what the crypto truly is — independent of, and written blind to, the detectors. recall.test.ts measures detection recall (family-level, greedy per file; unknown-classified findings count as detections) and prints the exact false-negative list. Baseline 0.645; guarded by a floor (not gated at 1.000 like the tuned benchmark, since real-world recall < 1 is expected). The per-difficulty split — canonical 0.81 vs aliased 0.32 / adversarial 0.37 — quantifies the lexical ceiling and names the closable gaps. See docs/validation/recall-benchmark.md.
  • tls-classical-kex detector (language-agnostic, config scope) — flags classical TLS key-exchange cipher suites (ECDHE-RSA/ECDHE-ECDSA/DHE-RSA, OpenSSL and IANA spellings) as harvest-now-decrypt-later exposure, the cross-language TLS gap the legacy-version rule missed. Closes the first cluster the recall benchmark surfaced: config recall 0.74 → 0.96, overall 0.645 → 0.711, tuned benchmark held at 1.000.
  • Library-form detector coverage — the second recall cluster, closed across six detectors: Go jwt.SigningMethod* and Rust Algorithm::RS256/ES256 identifier forms; libsodium crypto_sign_ed25519_keypair / crypto_kx / scalarmult (C); the ed25519 + rbnacl Ruby gems; BouncyCastle Ed25519/X25519/X448/DH lightweight classes (Java + C#, incl. the bare Kotlin-constructor form); and the cloudflare/circl + decred secp256k1/v4 Go modules in the dependency catalog. uncommon recall 0.66 → 0.90, overall 0.711 → 0.813; tuned benchmark held at 1.000, no new false positives. The residual 31 FNs are the lexical ceiling (runtime-constructed algorithm names, import aliasing) — out of reach without dataflow.
  • Real-repo validation fixes — running qscan over four real OSS repos (golang-jwt, paramiko, panva/jose, gin) surfaced precision bugs and recall holes the authored corpus missed; all closed and pinned by benchmark cases. Recall: x509/PEM key parsing (x509.Parse*, Go — not just keygen), JOSE RSA-OAEP key transport (jose-rsa-oaep), and classical SSH key exchange (ssh-kex-classical: diffie-hellman-group* / ecdh-sha2-* / curve25519) — overall recall 0.813 → 0.824. Precision: a code-only string-literal guard (identifier rules like go-jwt-signingmethod no longer fire inside string literals), a documentation-file skip (SSH/TLS/cert token rules skip .rst/.md), and Python-docstring suppression for token rules (PEM key material inside a docstring is still caught). Readiness score now down-weights findings in test/fixture/doc paths (a no-crypto web app scored 40 → 81). Tuned benchmark held at 1.000 with three new negative baits.

Added (audit remediation)

  • qscan --tier category-3|category-5 — CNSA security-tier migration targets in the report footer (formatTierGuidance), making the previously library-only remediationForTier reachable (category-5 → ML-KEM-1024 / ML-DSA-87).
  • Cross-language detector parity — TLS-config detection across all 7 non-JS packs; verify/decrypt-only coverage (Go/C/Ruby); the Rust openssl crate + ring X25519 + braced-import constructors; Python hazmat-DSA; Java BouncyCastle agreement classes; the JWT detector extended to Go/Ruby; PEM public-key / DH- parameters / CSR markers; C EVP API + libsodium; Python ec.ECDH().
  • Supply-chain CI — OpenSSF Scorecard workflow; a zero-runtime-dependency enforcement gate (scripts/check-zero-deps.mjs); reuse lint (advisory); dependabot.yml; per-package LICENSE.
  • PQC_TRANSITION_NOTE — IR 8547 deprecation timeline + HQC / FN-DSA (FIPS 206) / X-Wing forward-standards tracking. Dependency catalog: JOSE/JWT libs, pycrypto/jwcrypto/authlib, secp256k1 (cargo), net-ssh.
  • qscan --format evidence — ISO/IEC 27001 A.8.24 readiness report (findings
    • inventory + CBOM + a deterministic content hash; external signer fills the attestation). NuGet dependency ecosystem (.csproj/packages.config). SP 800-208 stateful-HBS detector (LMS/HSS/XMSS/XMSSMT). Java/C# JWT identifier-form detection. MCP apply_verified_patch (runs the patch-policy + verify + blast-radius gates offline). ACVP vector-provenance in Sieve reports (raw-byte hashes + declared source, so a kat PASS is traceable). Agent entropy-based redactor catch-all.

Fixed / changed (security & correctness)

  • Agent line (from the adversarial audit): qremediate --llm now rejects LLM patches that add a network/exec sink or rewrite >60 lines and holds them back from apply without --apply-llm; "verified" reworded to "crypto-verified, not security-reviewed". Rubric moved to the provider system role + anti-injection preamble. Spend caps (--max-llm, --max-findings). Provider host-pinning (refuse plaintext non-local base URLs). git add -- separator.
  • Standards: SSH guidance → mlkem768x25519-sha256; SLH-DSA ACVP loader fixed (classify SLH before DSA); X448 → SecP384r1MLKEM1024; RSASSA-PSS keygen no longer mis-classified as KEM (Java); Go ecdh.X25519() split into its own family.
  • X25519 / X448 severity low → medium (confidentiality/key-agreement, as Shor-broken as P-256 ECDH; the largest HNDL surface).

Release

  • The v1 Action tag is auto-moved to the released commit on publish (was stale); per-job workflow permissions scoped; persist-credentials: false on CI checkouts; inlineSources so published sourcemaps aren't dangling.

[0.4.2] — 2026-07-04

Multi-expert audit-hardening pass across all packages; Action dist/ re-bundled. First published to npm under the @quantakrypto scope with build provenance.

Fixed

  • Hardening fixes from a multi-discipline review (redactor coverage, readiness scoring, remediation edge cases); build clean, benchmark 1.000, zero runtime deps.

[0.4.1] — 2026-07-03

Fixed

  • qremediate now fully fixes files with multiple TLS issues in one pass; added the end-to-end testing runbook (docs/how-to-test-0.4.md).

[0.4.0] — 2026-07-03

The multi-language + BYOK-agent release (0.3 skipped). Six new detector languages and the optional LLM agent line land together; still zero runtime dependencies.

Added

  • core (detectors): language packs for Python, Go, Java/Kotlin, C#, Rust, Ruby, and C/OpenSSL — the DetectorRegistry now spans 8 source languages (JS/TS + 7) plus PEM key material. Multi-ecosystem dependency manifests: PyPI, cargo, Go modules, Maven, RubyGems (in addition to npm; +yarn/pnpm lock parsing). Coverage-honesty labelling (ANALYZABLE_LANGUAGES_LABEL).
  • agent (new package @quantakrypto/agent): zero-dep BYOK LLM client — native-fetch adapters for Anthropic Messages + OpenAI-compatible APIs, a zero-dep JSON-schema response validator, a repair-retry loop, and a response cache keyed by (promptVersion, model, level, fingerprint). Triage orchestrator (rubric prompt) and LLM fix orchestrator (proposeFix, skips secret-bearing files).
  • qscan: --triage (BYOK LLM re-ranks + explains findings; never suppresses, never gates CI) and qremediate — deterministic codemod fixes (--mode diff|apply), plus --llm and --mode pr (draft-PR), all verify-gated and worktree-isolated with no auto-merge.
  • mcp: deterministic triage_findings / apply_triage and remediate_findings — request/apply tools that stay offline and key-free (the host agent reasons; the server never calls a provider).
  • action: comment-plan migration-plan PR comment; dist/ re-bundled.

Notes

  • This is the first release published to npm; earlier versions were tagged but the packages went public here.

[0.2.2] — 2026-07-02

Changed / Fixed

  • Readiness score uses exponential decay so it stays responsive across the whole range (was pinning flat at 0 on large repos, hiding all progress).
  • The Action upserts its PR comment via a hidden marker instead of stacking a new comment every push.
  • Expanded the npm dependency DB (+15: ethers, web3, bitcoinjs-lib, openpgp, node-jose, ssh2, @peculiar/x509, http-signature, libsodium-wrappers, …).

[0.2.1] — 2026-07-02

Fixed

  • The qscan CLI was a silent no-op via npx / the .bin symlink / macOS /tmp — the main-guard compared import.meta.url to the unresolved argv[1]. Resolve symlinks like the MCP stdio guard does; added a symlink smoke test.

[0.2.0] — 2026-06-29

The audit-hardening release. Implements the full P0/P1/P2 roadmap (see docs/ROADMAP.md). Build clean; 307 tests pass; ESLint + Prettier clean; still zero runtime dependencies.

Added

  • core: shared canonical baseline (fingerprintFinding/applyBaseline/…); DetectorRegistry + Detector.scope/language; wired ScanOptions (include/files/scanMinified); new detectors (DH MODP, SSH keys, TLS certificate signature algorithms, JOSE ECDH-ES*, one-shot sign/verify, secp256k1); CWE tags on findings; CycloneDX CBOM export (toCbom); scanParallel worker pool; changedFiles incremental helper; CNSA 2.0 Category-5 + SP 800-208 remediation guidance.
  • qscan: --include, --max-file-size, --no-default-ignores, --scan-minified, --changed/--since (incremental), --parallel/ --concurrency, and --cbom output.
  • mcp: safe-by-default HTTP transport (loopback bind, QUANTAKRYPTO_MCP_TOKEN auth, filesystem tools gated behind QUANTAKRYPTO_MCP_ALLOW_FS, per-request timeout + response cap); generate_cbom tool.
  • sieve: SLH-DSA (FIPS 205); FIPS 203 §7.2 encapsulation-key modulus-range check; deeper ML-DSA + deterministic/hedged signing probe; bounded pipelining.
  • repo: ESLint + Prettier, test:coverage, a benchmark harness, OpenSSF Scorecard + release workflows, REUSE.toml, threat model, ADRs, SemVer policy, config spec, and ISO 27001 A.8.24 / ACVP-provenance designs.

Follow-ups landed (previously documented designs)

  • core/qscan: quantakrypto.config.json support — loadConfig in core plus flags > config > defaults precedence in qScan, with --config <path> and --no-config-file (distinct from the --no-config detector toggle). [P2-9]
  • tests: deterministic, seeded-PRNG fuzz targets for the hand-rolled parsers — manifest/dependency parsing + toSarif (core), decodeResponse/fromB64 (sieve), and the argv parser (qscan), in each package's test/fuzz.test.ts. [P1-10]
  • repo: a zero-dep .githooks/pre-commit hook (build → lint → format:check → test) [P2-5]; scripts/validate-sarif.mjs SARIF 2.1.0 structural validator + validate:sarif script [P2-6]; and advisory bench + gating sarif CI jobs.

Fixed (security & correctness)

  • EC key generation is now classified as key-exchange-capable (harvest-now exposure was under-reported). [P0-4]
  • PR-comment Markdown and ::error:: workflow-command output are escaped against injection from attacker-controlled finding text. [P0-2]
  • The Sieve runner spawns the SUT with a scrubbed minimal environment. [P0-3]
  • explain_finding resolves library-rule findings (was "no matching detector"). [P0-5]
  • Hardened the TLS cipher regex (ReDoS) and replaced the quadratic proximity scan with a binary search. [P0-6]
  • The GitHub Action now reuses qScan's runQscan and the shared baseline instead of a divergent second implementation. [P1-3]

0.1.0 — 2026-06-03

Initial release of the quantakrypto-tools monorepo — a zero-runtime-dependency TypeScript toolset for post-quantum readiness.

Added

  • @quantakrypto/core — shared engine: JavaScript/TypeScript + config crypto detectors, a vulnerable-dependency database, a cryptographic inventory with a 0–100 readiness score, and SARIF 2.1.0 / JSON / text reporters.
  • @quantakrypto/qscan — CLI to scan any codebase for quantum-vulnerable cryptography, with baselines, severity gating, and SARIF output.
  • @quantakrypto/mcp — Model Context Protocol server (stdio JSON-RPC implemented in-house) exposing scan/inventory/explain/suggest tools to AI coding agents, plus a hostable HTTP transport scaffold.
  • @quantakrypto/action — GitHub Action that fails CI when newly introduced quantum-vulnerable cryptography lands, with baseline suppression and SARIF.
  • @quantakrypto/sieve — conformance battery for ML-KEM / ML-DSA implementations driven over a JSON protocol; ships no KAT vectors and never fabricates them.
  • Project governance, CI, and a multi-discipline audit set under docs/.