Skip to main content

PQC readiness scanner: TLS endpoints, source code, and certificate files

Project description

pqready

PyPI version Python versions License CI

Post-quantum cryptography readiness scanner. Audits TLS endpoints, source code, and certificate files for quantum-vulnerable crypto and surfaces a prioritised migration plan — as a CLI, a library, an MCP server, or a remote Cloudflare-hosted tool.


Install

pip install pqready

Requires Python 3.11+. Installs two console scripts:

Command What it is
pqready the audit CLI
pqready-mcp MCP server over stdio (Claude Code, Cursor, Cline)

Quickstart

# Scan a TLS endpoint
pqready tls api.example.com

# Scan a source tree
pqready source ./my-service

# Inspect a certificate file
pqready cert /etc/ssl/certs/api.example.com.pem

# Auto-detect target type
pqready scan ./my-service
pqready scan api.example.com

# Generate an HTML report alongside the terminal summary
pqready tls api.example.com --output report.html

Exit codes: 0 clean · 1 CRITICAL/HIGH findings · 2 scan error. Plays cleanly with CI: just call pqready and let the exit code fail the job.

What it detects

pqready looks for three classes of quantum-relevant weakness.

TLS endpoints — backed by sslyze

ID Detects Severity
TLS-001 TLS 1.0 / 1.1 still supported HIGH
TLS-002 RSA key exchange (no PFS) — harvest-now-decrypt-later CRITICAL
TLS-003 RC4 / 3DES / NULL / EXPORT cipher accepted CRITICAL
TLS-004 Certificate uses RSA < 3072 bits CRITICAL
TLS-005 Certificate uses ECC (P-256 / P-384 / …) HIGH
TLS-006 Certificate uses RSA ≥ 3072 bits (not yet PQC) MEDIUM
TLS-007 X25519MLKEM768 hybrid PQC KEX advertised INFO ✓
TLS-008 Certificate expires in < 30 days HIGH
TLS-009 Certificate signed with MD5 or SHA-1 CRITICAL

Source code — AST for Python, regex for config

ID Detects Severity
SRC-001 import Crypto / from Crypto… (PyCrypto / PyCryptodome) HIGH
SRC-002 from cryptography.hazmat.primitives.asymmetric import rsa HIGH
SRC-003 RSA.generate(<3072) CRITICAL
SRC-004 ec.generate_private_key / EllipticCurvePrivateKey HIGH
SRC-005 hashlib.md5( / hashlib.sha1( MEDIUM
SRC-006 Hardcoded RSA / AES-128-CBC in config files LOW
SRC-007 .pem / .crt / .key referenced via open(…) LOW

Skips binary files and files > 1 MB. Prunes .git, .venv, node_modules, __pycache__, dist, build and friends.

Certificate files — PEM / DER / CRT / CER / KEY

Reuses the cert-level checks above (TLS-004 … TLS-009) and applies them to files on disk. Useful for offline audits of leaf certs and private keys.

Sample terminal output

pqready · TLS scan · api.example.com:443
────────────────────────────────────────
✗ CRITICAL  TLS-002  RSA key exchange offered (no perfect forward secrecy)
✗ CRITICAL  TLS-004  RSA certificate with 2048-bit key
✗ HIGH      TLS-005  ECC certificate (secp256r1)
✓ INFO      TLS-007  No hybrid PQC key exchange offered
Risk score: 80/100  ·  PQC-ready: NO
────────────────────────────────────────
Run with --output report.html to generate a full report.

The HTML report is self-contained (no external CSS / JS, no remote fetches). Open it in a browser or attach it to a ticket as-is.

Library usage

from pqready.core.tls import scan_tls
from pqready.core.source import scan_source
from pqready.core.certs import scan_cert_file
from pqready.reporters.html import render_html

tls_result   = scan_tls("api.example.com")
src_results  = scan_source("./services")
cert_result  = scan_cert_file("./api.example.com.pem")

render_html([tls_result, *src_results, cert_result], "report.html")

Every scanner returns a ScanResult:

@dataclass
class Finding:
    id: str               # e.g. "TLS-002"
    category: FindingCategory
    severity: Severity    # critical | high | medium | low | info
    title: str
    description: str
    location: str         # endpoint URL, file path, or "file:line"
    evidence: str
    remediation: str
    nist_ref: str         # e.g. "FIPS 203 (ML-KEM)"
    pqc_ready: bool

@dataclass
class ScanResult:
    target: str
    scan_type: str        # "tls" | "source" | "cert"
    findings: list[Finding]
    scanned_at: str       # ISO 8601
    duration_ms: int
    error: str | None
    risk_score: int       # 0-100, severity-weighted
    pqc_ready: bool       # False if any CRITICAL/HIGH

MCP server

pqready ships an MCP server (pqready-mcp) over stdio that exposes four tools any MCP-capable agent (Claude Code, Cursor, Cline, …) can call:

Tool Input Returns
scan_tls_endpoint hostname, port (=443) ScanResult dict
scan_source_code path {"results": [ScanResult]}
scan_cert_file path ScanResult dict
generate_html_report results_json, output_path {output_path, targets}

Wire it in by adding to your MCP client config:

{
  "mcpServers": {
    "pqready": { "command": "pqready-mcp" }
  }
}

Full tool schemas, example payloads, and a decision tree for which tool to call live in skill/SKILL.md — drop that file into any agent skill loader to teach the agent how to use pqready correctly.

Cloudflare Worker (remote MCP)

A thin proxy Worker lives in cf_worker/. It handles bearer auth and streams MCP traffic to a backend running pqready-mcp over HTTP/SSE (Cloud Run, Fly, VPS, etc.). The Python runtime cannot execute directly in a Worker, so this layer is auth + proxy only.

cd cf_worker
npm install
wrangler secret put PQREADY_API_TOKEN
npm run deploy

See cf_worker/README.md for the full setup.

Interpreting findings

risk_score is a coarse summary, not a substitute for reading the findings. Two endpoints can hit 100 for entirely different reasons.

pqc_ready is intentionally conservative: it returns False whenever any CRITICAL or HIGH finding exists. A green run only means we did not find any of the patterns we know to look for — it is not a certification.

Every finding carries:

  • NIST reference — most often FIPS 203 (ML-KEM), FIPS 204 (ML-DSA), SP 800-52, or SP 800-131A.
  • Remediation — concrete, copy-pasteable next step.

See skill/SKILL.md for the full per-ID remediation table.

Documentation

Where What
skill/SKILL.md Full MCP tool schemas + agent decision tree
cf_worker/README.md Cloudflare Worker deployment guide
GitHub repo Issues, releases, source
PyPI project Latest release, install command

Development

git clone https://github.com/shanglai/pqready.git
cd pqready
python -m venv .venv && source .venv/bin/activate   # or .venv\Scripts\activate on Windows
pip install -e ".[dev]"
ruff check .
pytest tests/

CI runs ruff + pytest on Python 3.11 and 3.12. Releases are cut by pushing a v* tag; .github/workflows/publish.yml publishes to PyPI via OIDC trusted publishing — no API tokens stored in the repo.

Contributing

Bug reports and PRs welcome. For non-trivial changes, please open an issue first so we can align on scope. Run ruff check . and pytest tests/ before opening a PR.

Limitations

  • TLS scans depend on the handshake sslyze observes. Some load balancers hide cipher details from external probes.
  • The source scanner is pattern-based and will miss findings hidden behind dynamic dispatch (e.g. getattr(crypto_mod, name)).
  • PQC group detection matches OpenSSL-style names. Servers exposing only IANA codepoints in custom log formats may report differently.
  • pqready does not test certificate revocation or CT-log inclusion.

License

Apache-2.0 — see LICENSE.

Project details


Download files

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

Source Distribution

pqready-0.2.1.tar.gz (34.0 kB view details)

Uploaded Source

Built Distribution

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

pqready-0.2.1-py3-none-any.whl (28.7 kB view details)

Uploaded Python 3

File details

Details for the file pqready-0.2.1.tar.gz.

File metadata

  • Download URL: pqready-0.2.1.tar.gz
  • Upload date:
  • Size: 34.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pqready-0.2.1.tar.gz
Algorithm Hash digest
SHA256 da2b1222917e253868055c05b2562b8b7f1b6aea6f2af166e3a5eee87440ff19
MD5 de3f8be8ddad5b58e17645b068ed9b49
BLAKE2b-256 bfa804bc809a5a043159d59aabf75bbc1e8f8386b3b97c58b3528b08e2f27ec2

See more details on using hashes here.

Provenance

The following attestation bundles were made for pqready-0.2.1.tar.gz:

Publisher: publish.yml on shanglai/pqready

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

File details

Details for the file pqready-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: pqready-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 28.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pqready-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b7380c9dba8d14ecdaebad307e7c185d3e0eb7d97add4be3562e0db8d8889b1e
MD5 1005a6a60fc70fcef63e1533d075532d
BLAKE2b-256 7427296627ae2c6880e5693393cf9fe27db6482d884329330064280c5ab2b98e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pqready-0.2.1-py3-none-any.whl:

Publisher: publish.yml on shanglai/pqready

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