Skip to main content

Post-quantum cryptography risk scanner for source code and dependency graphs.

Project description

pqcheck

CI PyPI Python License Sigstore

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, or getattr-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/.pqcheckignore are honored.
  • Dependency findings are inventory (introduces metadata 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pqcheck-0.1.0-py3-none-win_amd64.whl (4.2 MB view details)

Uploaded Python 3Windows x86-64

pqcheck-0.1.0-py3-none-musllinux_1_2_x86_64.whl (4.2 MB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

pqcheck-0.1.0-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (4.2 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64manylinux: glibc 2.5+ x86-64

pqcheck-0.1.0-py3-none-macosx_11_0_x86_64.whl (4.2 MB view details)

Uploaded Python 3macOS 11.0+ x86-64

pqcheck-0.1.0-py3-none-macosx_11_0_arm64.whl (4.0 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

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

Hashes for pqcheck-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 83dc7bd5e1d65710bda486863a98b56fe9163fa754dd14ee4d8c0a0e7422a572
MD5 770cdfa821c24d977a485336a957b546
BLAKE2b-256 c13ce267e18271a3e00ed0b67a159e7b9756d68eb386f16f5c2481427a2b17a9

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pqcheck-0.1.0-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pqcheck-0.1.0-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 2772d358371c38533bc3aeabaa2feea09c751861a5c13de47906276be2295454
MD5 00722a2483a17460dc926854408535dc
BLAKE2b-256 932de2b995267756845acf9145c2f00b2a7e4e063df30b6fae88646475b6967c

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

File hashes

Hashes for pqcheck-0.1.0-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
Algorithm Hash digest
SHA256 7aabbcc40161b6a09c70dc14020d45a6fd16046d9d331c001aed209a9dd7c8b5
MD5 1a0551f870932b0fe0c4d0f82655c581
BLAKE2b-256 0e7442021eca1145824289ce0430854f6c8ad2259d67cec2075ac943bbd54d5e

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pqcheck-0.1.0-py3-none-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for pqcheck-0.1.0-py3-none-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 94ad45fb3bef814419fdd58882690f68db9ea85181fef028364bc865ce7feb23
MD5 e840c931b5f288b009f3547a99170f82
BLAKE2b-256 eae4ca11edfca46483e833756ffd572fd2e7ef84479bd65e920db2b42a776110

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pqcheck-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pqcheck-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 404d05911c15f60df69491801ed06f5c4cbbe9a4691d344a2b7eb1ef3a4b0c19
MD5 4a18bde83d3fe4bbc053a4ad5f671d29
BLAKE2b-256 bda71d0009c4cec8abf2bbf319996cfe29a1b0dbb0167273ae5e1e9566993b03

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page