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.
Minor: additions only. No existing shape changes, no exit code moves, and the
Action stays on @v1 because nothing here is breaking.
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.
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.
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.
.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.
Minor: user-facing fixes to the Action, and the runtime it declares. Nothing existing changes shape, and no exit code moves.
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.
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.
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.
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.
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.
Minor: three additive features and two user-facing bug fixes. Nothing existing changes shape, and no exit code moves.
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.
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.
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-thisis 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.comover https, redirects refused, with a 10s timeout, and only for arepository_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.
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-thisis 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
ERRORverdict. If the SUT never returned a single protocol response, the report carries oneharnesscategory andoverall: "ERROR".ERRORoutranksFAIL: 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.
SutCrashErroralready captured the child's stderr, but every category discarded it and reported(err as Error).message. New exporteddescribeSutErrorquotes the diagnostic part of the dump, anchoring on the first and last lines that name a failure — Node buriesError: Cannot find modulebetween 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 saysError: Cannot find module '/repo/my-impl.js'instead ofexited with code 1. - Exports:
describeSutError,HARNESS_CATEGORY, and theVerdicttype. - CLI exit codes are unchanged: only
PASSexits 0, soERRORstill exits 1 and existing CI gates behave as before. Read.overallto distinguish them.
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 jsonadds a top-levelmandateMappingblock;--format sarifcarries the same underrun.properties.mandate(so an uploaded SARIF surfaces it in code scanning); and--format evidenceembeds a date-pinned, hashedmandateMappingin the ISO/IEC 27001 A.8.24 attestation - reproducible per commit per day and tamper-evident via the attestation content hash, mirroringpolicyMapping. The GitHub Action threads the same verdicts into the SARIF it uploads. New optionalReportOptions.mandate/ReadinessReportOptions.mandateandReadinessReport.mandateMapping(all additive; no export renamed or removed). --policycomposes 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, andprohibitedalways wins overpermitted. Exposed on the Action as a newpolicyinput.- Two forward-looking roadmap notes under
docs/roadmap/: attestation as a procurement/underwriting primitive, and a harvest-tripwire (HNDL canary) research note.
- Exit-code loosening (opt-in): with
--policyand--mandate--fail-now/--lead-months, a family the org lists aspermittedorinTransitionno longer trips the early gate (it did in 0.8.0, which had no composition). This only affects runs that pass both flags;--mandatealone is unchanged. The Action side is opt-in (itspolicyinput is new). - Exit-code tightening: the CLI now evaluates the mandate gate on
pre-baseline findings (kept + suppressed), matching the GitHub Action. A
--baselineaccepts 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-baselinethen--baseline). - Pre-1.0 shape change:
MandateEvaluationandMandateFindingVerdictgained required fields (policyName/acknowledged, andpolicyVerdict/acknowledgedrespectively). Runtime behavior for existing 3-argevaluateMandatescallers is identical; only TypeScript code that constructs one of these by hand needs the new fields. --mandate cnsa-2.0disallow 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 newPQC_STANDARDS.cnsaTimeline, instead of borrowing NIST IR 8547's 2035.nist-ir-8547is unchanged (2035). Behavior change: a--mandate cnsa-2.0build with prohibited classical crypto now reachesviolation(and, past 2033, fails) ~two years earlier than in 0.8.0.
- Evidence hash reproducibility.
evaluateMandatesnow pinsnowto UTC midnight of its date before any arithmetic, so themonthsUntil/monthsUntilDisallowcounters (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 whilenowwas date-pinned, so two same-day runs diverged on ~7% of days (month-rounding boundaries).
(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/corescanAdvisories(root, opts)shells out -execFile, a timeout, a boundedmaxBuffer, all in a try/catch (the blessedchanged.tspattern) - 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 becomedep-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_RULEis the generic SARIF catalog entry (merged in likeDEP_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 referencecrypto_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.1184present but the code saysML-KEM-1024). Both arecategory: "kem",hndl: false. Full positive/negative/gating tests. - Provenance / declared source repository. New
@quantakrypto/corecheckProvenance(root, opts)reads the root manifest's repository URL (package.jsonrepository,Cargo.tomlrepository,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: thenode:httpsHEAD request is injected by qScan (the networked plane), so the core module imports no outbound network module.PROVENANCE_RULESare the generic SARIF catalog entries. @quantakrypto/qscan: the new--auditflag wires the advisory + provenance checks intorunQscan- 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.
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 newmandates.tsevaluator - the bundled mandate catalog (MANDATES, keyedcnsa-2.0/nist-ir-8547, each clause named, dated, and cited),evaluateMandates(pure and deterministic - the caller suppliesnow, so qScan and qProbe can drive it off the sameFinding[]), andmandateGateFails(the exit-code decision), plusgetMandate,mandateIds, and theMandate*types. The clause deadlines derive from the datedPQC_STANDARDSsnapshot (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-nowinputs 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
--policyfile.
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.
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 theqscan . --crypto-agilityflag) 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-kexassert 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), exiting0valid /1invalid (each problem printed) /2on 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.
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.
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.lockdependency scanning: a whole ecosystem was invisible. AddscomposertoDependencyEcosystem, a curated DB of quantum-vulnerable PHP packages (phpseclib, paragonie/*, firebase/php-jwt, lcobucci/jwt, web-token/jwt-framework, mdanter/ecc, simplito/elliptic-php), thecomposer.json/composer.lockmanifest mapping, and a generousvendor/packageextractor (both files are JSON, so the DB filter keeps it safe). - JS home-turf recall edges: the Node
cryptodetector now catches bracket (computed-member) access, e.g.crypto['createSign'](…)/crypto["createECDH"](…)/crypto['generateKeyPairSync']('rsa'), andgenerateKeyPair(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:scanNpmManifestnow walks the legacy package-lock v1 nesteddependenciestree (transitive deps that never appear at the top level) and resolvesnpm:-aliased packages from package.json values, v1 entryversionfields, and v3packagesentrynamefields, 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.
- Every finding in the
--report jsonoutput now carries a top-levelfingerprintfield, and each SARIF result mirrors the same value inproperties.fingerprint(alongside the existingpartialFingerprints). 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. ReusesfingerprintFindingfrom the baseline module (no new public API), so JSON identity, SARIFpartialFingerprints, and the baseline suppression set are one value. Additive and backward compatible.
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 newhndl.tsengine - a hand-rolled, zero-dependencyhndl.ymlparser (data assets,public|internal|confidential|regulatedclassification,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 optionalhndlfield: per-findingexposureScore/dataAsset/rationaleand a repo-level HNDL summary.@quantakrypto/qscan:qscan ./ --hndlemits 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 initscaffoldshndl.ymlseeded with detected data-adjacent findings.- Docs:
docs/HNDL.mddocuments the exposure model (formula, weights, Mosca worked example) so the score can be argued with. - Fingerprint integration point: exposures key by
finding.fingerprintwhen present, else the canonicalfingerprintFinding()hash; switches transparently onceFinding.fingerprintlands 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/EVMecrecover+ OpenZeppelin/SoladyECDSA.recover/SignatureChecker(secp256k1), Moveed25519_verify/ecdsa_k1::secp256k1_verify, Cairocheck_ecdsa_signature.severity: high— on-chain keys often ARE asset custody. - WebAuthn / FIDO2 / passkeys — catches the numeric COSE algorithm ids
(
alg: -7ES256,-257RS256,-8EdDSA) 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, Androidapksigner/Gradle signing,rpmsign/dpkg-sig,nuget sign, Applecodesign/notarytool. - Weak hash in signatures/certs (quantum-adjacent) — activates the
hashcategory: SHA-1/MD5 in a signature or X.509 certificate context (SHA1withRSA,sha1WithRSAEncryption+ its OID,openssl -sha1in 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.
- 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/destructuredprocess.envkey 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 -sdeployment (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 barek=rsain prose. - CBOM schema validity: quantum-posture flags moved out of
cryptoProperties(which isadditionalProperties:false) into componentproperties[]; the invalidkeyagreecryptoFunction is nowother; thecertificatecategory defaults toassetType: certificate(ACM/Vault-PKI/SPIFFE-SVID were mislabelled). - OpenVEX product
@idis now an IRI (file:…#L…).
Five new detectors close the coverage gaps that were tracked as post-1.0:
- Objective-C (
objc-crypto,.m/.mm) — Apple Security-frameworkSecKey*RSA/EC keygen, RSA/ECDSA signing, RSA encryption, ECDH key agreement. Sibling of the Swift pack; gated to.m/.mmso it never double-scans C/C++.hheaders. - Dart / Flutter (
dart-crypto,.dart) — pointycastle andpackage:cryptographyRSA/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.comCA-signed host/user certificate surface (distinct from SSH key-exchange). - SPIFFE / SPIRE (
spire-crypto) — classicalca_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).
- New guard
scripts/check-offline-boundary.mjs(wired intoci.ymlandsupply-chain-audit.yml) enforces the two-plane architecture that was previously convention-only:core/mcp/sievestay strictly offline and key-free (no@quantakrypto/agentimport, no outboundfetch(/WebSocket/XHR, no LLM API-key read),qscanreaches the agent only via a dynamicimport(), 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.
- The CycloneDX CBOM now classifies each finding into its proper
assetTypeinstead of labelling everythingalgorithm: X.509 findings becomecertificate, private/public key material becomesrelated-crypto-material(typedprivate-key/public-key), and TLS findings becomeprotocol(protocolProperties.type: "tls"). Algorithm-usage components are unchanged. Every asset still carriesquantumVulnerable/harvestNowDecryptLater. (Completes the "future refinement" noted in the CBOM audit.)
qscan --format vexemits 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 syntheticQK-<ruleId>vulnerability), every affectedfile:lineproduct,status: "affected", remediation asaction_statement, and any--triageverdict instatus_notes. Deterministic output. New@quantakrypto/coreAPI:toOpenVex,OpenVexDocument,OpenVexStatement,OpenVexOptions; qScan re-exportsrenderVex.
- New
@quantakrypto/coreAPIverifyReadinessReport(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).
- De-stubbed the qScan CLI and MCP tool tests that predated a real
core.scanand 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.mjsran its CLImain()at import time (noimport.meta.urlguard), so importing it triggered a scan andprocess.exit(). CLI behaviour is unchanged.
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-kmsnow 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.
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-5is now an alias for--profile cnsa-2.0, and--policystill composes an org's exceptions on top. New@quantakrypto/coreAPI:StandardsProfile,STANDARDS_PROFILES,getStandardsProfile,standardsProfileIds,defaultStandardsProfile,remediationForProfile,formatProfileGuidance. A drift test keeps the profile parameter sets aligned withPQC_STANDARDS.
qscan --format evidence --sign <cmd>/--timestamp <cmd>complete the A.8.24 evidence chain: the readiness report's deterministiccontentHashis 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 inattestation.signature/attestation.timestamp, alongside a non-sensitivesignedWith/timestampedWithprovenance 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 changescontentHash(attestation is excluded from the hashed body). Per ADR-0004 the tool implements no cryptography — it orchestrates the signer; per ADR-0005 this is aqscanCLI feature only (the MCP server stays offline and key-free). New@quantakrypto/coreAPI:signReadinessReport,EvidenceSigner,SignEvidenceOptions. Both flags require--format evidence(a loud error otherwise), and a non-zero signer exit aborts with a clear message.
docs/API.md(human reference) anddocs/api-surface.json(the machine-readable contract) are generated from each package's public entry point bynpm 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.
@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.
@quantakrypto/mcp patch release only — the other @quantakrypto/* packages
remain at 0.5.0 (see Unreleased for the pending, still-unpublished core work).
resources/templates/listnow returns an empty{ resourceTemplates: [] }instead of-32601 method not found. The server advertises theresourcescapability, 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_findingsfindings,apply_triageverdicts, andscore_deltabefore/after) now declare anitemsobject schema describing the element shape (a finding, or a{ fingerprint, exposureScore, priority, rationale }verdict) instead of a baretype: "array". MCP inspectors flag the bare form ("missing items definition") and it lowered tool-definition-quality scores.
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.tsowns transport tokens. Removed the redundantvpnnet-sshd-classical-kex(sshd KexAlgorithms) andmeshmesh-istio-classical-cipher(ECDHE-RSA suites) rules —source.ts'sssh-kex-classical/tls-classical-kextoken detectors already cover them.cloudformation.tsowns crypto inside templates.jwkandcloud-kmsnow defer inside a CloudFormation/ARM template (newisCloudTemplategate), and the CFN KMS rule matches all three spec-key spellings (KeySpec/KeyPairSpec/CustomerMasterKeySpec) so nothing is missed.jwkskips docs (README examples) and is now usage-aware: an RSA/EC signing JWK (use:"sig", orRS/PS/ESalg) is classified as asignature(hndl:false) instead of an HNDL-exposed key; encryption keys stayhndl:true.josedefers tojwkwhen thealgbelongs to a JWK object (aktyis in the same object), so a JWK's declared alg is not counted twice.
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 atransitionDeadlineanddefaultVerdict), 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 (parseCryptoPolicythrows on an unknown family) rather than silently dropping the verdicts. New@quantakrypto/coreAPI:buildPolicyMapping,parseCryptoPolicy,verdictForAlgorithm,CryptoPolicy. Seedocs/compliance/example-crypto-policy.json.
- A single, dated, cited source of truth for the PQC standards the tool tracks
(
@quantakrypto/corePQC_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 asourceand anasOfdate, pluslastReviewed/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.
ssh-public-keyfalse 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 realauthorized_keys/known_hostsentry) or is one of ≥2 distinct ssh key/host-key algorithm tokens on the line (aHostKeyAlgorithms …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.
- 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). FlagsMicrosoft.KeyVaultkty: 'RSA'/'EC'keys (gated to the Key Vault marker) and legacyminimumTlsVersion: 'TLS1_0'/'TLS1_1'. - Swift / CryptoKit + Security framework — P256/384/521
SigningvsKeyAgreement(ECDSA / ECDH),Curve25519Signing/KeyAgreement(Ed25519 / X25519), andSecKeyCreateRandomKeykSecAttrKeyTypeRSA/EC..swiftadded to the C-style comment table and toDetectorLanguage. - Pulumi — the
pulumi-tlsprovider'stls.PrivateKeyacross TS/JS/Python/Go, gated to a pulumi-tls marker, classifying thealgorithmvalue (RSA/ECDSA/ED25519). - All three validated against real OSS repos with zero false positives, and pinned by new labeled benchmark corpus cases.
- 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-kexfired on algorithm names listed inside a Terraform/Packerdescription = "…"(6 hits on one line). Adescription/help/doc/commentfield 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.
- webcrypto × jose / jwk × jwt. A quoted
RSA-OAEP/ECDH-ESnext to asubtle.*(call is owned by the WebCrypto detector; analginside a JWK object (a"kty"in scope) is owned by the JWK detector — thejwt-josedetector defers in both cases (mirroring the existingjose→jwkguard).RSA1_5is still reported (WebCrypto doesn't match it). - Java: ECIES (EC encryption, HNDL);
SSLContext.getInstance("TLSv1.1"/"SSLv2"); thenew X448KeyPairGenerator()/Ed448KeyPairGenerator/X448PrivateKeyParametersforms. C#:SecurityAlgorithms.*Sha256Signatureconstants; certificate pinning no longer flagged as a disabled validator, while=> true,=> { return true; }, anddelegate { return true; }still are. Rust: qualifiedx448::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_deriveon an HKDF/scrypt/TLS1-PRF context andEVP_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-ciphernow 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); terraformtls_private_keyED25519 and the modernaws_kms_key key_specalias; databasePGSSLMODE/MySQLssl-mode/YAMLsslmode:forms; ansible X25519/X448/Ed25519/DSA; CloudFrontSSLv3; 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.p12exports).
- 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 duplicatebom-refwhose copy lackscryptoProperties. CBOMserialNumberis 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
ENOENTreports the actual missing path (a--policy/--write-baselinefailure is no longer blamed on the scan path);--mergewithout--format cbomis 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.
- Removed three unused exported symbols (
ToolInputSchema,SuccessResponse,writeLine) and the deadJsonValuetype; fixed two unreachable/redundant branches (cosignsign-blobalternation, a redundantCustomerMasterKeySpecfast-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'sdeclarationemit +noUnusedLocalsverify nothing external depended on them.
- 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.
- Committed-keystore detection (
keystore) — JKS/JCEKS (magic0xFEEDFEED/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.tsisKeystorePath, 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/.pgpOpenPGP 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'smergeCbomsso 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.pkper 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): aByteEncode₁₂of in-range coefficients passes the server's encaps modulus check; the throwaway secret is never computed. Newmlkem768.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 onS. 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_endpointtool — exposes qProbe to AI agents; the qprobe plane is dynamically imported so the server stays offline until invoked, the ownership attestation is enforced (refuses unlessi_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 viaQUANTAKRYPTO_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 correctedmq-classical-cipheralgorithm labelling / legacy-TLShndlmessages. - 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-managerprivateKey.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 libpqsslmodewithout 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/agentand 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 SSHKEXINITalgorithms, and reports the negotiated reality without ever modifying an endpoint ("engine disposes"). Ships aTHREAT-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 theqprobe-*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.6cryptographic-assetdocuments that merge viabom-link, and every readiness score comes from the samebuildInventorymath. mergeCboms(boms)in@quantakrypto/core— merges multiple CycloneDX 1.6 CBOMs into one combined bill of materials, unioning components by their deterministicbom-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 . --cbomandqprobe … --cbom, thenmergeCboms([code, endpoints]).- Three more infra detectors in
@quantakrypto/core(surfaced automatically by qScan / Action / MCP):cloudformation(AWS CloudFormation / Azure ARM / Bicep-JSON:AWS::KMS::KeyKeySpec RSA/ECC, ACMKeyAlgorithm, CloudFront/ELB legacy TLS policies, ARM Key Vault key type),mesh(Linkerd ECDSA identity issuer, Consul Connect CA key type, Istio classicalcipherSuites), anddnssec(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:tlsdoes 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_configKexAlgorithmslines that offer no PQC hybrid KEX — a server that already listssntrup761x25519/mlkem768x25519stays 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— Ansiblecommunity.cryptoopenssl_privatekey/csrtype: RSA/ECC.age— a committedAGE-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:transitkey types (rsa-*,ecdsa-p*,ed25519) andpkirolekey_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
netprobes (SSH, TLS-hybrid, SMTP) added noclose/endhandler, 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 syncedversion.tsso JSON/SARIF/CBOM report the correct tool version.
-
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), theX509hex package (X509.PrivateKey.new_rsa/new_ec), and erlang-jose (JOSE.JWK.generate_key({:rsa|:ec|:okp, …})). Symmetric/MAC:cryptocalls 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_newclassified by itsOPENSSL_KEYTYPE_*, defaulting to RSA;openssl_public_encrypt/_private_decrypt;openssl_sign/_verify), phpseclib3RSA/EC/DSA/DH::createKey, and libsodiumsodium_crypto_box/kx(X25519) +sodium_crypto_sign(Ed25519). The key-type window is bounded to the current statement so oneopenssl_pkey_newcan'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 CommonJSconst { createECDH: mk } = require(...)destructure-rename, for the keygen / ECDH / DH constructors. - Python — module aliases (
from ...asymmetric import rsa as _rsa, comma-separated specifiers, and PyCryptodomeimport ...RSA as _R), resolving_rsa.generate_private_key(/_ec.ECDSA(/_ec.ECDH(/_R.generate(. - Rust — braced/renamed
use x25519_dalek::{EphemeralSecret as MontgomerySecret}(andx448/ed25519_dalek), resolving the aliased type's construction call. Also adds thex448crate to the catalog.
Together these lift recall's
aliasedbucket 0.32 → 0.47 and overall recall 0.824 → 0.847, with precision held at 1.000. - JS/TS —
-
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/GenerateDataKeyPairwith aKeySpec/KeyPairSpec/ legacyCustomerMasterKeySpecofRSA_*orECC_*. One lexical rule catches every SDK language (JS/TS, Python/boto3, Java, Go, CLI, JSON) and both thekey: valand quoted-JSON"key": valforms; the case- sensitive PascalCase field name means it never double-counts with Terraform's snake_casecustomer_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 KMScustomer_master_key_spec(RSA_*/ECC_*), Google Cloud KMSRSA_SIGN_*/EC_SIGN_*algorithm strings, and Azure Key Vaultkey_type(RSA/EC, incl.-HSM). Matches both HCL and.tf.jsonsyntax; gated to.tf/.tf.jsonso 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 offcrvso a single key is counted once; symmetricoctkeys 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_*, andwc_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; distinctivembedtls_*/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 incomment-planmode) 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. -
qscanhuman 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).
- 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
verifyFixthe 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 indocs/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
triageFntest hook torunQscanso the--triagepath 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_EXAMPLESmeta-test. Every canned before/after example theget_fix_examplestool 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.
- Agent-line exfiltration-guard bypass — the
qremediateblast-radius guard (NEW_SINK_RE) matched only barerequire("http")/ dynamicimport("http"), so a hostile or prompt-injected LLM patch addingrequire("node:https")or a staticimport { request } from "node:net"sink was accepted (verified end-to-end). It now matches thenode:prefix, static-import forms, andtls/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 ('ano 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. secp256k1key 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_deltaNaN — validates itsbefore/afterfindings arrays (mirroring the triage/remediate tools) instead of emittingNaNreadiness scores on malformed input.
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.
- 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.tsmeasures 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 —canonical0.81 vsaliased0.32 /adversarial0.37 — quantifies the lexical ceiling and names the closable gaps. Seedocs/validation/recall-benchmark.md. tls-classical-kexdetector (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 RustAlgorithm::RS256/ES256identifier forms; libsodiumcrypto_sign_ed25519_keypair/crypto_kx/scalarmult(C); theed25519+rbnaclRuby gems; BouncyCastleEd25519/X25519/X448/DH lightweight classes (Java + C#, incl. the bare Kotlin-constructor form); and thecloudflare/circl+ decredsecp256k1/v4Go 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 likego-jwt-signingmethodno 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.
qscan --tier category-3|category-5— CNSA security-tier migration targets in the report footer (formatTierGuidance), making the previously library-onlyremediationForTierreachable (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
opensslcrate + 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; Pythonec.ECDH(). - Supply-chain CI — OpenSSF Scorecard workflow; a zero-runtime-dependency
enforcement gate (
scripts/check-zero-deps.mjs);reuse lint(advisory);dependabot.yml; per-packageLICENSE. 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. MCPapply_verified_patch(runs the patch-policy + verify + blast-radius gates offline). ACVP vector-provenance in Sieve reports (raw-byte hashes + declared source, so akatPASS is traceable). Agent entropy-based redactor catch-all.
- inventory + CBOM + a deterministic content hash; external signer fills the
attestation). NuGet dependency ecosystem (
- Agent line (from the adversarial audit):
qremediate --llmnow rejects LLM patches that add a network/exec sink or rewrite >60 lines and holds them back fromapplywithout--apply-llm; "verified" reworded to "crypto-verified, not security-reviewed". Rubric moved to the providersystemrole + 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); Goecdh.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).
- The
v1Action tag is auto-moved to the released commit on publish (was stale); per-job workflow permissions scoped;persist-credentials: falseon CI checkouts;inlineSourcesso published sourcemaps aren't dangling.
Multi-expert audit-hardening pass across all packages; Action dist/ re-bundled.
First published to npm under the @quantakrypto scope with build provenance.
- Hardening fixes from a multi-discipline review (redactor coverage, readiness scoring, remediation edge cases); build clean, benchmark 1.000, zero runtime deps.
qremediatenow fully fixes files with multiple TLS issues in one pass; added the end-to-end testing runbook (docs/how-to-test-0.4.md).
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.
- core (detectors): language packs for Python, Go, Java/Kotlin, C#, Rust,
Ruby, and C/OpenSSL — the
DetectorRegistrynow 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-fetchadapters 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) andqremediate— deterministic codemod fixes (--mode diff|apply), plus--llmand--mode pr(draft-PR), all verify-gated and worktree-isolated with no auto-merge. - mcp: deterministic
triage_findings/apply_triageandremediate_findings— request/apply tools that stay offline and key-free (the host agent reasons; the server never calls a provider). - action:
comment-planmigration-plan PR comment;dist/re-bundled.
- This is the first release published to npm; earlier versions were tagged but the packages went public here.
- 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, …).
- The
qscanCLI was a silent no-op vianpx/ the.binsymlink / macOS/tmp— the main-guard comparedimport.meta.urlto the unresolvedargv[1]. Resolve symlinks like the MCP stdio guard does; added a symlink smoke test.
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.
- core: shared canonical baseline (
fingerprintFinding/applyBaseline/…);DetectorRegistry+Detector.scope/language; wiredScanOptions(include/files/scanMinified); new detectors (DH MODP, SSH keys, TLS certificate signature algorithms, JOSEECDH-ES*, one-shotsign/verify,secp256k1); CWE tags on findings; CycloneDX CBOM export (toCbom);scanParallelworker pool;changedFilesincremental 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--cbomoutput. - mcp: safe-by-default HTTP transport (loopback bind,
QUANTAKRYPTO_MCP_TOKENauth, filesystem tools gated behindQUANTAKRYPTO_MCP_ALLOW_FS, per-request timeout + response cap);generate_cbomtool. - 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.
- core/qscan:
quantakrypto.config.jsonsupport —loadConfigin core plus flags > config > defaults precedence in qScan, with--config <path>and--no-config-file(distinct from the--no-configdetector 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'stest/fuzz.test.ts. [P1-10] - repo: a zero-dep
.githooks/pre-commithook (build → lint → format:check → test) [P2-5];scripts/validate-sarif.mjsSARIF 2.1.0 structural validator +validate:sarifscript [P2-6]; and advisorybench+ gatingsarifCI jobs.
- 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_findingresolves 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
runQscanand 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.
@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/.