Snyk for Post-Quantum Cryptography β a developer-first static analysis tool that finds quantum-vulnerable cryptography in your code, configs, and dependencies before quantum computers (or your auditors) do.
pqc-scan
is zero-friction and lives where your code lives: in your editor, on the command line, and right inside your pull requests. It parses real source with tree-sitter ASTs (not brittle regexes), tells you exactly which line is vulnerable, why it matters, and how to migrate to a NIST-standardized post-quantum algorithm.
pqc-scan scan .
pqc-scan Β· Post-Quantum Cryptography scan
β CRITICAL PQC001 Β· RSA Key Generation [high confidence]
ββ app/keys.py:10:19 (RSA-2048)
β rsa.generate_private_key(
β public_exponent=65537,
β key_size=2048,
β )
ββ RSA key generation detected. RSA is broken by Shor's algorithm on a
cryptographically relevant quantum computer, regardless of key size.
Migrate to: ML-KEM-768 (CRYSTALS-Kyber) β¦ Β· FIPS 203 (ML-KEM) / FIPS 204 (ML-DSA)
see: https://csrc.nist.gov/pubs/fips/203/final
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Critical: 2 High: 2 Medium: 0 Low: 0 | Total findings: 4
Files scanned: 1 | Time: 0.00s
The public-key cryptography that secures almost everything online β TLS handshakes, SSH, code signing, JWTs, certificates, VPNs β relies on math problems that are hard for classical computers. Shor's algorithm, run on a cryptographically relevant quantum computer, solves all of them efficiently. That breaks, completely:
RSA(any key size, including RSA-4096)** Elliptic-curve crypto**: ECDSA, ECDH/ECDHE, X25519/X448, Ed25519/Ed448** Finite-field crypto**: Diffie-Hellman (DH/DHE), DSA
Symmetric crypto and hashing are weakened but not broken: Grover's algorithm only halves the effective security level, so AES-256 and SHA-256/SHA3-256 remain safe.
You don't need a quantum computer to exist today to be at risk. Adversaries can capture encrypted traffic now and decrypt it later once quantum hardware matures. Any data with a long confidentiality lifetime β health records, financial data, state secrets, long-lived credentials β is already exposed. Key-exchange material (ECDH, X25519, DH) is the prime target.
August 2024β NIST finalized the first post-quantum standards:** FIPS 203 (ML-KEM)for key encapsulation, FIPS 204 (ML-DSA)and FIPS 205 (SLH-DSA)for digital signatures. 2030 / 2035**β US federal guidance (CNSA 2.0, NSM-10) sets a migration deadline: begin now, complete the transition for most systems by2030, finish by 2035.
Migration is a multi-year inventory-and-replace effort. pqc-scan
is the inventory step you can run on every commit.
pqc-scan
requires Python β₯ 3.10.
Install editable from a clone of the source:
git clone https://github.com/pqc-scan/pqc-scan.git
cd pqc-scan
pip install -e .
This installs the pqc-scan
console script. Verify:
pqc-scan --version
For development (tests + coverage):
pip install -e ".[dev]"
pytest
All scanning dependencies (tree-sitter grammars for Python, JavaScript, Java, Go, plus Typer, Rich and PyYAML) are installed automatically β there is no native toolchain to build.
pqc-scan scan . # scan the current tree, pretty console report
pqc-scan scan src/ -s high # only report HIGH and CRITICAL findings
pqc-scan scan . -o sarif -f out.sarif # write SARIF for GitHub code scanning
pqc-scan scan . --changed-only # only files changed in the current git diff
pqc-scan rules # list every detection rule
pqc-scan init # write a starter .pqcscan.yml
pqc-scan
exposes four commands: scan
, report
, init
, and rules
.
pqc-scan scan [PATH] [OPTIONS]
PATH
defaults to .
(the current directory) and may be a file or a directory.
| Flag | Alias | Description |
|---|---|---|
--output |
||
-o |
||
Output format: console (default), sarif , cbom , json . |
||
--output-file |
||
-f |
||
| Write output to this file instead of stdout. | ||
--severity |
||
-s |
||
Minimum severity to report: critical , high , medium , low . |
||
--exclude |
||
| Glob pattern to exclude. Repeatable. | ||
--changed-only |
||
| Only scan files changed in the current git diff (fast PR scans). | ||
--config |
||
Path to a .pqcscan.yml config file. |
||
--no-color |
||
| Disable colored output (auto-disabled when writing to a file). | ||
--fail-on-findings |
||
Exit with code 1 if any findings are reported β for CI gating. |
||
--limit N |
||
Show at most N findings in console output (0 = all). |
||
--summary |
||
| Console output: totals and a per-file breakdown only, no per-finding detail. | ||
--group-by |
||
Console grouping: severity (default) or file . |
Examples:
pqc-scan scan . --fail-on-findings
pqc-scan scan . --exclude "**/tests/**" --exclude "**/third_party/**"
pqc-scan scan src/ -o json
pqc-scan scan . --changed-only -s high --fail-on-findings
pqc-scan scan . --summary
pqc-scan scan . --group-by file --limit 50
Example console output (scanning a single file):
pqc-scan Β· Post-Quantum Cryptography scan
β CRITICAL PQC001 Β· RSA Key Generation [high confidence]
ββ app/keys.py:10:19 (RSA-2048)
β rsa.generate_private_key(
β public_exponent=65537,
β key_size=2048,
β )
ββ RSA key generation detected. RSA is broken by Shor's algorithm on a
cryptographically relevant quantum computer, regardless of key size.
Migrate to: ML-KEM-768 (CRYSTALS-Kyber) for encryption / key
establishment, or ML-DSA-65 β¦ Β· FIPS 203 (ML-KEM) / FIPS 204 (ML-DSA)
see: https://csrc.nist.gov/pubs/fips/203/final
β HIGH PQC002 Β· RSA Encryption / Padding [high confidence]
ββ app/keys.py:26:9 (RSA-OAEP)
β padding.OAEP(
β mgf=padding.MGF1(algorithm=hashes.SHA256()),
β algorithm=hashes.SHA256(),
β label=None,
β )
ββ RSA-based encryption or padding (OAEP / PKCS1v15) detected. RSA encryption
is broken by Shor's algorithm.
Migrate to: ML-KEM-768 (CRYSTALS-Kyber) Β· FIPS 203
see: https://csrc.nist.gov/pubs/fips/203/final
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Critical: 2 High: 2 Medium: 0 Low: 0 | Total findings: 4
Files scanned: 1 | Time: 0.00s
report
runs a scan and writes a machine-readable artifact to disk. Unlike scan
,
the format defaults to cbom
and --output-file
is required.
pqc-scan report [PATH] --output-file FILE [--format cbom|sarif|json] [--config FILE]
pqc-scan report . --output-file cbom.json
pqc-scan report . --format sarif --output-file results.sarif
pqc-scan init [PATH] [--force]
Writes a starter .pqcscan.yml
to PATH
(default .
). Refuses to overwrite an
existing file unless --force
is given.
pqc-scan init
pqc-scan rules
Prints a table of every rule (ID, name, severity, category, description).
pqc-scan
ships 14 rules, covering quantum-vulnerable key generation, signatures, encryption, key exchange, hashing, weak JWT/TLS configuration, legacy ciphers, and quantum-vulnerable dependencies.
| ID | Name | Severity | Category |
|---|---|---|---|
| PQC001 | |||
| RSA Key Generation | critical |
||
| key-generation | |||
| PQC002 | |||
| RSA Encryption / Padding | high |
||
| encryption | |||
| PQC003 | |||
| RSA Signature | critical |
||
| signing | |||
| PQC004 | |||
| ECDSA Key Generation or Signing | critical |
||
| signing | |||
| PQC005 | |||
| ECDH / X25519 Key Exchange | high |
||
| key-exchange | |||
| PQC006 | |||
| Ed25519 / Ed448 Key Generation | high |
||
| signing | |||
| PQC007 | |||
| Diffie-Hellman Key Exchange | high |
||
| key-exchange | |||
| PQC008 | |||
| DSA Key Generation or Signing | critical |
||
| signing | |||
| PQC009 | |||
| SHA-1 Usage | medium |
||
| hashing | |||
| PQC010 | |||
| MD5 Usage | high |
||
| hashing | |||
| PQC011 | |||
| Weak JWT Algorithm (RS/ES/PS) | high |
||
| signing | |||
| PQC012 | |||
| Weak TLS Configuration | medium |
||
| configuration | |||
| PQC013 | |||
| DES / 3DES Usage | high |
||
| encryption | |||
| PQC014 | |||
| Quantum-Vulnerable Dependency | medium |
||
| dependency |
Run pqc-scan rules
for the full descriptions and to confirm the set installed on your machine.
Supported languages (code): Python, JavaScript/TypeScript, Java, Go.
Supported manifests/configs: requirements.txt
, package.json
, plus
YAML / JSON / TOML / .conf
configuration files (TLS, JWT, cipher lists).
Library coverage highlights (beyond the language standard libraries):
Pythonβcryptography
(hazmat), pycryptodome/pycrypto (RSA.generate
,pkcs1_15
,pss
,DSS
,PKCS1_OAEP
), pyOpenSSL (TYPE_RSA
), paramiko, PyJWT / python-jose,ssl
,hashlib
(includinghashlib.new("sha1")
and theusedforsecurity=False
demotion tolow
).JavaScriptβ Nodecrypto
(generateKeyPair
,createDiffieHellman
,publicEncrypt
,createSign
), WebCryptoSubtleCrypto
,jsonwebtoken
,jose
(setProtectedHeader
), node-forge, AWS KMS asymmetricKeySpec
s.Javaβ JCA factories (KeyPairGenerator
,Signature
,Cipher
,MessageDigest
,KeyAgreement
), Bouncy Castle lightweight API (RSAKeyGenerationParameters
,ECDSASigner
,Ed25519Signer
, β¦), andSSLContext.getInstance
with legacy protocols.Goβcrypto/rsa
,crypto/ecdsa
,crypto/ecdh
,crypto/ed25519
,crypto/dsa
,crypto/tls
configuration (MinVersion
pins and weakCipherSuites
),x/crypto/curve25519
, and golang-jwt signing methods.
Context hints. Findings inside code that looks like crypto-library
plumbing (paths containing hazmat
, _internal
, backends
, vendor
, β¦) or
inside a generate_*_key()
-style wrapper carry a context_hint
explaining
whether the call site is actionable for you or belongs to a library you merely
consume. Hints appear in console output, SARIF properties.contextHint
, and
the JSON context_hint
field.
Choose with -o
/--output
on scan
, or --format
on report
.
A colorized, human-readable report with severity badges, the exact code snippet, the
migration target, and the relevant NIST standard. Color is auto-disabled when writing
to a file or with --no-color
.
Static Analysis Results Interchange Format 2.1.0. Upload it with the CodeQL action and findings appear as inline annotations on the exact line in pull requests and in the repository's Security β Code scanning tab.
pqc-scan scan . -o sarif -f pqc-scan.sarif
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/.../sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": { "driver": { "name": "pqc-scan", "version": "0.1.0", "rules": [ ... ] } },
"results": [ ... ]
}
]
}
A CycloneDX 1.6 CBOM: a structured inventory of every cryptographic asset found,
emitted as cryptographic-asset
components. Ideal for compliance, supply-chain attestation, and tracking migration progress over time.
pqc-scan report . --format cbom --output-file cbom.json
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"components": [
{ "type": "cryptographic-asset", "name": "RSA-2048", "...": "..." }
]
}
Scan metadata plus every finding with full migration metadata β easy to pipe into
jq
, dashboards, or custom tooling.
pqc-scan scan . -o json | jq '.findings[] | {rule_id, severity, file_path, line_number, algorithm}'
{
"tool": "pqc-scan",
"version": "0.1.0",
"generated_at": "2026-07-06T12:00:00Z",
"paths": ["/repo"],
"summary": {
"total": 4,
"by_severity": { "critical": 2, "high": 2, "medium": 0, "low": 0 },
"files_scanned": 128,
"duration_seconds": 0.4211,
"errors": []
},
"findings": [
{
"file_path": "app/keys.py",
"line_number": 10,
"column_number": 19,
"algorithm": "RSA-2048",
"category": "key-generation",
"severity": "critical",
"confidence": "high",
"rule_id": "PQC001",
"context_hint": null,
"migration_suggestion": {
"recommended_algorithm": "ML-KEM-768 (CRYSTALS-Kyber) β¦",
"nist_standard": "FIPS 203 (ML-KEM) / FIPS 204 (ML-DSA)",
"docs_url": "https://csrc.nist.gov/pubs/fips/203/final"
}
}
]
}
Run pqc-scan
on every pull request and surface findings as inline code-scanning
annotations. Save this as .github/workflows/pqc-scan.yml
:
name: pqc-scan
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
security-events: write # required to upload SARIF to code scanning
jobs:
pqc-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run pqc-scan
uses: ./ # this repo's action; or pin: pqc-scan/pqc-scan@v1
with:
path: .
severity: high # critical | high | medium | low
output-sarif: pqc-scan.sarif
fail-on-findings: 'false' # set 'true' to block the PR on any finding
- name: Upload SARIF to GitHub code scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: pqc-scan.sarif
Action inputs
| Input | Default | Description |
|---|---|---|
path |
||
. |
||
| File or directory to scan. | ||
severity |
||
medium |
||
| Minimum severity to report. | ||
output-sarif |
||
pqc-scan.sarif |
||
| Path to write the SARIF report. | ||
fail-on-findings |
||
false |
||
| Fail the job (exit 1) if any findings are reported. |
The action exits 0
when the scan is clean; with fail-on-findings: 'true'
the SARIF is still uploaded before the job is failed, so annotations always appear.
If you prefer to run the CLI directly without the composite action, the equivalent step is just:
- run: pip install -e . && pqc-scan scan . -o sarif -f pqc-scan.sarif -s high
Pair it with --changed-only
in PR jobs to scan only the diff and keep runs fast.
pqc-scan
reads a .pqcscan.yml
file. It is discovered automatically by walking up
from the scanned path, or pointed at explicitly with --config
. Generate a starter
with pqc-scan init
. Full schema:
exclude:
- "**/tests/**"
- "**/*.test.py"
- "**/vendor/**"
- "**/node_modules/**"
severity_threshold: medium
languages:
- python
- javascript
- java
- go
scan_configs: true # Scan YAML/JSON/TOML/.conf config files
scan_dependencies: true # Scan dependency manifests (requirements.txt, package.json, ...)
rules:
disable: [] # e.g. [PQC010] to silence a specific rule
output:
default_format: console # console | sarif | cbom | json
cbom_path: cbom.json
Notes:
- CLI flags override config values (e.g.
-s high
beatsseverity_threshold
). rules.disable
takes rule IDs (PQC001
β¦PQC014
), case-insensitive.- Without a config file, the defaults exclude
**/tests/**
,**/node_modules/**
,**/.venv/**
,**/venv/**
,**/vendor/**
,**/dist/**
, and**/build/**
(thetests
glob matches test directories atany depth), and the walker never descends into.git
,site-packages
, caches, or IDE directories. - A malformed
--config
file exits with code2
; a malformeddiscoveredconfig prints a warning and falls back to defaults.
Every finding ships with concrete, NIST-aligned migration guidance and a before/after code example. The high-level mapping:
| Vulnerable today | Migrate to | NIST standard |
|---|---|---|
| RSA key generation / encryption (PQC001/002) | ML-KEM-768 (CRYSTALS-Kyber) | |
| FIPS 203 | ||
| RSA signatures (PQC003) | ML-DSA-65 (CRYSTALS-Dilithium) | |
| FIPS 204 | ||
| ECDSA / EC keys (PQC004) | ML-DSA-65 | |
| FIPS 204 | ||
| ECDH / ECDHE / X25519 (PQC005) | ML-KEM-768 | |
| FIPS 203 | ||
| Ed25519 / Ed448 (PQC006) | ML-DSA-65 (or SLH-DSA) | |
| FIPS 204 / 205 | ||
| Diffie-Hellman (PQC007) | ML-KEM-768 | |
| FIPS 203 | ||
| DSA (PQC008) | ML-DSA-65 | |
| FIPS 204 | ||
| SHA-1 (PQC009) | SHA-256 / SHA3-256 | |
| FIPS 180-4 / 202 | ||
| MD5 (PQC010) | SHA-256 / SHA3-256 | |
| FIPS 180-4 / 202 | ||
| Weak JWT RS/ES/PS256 (PQC011) | HS256 internally; track IETF JOSE for PQC | β |
| Weak TLS config (PQC012) | TLS 1.3 + hybrid X25519MLKEM768 | |
| FIPS 203 | ||
| DES / 3DES (PQC013) | AES-256-GCM | |
| FIPS 197 |
Heads-up about liboqs.The Python post-quantum libraryoqs
(liboqs-python) isnota purepip install
. It wraps the compiledliboqsC library, which must be available first:pip install liboqs
builds it from source via CMake, or you install the distro package / build from[https://github.com/open-quantum-safe/liboqs]. Hash and symmetric replacements (SHA-256, AES-256-GCM) need no special install β they live inhashlib
and thecryptography
package.
pqc-scan
uses two complementary detection strategies:
Code (Python, JavaScript/TypeScript, Java, Go): tree-sitter AST analysis. Source is parsed into a concrete syntax tree and the analyzers traverse real nodes β call expressions, imports, dotted attribute access, string and keyword arguments. This isnot regex matching: it resolves what is actually beingcalled(e.g.rsa.generate_private_key(...)
,ec.generate_private_key(...)
), so it sees through aliasing and whitespace, reports precise 1-based line/column positions, and avoids matching the same construct inside comments or strings. -
Configs and dependencies: pattern-based scanning. TLS/JWT/cipher settings in YAML/JSON/TOML/.conf
, and manifests likerequirements.txt
andpackage.json
, are matched against curated patterns of quantum-vulnerable algorithms and libraries.
Each language analyzer exposes a common contract (LANGUAGE
, EXTENSIONS
,
GRAMMAR
, analyze(...)
) and emits Finding
objects through a single shared factory, so console, SARIF, CBOM, and JSON outputs all stay perfectly consistent.
pqc-scan
is a static analyzer. It is tuned for high-confidence, low-noise detection, but a few caveats apply:
It flags presence, not necessarily exploitable risk. APQC014
dependency match means a quantum-vulnerable library is declared, not that a vulnerable code path is exercised. Treat findings as an inventory to triage.Dynamic / reflective crypto can be missed. Algorithms chosen at runtime, built from string concatenation, or invoked through heavy indirection may not be resolved by static analysis.Detection is best-effort across libraries. Coverage targets the most common crypto APIs per language; exotic or in-house wrappers may not be recognized.Configuration heuristics are pattern-based and can occasionally over- or under-match unusual config layouts. Tune withrules.disable
andexclude
.
Use --changed-only
for fast PR feedback, the full scan for an inventory/CBOM, and the migration guidance attached to each finding as your remediation checklist.
Apache-2.0. See the project metadata in pyproject.toml
.