Post-quantum cryptography risk scanner for source code and dependency graphs.
Project description
pqcheck
Generate a Cryptography Bill of Materials (CBOM) for your codebase in seconds, and gate CI on a crypto policy you can read.
pqcheck scans source code and dependency lockfiles for cryptographic
algorithm usage — RSA, ECDSA, AES modes, legacy hashes, post-quantum
primitives — and emits a CycloneDX 1.6 CBOM plus SARIF 2.1.0, evaluated
against a versionable YAML policy. It runs on a laptop and in CI with no
server, no account, and no network access during the scan.
Why now: NIST IR 8547 deprecates RSA and ECC by 2030 and disallows them
by 2035. Every migration framework (OMB M-23-02, the EU coordinated
roadmap, FS-ISAC guidance) makes inventory the first step. The existing
open tooling for that step needs a SonarQube server; the commercial
options start at enterprise pricing. This is the pip install version.
Status: pre-release. v0.1.0 lands on PyPI with Sigstore-signed
wheels. Until then: uv sync --all-extras && uv run pqcheck --help.
Leia em português.
Quickstart
$ pqcheck scan ./your-repo --policy cryptoct-default
✗ src/auth.py:42 — RSA [banned/critical, confidence high] — Shor — migrate to ML-KEM-768
⚠ src/hash.py:7 — BLAKE2B [default/medium, confidence high]
✓ src/aead.py:12 — AES [approved/info, confidence high]
dependencies: 47 parsed
policy cryptoct-default-1.0.0: 1 fail, 1 warn, 1 allow
Machine-readable outputs:
$ pqcheck scan . --policy cryptoct-default --format cbom -o cbom.cdx.json
$ pqcheck scan . --format sarif -o pqcheck.sarif # imports into GitHub Code Scanning
Gate CI (exit 1 when the policy trips):
$ pqcheck scan . --policy br-bcb-conservative --fail-on policy
$ pqcheck self-audit # fixed policy, CBOM always written
--fail-on high gates on base severity instead; --strict treats WARN
decisions as failures. A finding's displayed severity is demoted by
detection confidence, but gating always reads the base severity — low
confidence never lets RSA through.
Verify a release yourself (needs the sigstore extra; the .sigstore.json
bundle ships next to each wheel on the GitHub release):
$ pqcheck verify-release pqcheck-0.1.0-py3-none-any.whl
OK: pqcheck-0.1.0-py3-none-any.whl verified against pqcheck-0.1.0-py3-none-any.whl.sigstore.json
What it detects
| Surface | Coverage |
|---|---|
| Python source | stdlib hashlib, cryptography (current + legacy + decrepit paths), pycryptodome — via AST, no execution |
| Go source | stdlib crypto/* and golang.org/x/crypto — union of a bundled go/types analyzer (semantic: key sizes, curves, modes) and a tree-sitter pass that also covers GOOS/cgo-gated files, which type resolution cannot see by construction |
| Lockfiles | pyproject.toml, uv.lock, requirements.txt, pom.xml, go.mod+go.sum, package-lock.json |
Java is next on the roadmap; it is not in v0.1.
Policies
Six bundled policies, all plain YAML you can fork:
cryptoct-default/cryptoct-strict/cryptoct-advisory— the general tiers (fail / fail-hard / report-only).br-bcb-conservative— for Brazilian financial institutions aligning their inventory with the cybersecurity controls of Res. CMN 4.893/2021.br-drex-piloto— the strictest profile, for teams that want new code quantum-safe by construction.br-vendor-dd— advisory profile for vendor due-diligence annexes.
To be clear about the regulatory framing: no Brazilian regulation currently mandates a cryptographic inventory or PQC migration. The BR profiles anticipate that direction; they do not claim an obligation that does not exist.
pqcheck policy show <name> prints any of them resolved;
pqcheck policy validate <file> checks your own against the schema.
Measured precision and recall
Every HIGH/CRITICAL finding across a 10-repo public corpus (pyjwt,
paramiko, sigstore-python, age, go-jose, smallstep/crypto, …) was
human-adjudicated by reading the flagged line: 255 findings, 0 false
positives. Recall on the same repos, against a ground truth of 527
adjudicated call sites, is 1.00. The honest caveat: that corpus's miss
list drove the catalog expansion, so it is a tuning set and says
nothing about generalization. A held-out set of 6 unseen repos
(authlib, borgbackup, certbot, cosign, wireguard-go, certmagic)
measured recall 0.989 (273/276; the 3 misses are borgbackup's
Cython/OpenSSL binding, outside the import-resolution model) and
precision 0.99 (76/77). One round of fixes came from that miss list,
so the held-out set is no longer strictly untouched either — future
generalization claims need fresh repos. Protocols, pinned SHAs, and
verdicts are in tests/corpus/.
Known limitations
pqcheck is a static, single-repo scanner, and these gaps follow from that design:
- No dataflow analysis: an algorithm reached through a variable,
parameter, or lookup table is either missed or reported as a generic
low-confidence finding — e.g.
.digest()called on a hash object passed in as a parameter, or a cipher picked from a dict at runtime. authlib is the clearest example in the held-out corpus. - No C-extension or FFI visibility: crypto implemented behind an in-repo Cython or C binding, like borgbackup's OpenSSL binding, is invisible to source-level detection — only the Python or Go call surface is scanned.
- Catalog-scoped detection: pqcheck flags what's in its curated Python and Go catalogs. A library or symbol outside the catalog produces no findings, and no findings isn't evidence of no crypto.
- Static analysis only: there's no runtime or dynamic-dispatch
resolution.
hashlib.new(name_var)with a name computed at runtime, orgetattr-based dispatch, resolve to nothing. - Usage-context blindness: a symbol match carries no intent. "RSA
verifying a third-party webhook" reads the same as "RSA encrypting
data at rest," and an
ECDH()call used only for key-format conversion gets flagged the same as a real key agreement (one known false positive in the held-out set). Context-scoped policy rules are parsed but not shipped in the bundled policies until detectors emit context. - Hybrid-scheme detection covers
filippo.io/hpke(X25519MLKEM768) only; other hybrid constructions read as their classical component. - Only the repo root's
.gitignore/.pqcheckignoreare honored. - Dependency findings are inventory (
introducesmetadata in the CBOM); they do not trip the policy gate in v0.1 — call sites do.
See tests/corpus/ for the adjudication protocol behind these numbers.
Development
Requires Python 3.12+ and uv:
uv sync --all-extras, then uv run pytest, uv run ruff check .,
uv run mypy. See CONTRIBUTING.md.
License
Apache-2.0. See SECURITY.md for the vulnerability disclosure process and the honest list of classical crypto this project itself transitively depends on.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pqcheck-0.1.0-py3-none-win_amd64.whl.
File metadata
- Download URL: pqcheck-0.1.0-py3-none-win_amd64.whl
- Upload date:
- Size: 4.2 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83dc7bd5e1d65710bda486863a98b56fe9163fa754dd14ee4d8c0a0e7422a572
|
|
| MD5 |
770cdfa821c24d977a485336a957b546
|
|
| BLAKE2b-256 |
c13ce267e18271a3e00ed0b67a159e7b9756d68eb386f16f5c2481427a2b17a9
|
Provenance
The following attestation bundles were made for pqcheck-0.1.0-py3-none-win_amd64.whl:
Publisher:
cli-release.yml on Rafael-Ryu/pqcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pqcheck-0.1.0-py3-none-win_amd64.whl -
Subject digest:
83dc7bd5e1d65710bda486863a98b56fe9163fa754dd14ee4d8c0a0e7422a572 - Sigstore transparency entry: 2163921970
- Sigstore integration time:
-
Permalink:
Rafael-Ryu/pqcheck@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Branch / Tag:
refs/tags/pqcheck-v0.1.0 - Owner: https://github.com/Rafael-Ryu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cli-release.yml@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Trigger Event:
push
-
Statement type:
File details
Details for the file pqcheck-0.1.0-py3-none-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: pqcheck-0.1.0-py3-none-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 4.2 MB
- Tags: Python 3, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2772d358371c38533bc3aeabaa2feea09c751861a5c13de47906276be2295454
|
|
| MD5 |
00722a2483a17460dc926854408535dc
|
|
| BLAKE2b-256 |
932de2b995267756845acf9145c2f00b2a7e4e063df30b6fae88646475b6967c
|
Provenance
The following attestation bundles were made for pqcheck-0.1.0-py3-none-musllinux_1_2_x86_64.whl:
Publisher:
cli-release.yml on Rafael-Ryu/pqcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pqcheck-0.1.0-py3-none-musllinux_1_2_x86_64.whl -
Subject digest:
2772d358371c38533bc3aeabaa2feea09c751861a5c13de47906276be2295454 - Sigstore transparency entry: 2163921879
- Sigstore integration time:
-
Permalink:
Rafael-Ryu/pqcheck@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Branch / Tag:
refs/tags/pqcheck-v0.1.0 - Owner: https://github.com/Rafael-Ryu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cli-release.yml@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Trigger Event:
push
-
Statement type:
File details
Details for the file pqcheck-0.1.0-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.
File metadata
- Download URL: pqcheck-0.1.0-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
- Upload date:
- Size: 4.2 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64, manylinux: glibc 2.5+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7aabbcc40161b6a09c70dc14020d45a6fd16046d9d331c001aed209a9dd7c8b5
|
|
| MD5 |
1a0551f870932b0fe0c4d0f82655c581
|
|
| BLAKE2b-256 |
0e7442021eca1145824289ce0430854f6c8ad2259d67cec2075ac943bbd54d5e
|
Provenance
The following attestation bundles were made for pqcheck-0.1.0-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl:
Publisher:
cli-release.yml on Rafael-Ryu/pqcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pqcheck-0.1.0-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl -
Subject digest:
7aabbcc40161b6a09c70dc14020d45a6fd16046d9d331c001aed209a9dd7c8b5 - Sigstore transparency entry: 2163921836
- Sigstore integration time:
-
Permalink:
Rafael-Ryu/pqcheck@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Branch / Tag:
refs/tags/pqcheck-v0.1.0 - Owner: https://github.com/Rafael-Ryu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cli-release.yml@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Trigger Event:
push
-
Statement type:
File details
Details for the file pqcheck-0.1.0-py3-none-macosx_11_0_x86_64.whl.
File metadata
- Download URL: pqcheck-0.1.0-py3-none-macosx_11_0_x86_64.whl
- Upload date:
- Size: 4.2 MB
- Tags: Python 3, macOS 11.0+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94ad45fb3bef814419fdd58882690f68db9ea85181fef028364bc865ce7feb23
|
|
| MD5 |
e840c931b5f288b009f3547a99170f82
|
|
| BLAKE2b-256 |
eae4ca11edfca46483e833756ffd572fd2e7ef84479bd65e920db2b42a776110
|
Provenance
The following attestation bundles were made for pqcheck-0.1.0-py3-none-macosx_11_0_x86_64.whl:
Publisher:
cli-release.yml on Rafael-Ryu/pqcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pqcheck-0.1.0-py3-none-macosx_11_0_x86_64.whl -
Subject digest:
94ad45fb3bef814419fdd58882690f68db9ea85181fef028364bc865ce7feb23 - Sigstore transparency entry: 2163921783
- Sigstore integration time:
-
Permalink:
Rafael-Ryu/pqcheck@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Branch / Tag:
refs/tags/pqcheck-v0.1.0 - Owner: https://github.com/Rafael-Ryu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cli-release.yml@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Trigger Event:
push
-
Statement type:
File details
Details for the file pqcheck-0.1.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: pqcheck-0.1.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 4.0 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
404d05911c15f60df69491801ed06f5c4cbbe9a4691d344a2b7eb1ef3a4b0c19
|
|
| MD5 |
4a18bde83d3fe4bbc053a4ad5f671d29
|
|
| BLAKE2b-256 |
bda71d0009c4cec8abf2bbf319996cfe29a1b0dbb0167273ae5e1e9566993b03
|
Provenance
The following attestation bundles were made for pqcheck-0.1.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
cli-release.yml on Rafael-Ryu/pqcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pqcheck-0.1.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
404d05911c15f60df69491801ed06f5c4cbbe9a4691d344a2b7eb1ef3a4b0c19 - Sigstore transparency entry: 2163921925
- Sigstore integration time:
-
Permalink:
Rafael-Ryu/pqcheck@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Branch / Tag:
refs/tags/pqcheck-v0.1.0 - Owner: https://github.com/Rafael-Ryu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cli-release.yml@c0ccf5be96e71add49c9d9865b020a279d1f1a4a -
Trigger Event:
push
-
Statement type: