Skip to main content

Lint and flag classical (quantum-vulnerable) cryptography in source code. Ships as a GitHub Action and a CLI.

Project description

PQC Lint

PQC Native ML-DSA / ML-KEM / SLH-DSA License Version

Static analyzer for classical cryptography. pqc-lint scans your source code for quantum-vulnerable crypto primitives — RSA, ECDSA, Ed25519, DH, ECDH, DSA, MD5, SHA-1 — across six languages (Python, JavaScript/TypeScript, Go, Rust, Java/Kotlin, C/C++) and points each finding at the matching NIST PQC replacement (ML-DSA, ML-KEM, SLH-DSA). Ships as both a drop-in GitHub Action and a standalone CLI. Emits SARIF 2.1.0 for GitHub code scanning and inline PR annotations via workflow commands.

The Problem

Every RSA keypair, every ECDSA signature, every ECDH handshake in your codebase is a time bomb. Once a cryptographically relevant quantum computer (CRQC) exists, Shor's algorithm breaks all of them. Data encrypted today under RSA-OAEP can be captured now and decrypted later ("harvest-now-decrypt-later"). Migration is not optional — it is a years-long engineering effort, and step one is knowing where the classical crypto actually lives.

The Solution

pqc-lint gives you that inventory. Every PR gets scanned, every finding is mapped to a specific PQC replacement with rationale, and CI fails if critical quantum-vulnerable primitives land on main.

Quick Start

As a GitHub Action

Add .github/workflows/pqc-lint.yml:

name: PQC Lint

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

permissions:
  contents: read
  security-events: write
  pull-requests: write

jobs:
  pqc-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dyber-pqc/pqc-lint-action@v1
        with:
          path: '.'
          fail-on: 'high'
          upload-sarif: 'true'

Findings appear as:

  • Inline PR annotations on the changed lines (via workflow commands)
  • Entries in the GitHub Security tab (via SARIF upload)
  • Failed check if any finding is at or above the fail-on threshold

As a CLI

pip install pqc-lint

pqc-lint scan ./src
pqc-lint scan ./src --format sarif --output results.sarif
pqc-lint scan ./src --fail-on high
pqc-lint scan ./src --languages python,go
pqc-lint rules              # list all rules
pqc-lint --version

Architecture

                                   +--------------------+
                                   |  CLI (click)       |
                                   |  action_runner     |
                                   +---------+----------+
                                             |
                                             v
            +----------+               +-----+------+              +------------+
            |  file    |               |            |              |            |
 path ----->|  walker  |--- file ----->|  Scanner   |--- matcher ->|  Patterns  |
            +----------+               |            |              |  (per-lang)|
               excludes                +-----+------+              +-----+------+
               globs                         |                           |
                                             | Finding                   | regex hits
                                             v                           v
                                        +----+-----+              +------+------+
                                        | ScanReport|<------------|   Rules     |
                                        +----+-----+              +-------------+
                                             |
             +----------------+--------------+---------------+----------------+
             |                |              |               |                |
             v                v              v               v                v
        +---------+    +----------+   +-----------+    +---------+      +-----------+
        |  text   |    |  json    |   |  sarif    |    |  github |      | (other)   |
        |(rich)   |    |          |   |  2.1.0    |    |  commds |      |           |
        +---------+    +----------+   +-----------+    +---------+      +-----------+

Threat Model

Adversary capability pqc-lint claim
Future CRQC (Shor's algorithm) Flags every known classical public-key primitive in the repo.
Insider commits RSA without review CI annotation + failed check at fail-on: high.
Supply-chain slip (new dep uses ECDH) Regex patterns catch the import/call site on next PR.
Obfuscated / dynamic crypto Not in scope. Static regex matching; does not evaluate code.
Binary-only / generated code Not in scope. Source files only.

pqc-lint is a detector — not a remediation tool and not a proof of absence. It catches the common call sites in six languages across the dominant libraries. It is designed to have a low false-negative rate on idiomatic usage and a tolerable false-positive rate. Review each finding.

Rule Catalog

Signatures (broken by Shor's)

Rule Severity Primitive Replacement
PQC001 CRITICAL RSA signing ML-DSA-65 (FIPS 204)
PQC002 CRITICAL ECDSA ML-DSA-65 (FIPS 204)
PQC003 HIGH Ed25519 ML-DSA-44 / SLH-DSA
PQC004 HIGH DSA ML-DSA-44 / SLH-DSA

Key exchange (broken by Shor's)

Rule Severity Primitive Replacement
PQC101 CRITICAL ECDH ML-KEM-768 (FIPS 203)
PQC102 CRITICAL DH ML-KEM-768 (FIPS 203)
PQC103 HIGH X25519 ML-KEM-512 (FIPS 203)

Encryption (broken by Shor's)

Rule Severity Primitive Replacement
PQC201 CRITICAL RSA-OAEP ML-KEM-768 (FIPS 203)
PQC202 CRITICAL RSA PKCS#1 v1.5 ML-KEM-768 (FIPS 203)

Weak hashes

Rule Severity Primitive Replacement
PQC301 MEDIUM MD5 SHA3-256 / SHAKE-256
PQC302 MEDIUM SHA-1 SHA3-256 / SHAKE-256

Supported Languages and Libraries

Language Extensions Libraries detected
Python .py cryptography, pycryptodome, ecdsa, hashlib
JavaScript/TypeScript .js, .jsx, .mjs, .cjs, .ts, .tsx Node crypto, Web Crypto API, node-forge, tweetnacl
Go .go crypto/rsa, crypto/ecdsa, crypto/ed25519, crypto/md5, etc.
Rust .rs rsa, ecdsa, ed25519-dalek, x25519-dalek, ring
Java / Kotlin .java, .kt java.security, javax.crypto, BouncyCastle
C / C++ .c, .cc, .cpp, .cxx, .h, .hpp OpenSSL legacy API + EVP API

Output Formats

Format Best for Contents
text local terminal Rich-formatted table, grouped by file, with snippets and fixes.
json custom tooling / piping Schema-v1.0 JSON: scan metadata + findings[] with full fields.
sarif GitHub code scanning SARIF 2.1.0: rules catalog + results. Upload via upload-sarif.
github inside GitHub Actions ::error, ::warning, ::notice workflow commands — PR inline.

fail-on severity semantics

The action (or CLI) exits non-zero if any finding has severity >= the fail-on threshold.

fail-on Fails CI when
critical A CRITICAL finding exists (RSA/ECDSA signing, ECDH, DH, RSA-OAEP).
high (default) A CRITICAL or HIGH finding exists (adds Ed25519, DSA, X25519).
medium Adds MD5 / SHA-1.
low Any finding at all.
info Any finding at all, including info-level annotations.

Excluded by default

**/.git/**
**/node_modules/**
**/__pycache__/**
**/.venv/**
**/venv/**
**/dist/**
**/build/**
**/.pytest_cache/**
**/.ruff_cache/**
**/*.min.js

Pass more globs via exclude: on the action or --exclude on the CLI.

API Reference

from pqc_lint import Scanner, Severity

scanner = Scanner(languages=("python", "go"))
report = scanner.scan_path("./src")

print(report.counts_by_severity())
# {'critical': 3, 'high': 1, 'medium': 2, 'low': 0, 'info': 0}

if report.has_failing(Severity.HIGH):
    raise SystemExit(1)

for f in report.findings:
    print(f.rule_id, f.file, f.line, f.suggestion)

Reporters:

from pqc_lint.reporters import SarifReporter, JsonReporter, TextReporter

sarif_text = SarifReporter().render(report)
json_text  = JsonReporter().render(report)
text_out   = TextReporter().render(report)

Development

cd tools/pqc-lint-action
pip install -e ".[dev]"
pytest -v
ruff check src/ tests/

Contributing

Issues and PRs welcome. When adding a new rule or pattern:

  1. Add the Rule entry in src/pqc_lint/rules.py with an appropriate ID range.
  2. Add the regex pattern(s) to the per-language matcher(s) in src/pqc_lint/patterns/.
  3. Add a test in tests/test_scanner_<language>.py that writes a minimal vulnerable file and asserts the rule fires.

License

Apache 2.0. See LICENSE.

Related

Part of the QuantaMrkt open-source tools registry — a catalog of post-quantum security tooling.

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

pqc_lint-0.1.0.tar.gz (25.5 kB view details)

Uploaded Source

Built Distribution

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

pqc_lint-0.1.0-py3-none-any.whl (27.6 kB view details)

Uploaded Python 3

File details

Details for the file pqc_lint-0.1.0.tar.gz.

File metadata

  • Download URL: pqc_lint-0.1.0.tar.gz
  • Upload date:
  • Size: 25.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for pqc_lint-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b707c483e17ea827c104c3dd44c975e72cf9b29991a30b86035de558df3ab039
MD5 80aaefdc770572e93c22e61f60b5e06d
BLAKE2b-256 8b185ddbccc6b860a628a95b9a23b0095f30c843b2079a0769b79bec03d1b269

See more details on using hashes here.

File details

Details for the file pqc_lint-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pqc_lint-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 27.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for pqc_lint-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ae2e7dec791e3796dfa2353790520f65f8b56df07ebc398cdb6272bca115d75c
MD5 98c7b0124bac9642786ba739db0cb98d
BLAKE2b-256 9ef643c84d7e427ae39ec3257f56bc49d0a078811cf77a1ec9defe11f41211ad

See more details on using hashes here.

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