7 Proven Steps: PQC in CI with ML-KEM Gate & CBOM

Engineering leaders don’t need more theory—you need merge-blocking controls and audit-ready artifacts. This guide shows how to operationalize PQC in CI by shipping two core capabilities:

  • A cryptography bill of materials (CBOM) that inventories algorithms, key sizes, and crypto libraries across code, containers, and services.
  • A merge gate for ML-KEM readiness (a.k.a. Kyber) that prevents new quantum-vulnerable tech debt, supports hybrid KEM planning, and proves crypto-agility for audits.

You’ll get runnable snippets for GitHub Actions/GitLab CI, OPA/Rego policies, and lightweight scanners you can adapt in a sprint.

7 Proven Steps: PQC in CI with ML-KEM Gate & CBOM

What you’ll build

  • CBOM pipeline: repo + container scanning with JSON output (per service).
  • Crypto policy with allowed/disallowed primitives and key floors.
  • ML-KEM readiness gate to block merges that add quantum-vulnerable usage without waivers.
  • Exception registry with time-boxed waivers + SOC 2/ISO 27001/PCI mapping.
  • Evidence pack: signed CI logs, policy commit hash, CBOM snapshot, and exception ledger.

Step 1 — Build a CBOM (code, containers, services)

Create a repo-local scanner to enumerate algorithms, key sizes, and libraries. Treat it like an SBOM, but for cryptography.

tools/cbom_scan.py (Python 3.11)

#!/usr/bin/env python3
import os, re, json, glob, hashlib
from pathlib import Path

ALG_PATTERNS = {
  # algorithms (extend as needed)
  "md5": r"\bmd5\b",
  "sha1": r"\bsha1\b",
  "sha256": r"\bsha-?256\b|\bSHA256\b",
  "sha3_256": r"\bsha3[-_]?256\b",
  "des": r"\bdes(_?ede3)?\b|\b3des\b|\btriple[-_]?des\b",
  "rc4": r"\brc4\b",
  "aes_ecb": r"aes[-_]?ecb",
  "aes_gcm": r"aes[-_]?gcm",
  "chacha20_poly1305": r"chacha20[-_]?poly1305",
  "rsa": r"\brsa\b",
  "ecdsa": r"\becdsa\b",
  "x25519": r"\bx25519\b",
  "ml_kem": r"\b(ml[-_]?kem|kyber(512|768|1024))\b"
}
LIB_HINTS = [r"pyca/cryptography", r"OpenSSL", r"wolfSSL", r"BoringSSL", r"libsodium", r"NaCl", r"mbedTLS", r"liboqs"]

# file globs to inspect
GLOBS = ["**/*.py","**/*.js","**/*.ts","**/*.go","**/*.java","**/*.rs","**/*.c","**/*.cpp","**/*.cs","**/*.rb","**/*.php","**/*.scala"]

def scan_file(path):
    text = Path(path).read_text(errors="ignore")
    hits = []
    for name, rgx in ALG_PATTERNS.items():
        m = re.findall(rgx, text, flags=re.I)
        if m:
            hits.append({"algo": name, "count": len(m)})
    libs = []
    for hint in LIB_HINTS:
        if re.search(hint, text, re.I):
            libs.append(hint)
    return hits, list(set(libs))

def key_size_guess(path, text):
    # rough key floor detection; extend for your stack
    ks = []
    for m in re.finditer(r"RSA\W*(\d{3,5})", text, re.I):
        ks.append({"algo": "rsa", "bits": int(m.group(1))})
    for m in re.finditer(r"(P-256|P-384|P-521|ed25519|x25519)", text, re.I):
        ks.append({"algo": "ec", "curve": m.group(1)})
    return ks

def scan_repo(root="."):
    results = []
    for pattern in GLOBS:
        for f in glob.glob(os.path.join(root, pattern), recursive=True):
            try:
                txt = Path(f).read_text(errors="ignore")
            except Exception:
                continue
            algos, libs = scan_file(f)
            ks = key_size_guess(f, txt)
            if algos or libs or ks:
                results.append({"file": f, "algos": algos, "libs": libs, "key_hints": ks})
    return results

if __name__ == "__main__":
    out = {
      "service": os.environ.get("SERVICE_NAME","unknown"),
      "commit": os.environ.get("GIT_COMMIT",""),
      "findings": scan_repo(),
      "container_libs": [],  # to be filled by Step 1b
      "ts": os.popen("date -Iseconds").read().strip()
    }
    print(json.dumps(out, indent=2))

Containers: add a tiny inspector that prints crypto libs present in images.

tools/cbom_container.sh

#!/usr/bin/env bash
set -euo pipefail
IMG="${1:-app:latest}"
tmp=$(mktemp -d)
cid=$(docker create "$IMG" sh -c 'true')
docker export "$cid" | tar -C "$tmp" -xf -
docker rm "$cid" >/dev/null

# naive scan for library names / providers
grep -R -Ei 'lib(ssl|crypto|oqs)|oqsprovider|wolfssl|mbedtls|libsodium' "$tmp" | cut -d: -f1 \
  | sort -u | head -n 200 > cbom_container_libs.txt

jq --argfile libs <(jq -R -s 'split("\n")|map(select(length>0))' cbom_container_libs.txt) \
   '.container_libs = $libs' cbom.json > cbom.out.json || cp cbom.json cbom.out.json

GitHub Actions (extract + publish CBOM)

name: cbom
on: [push, pull_request]
jobs:
  make-cbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Python deps
        run: pipx install jq && python -V
      - name: Run CBOM scanner
        env: { SERVICE_NAME: usersvc, GIT_COMMIT: ${{ github.sha }} }
        run: |
          python tools/cbom_scan.py > cbom.json
      - name: (optional) Container CBOM
        run: |
          docker build -t app:ci .
          bash tools/cbom_container.sh app:ci || true
      - name: Upload CBOM
        uses: actions/upload-artifact@v4
        with: { name: cbom-${{ github.sha }}, path: cbom*.json }

Free Website Vulnerability Scanner — Landing

Screenshot of the free tools webpage where you can access security assessment tools for different vulnerability detection.
Screenshot of the free tools webpage where you can access security assessment tools for different vulnerability detection.

Step 2 — Define a crypto policy (one file, reviewable)

policy/crypto-policy.yaml

version: 1
allowed_primitives:
  # plan for hybrid today, ML-KEM soon; adapt to org roadmap
  key_establishment: ["ECDH-P256+ML-KEM-768", "X25519+ML-KEM-768", "ML-KEM-768"]
  bulk_cipher: ["AES-GCM-128","AES-GCM-256","CHACHA20-POLY1305"]
  hash: ["SHA-256","SHA-384","SHA3-256","SHA3-384"]
  signature: ["Ed25519","ECDSA-P256","ML-DSA-65"]  # transitional + target
disallowed:
  - "MD5"    # collision-broken
  - "SHA-1"  # collision-broken
  - "DES"    # weak
  - "RC4"    # weak
  - "AES-ECB" # malleable
floors:
  rsa_min_bits: 3072
  ecc_allowed_curves: ["P-256","P-384","X25519"]
classify:
  quantum_vulnerable:
    - "RSA"
    - "ECDH"
    - "ECDSA"
waiver:
  max_days: 180
  needs:
    - owner
    - reason
    - compensating_controls
    - exit_by

Keep this as code in the repo so changes go through PRs like any other policy.


Step 3 — Gate disallowed primitives in CI

Use OPA/Rego to read the CBOM and fail merges if policy violations exist without a valid waiver.

policy/cbom.rego

package cbom.gate

default deny := []

# Input shape: { "cbom": {...}, "policy": {...}, "waivers": [...] }

violation[v] {
  f := input.cbom.findings[_]
  a := f.algos[_]
  banned := input.policy.disallowed[_]
  lower(a.algo) == lower(banned)
  v := {"file": f.file, "reason": sprintf("Disallowed primitive: %s", [a.algo])}
}

weak_rsa[v] {
  f := input.cbom.findings[_]
  k := f.key_hints[_]
  k.algo == "rsa"
  k.bits < input.policy.floors.rsa_min_bits
  v := {"file": f.file, "reason": sprintf("RSA key too small: %d", [k.bits])}
}

quantum_vuln[v] {
  f := input.cbom.findings[_]
  a := f.algos[_]
  qv := input.policy.classify.quantum_vulnerable[_]
  lower(a.algo) == lower(qv)
  not has_valid_waiver(f.file)
  v := {"file": f.file, "reason": "Quantum-vulnerable usage without waiver"}
}

has_valid_waiver(file) {
  w := input.waivers[_]
  w.file == file
  now := time.now_ns()
  ttl := input.policy.waiver.max_days * 24 * 3600 * 1e9
  now - time.parse_rfc3339_ns(w.expires) < ttl
}

deny := concat_array([violation, weak_rsa, quantum_vuln])

Waiver ledger (time-boxed, reviewable)

exceptions/waivers.yaml

- file: "svc/payments/legacy_rsa.go"
  owner: "payments-team"
  reason: "Legacy partner requires RSA-2048; migration scheduled."
  compensating_controls: ["TLS 1.3 only", "short-lived certs", "rate-limit"]
  expires: "2026-03-31T23:59:59Z"

CI step (GitHub Actions)

- name: Evaluate CBOM against policy
  run: |
    pipx install yq
    jq -n --slurpfile c cbom.json \
          --argfile p policy/crypto-policy.yaml \
          --argfile w exceptions/waivers.yaml \
      '{cbom:$c[0], policy:$p, waivers:$w}' > input.json
    curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64
    chmod +x opa
    ./opa eval -I -f pretty -i input.json -d policy 'data.cbom.gate.deny' | tee deny.txt
    if grep -q '{' deny.txt; then
      echo "::error::CBOM policy violations found"
      exit 1
    fi

Step 4 — Add an ML-KEM readiness gate

The aim is to prove crypto-agility and prevent new debt. Start with configuration and dependency signals, then evolve to runtime validation.

Option A — Dependency/config signals (fastest)
Require any service handling key establishment to declare a PQC plan.

crypto.yml (per service)

service: usersvc
key_establishment_target: "ECDH-P256+ML-KEM-768"   # target hybrid
status:
  runtime_lib: "OpenSSL-3.x"
  pqc_provider: "oqsprovider"        # or "none" if not yet enabled
  rollout: "pilot"                    # pilot | partial | full
milestones:
  enable_provider_in_staging: "2026-01-31"
  hybrid_kem_in_prod: "2026-06-30"

CI check: block merges that introduce new ECDH/RSA usage without an ML-KEM plan.

# tools/mlkem_gate.sh
set -euo pipefail
git diff --name-only origin/main...HEAD | xargs -I{} grep -nE 'ECDH|RSA' {} | tee kem_hits.txt || true
plan=$(yq '.key_establishment_target' crypto.yml)
if [ -s kem_hits.txt ] && ! grep -qi 'ML-KEM' <<< "$plan"; then
  echo "::error::New key-establishment usage without ML-KEM plan in crypto.yml"
  exit 1
fi

Option B — Runtime provider presence (when feasible)
If you ship OpenSSL with OQS provider (or equivalent), assert the provider is loadable in CI images.

# minimal provider probe
openssl list -providers 2>/dev/null | grep -i oqs || {
  echo "::warning::OQS provider not present; ML-KEM readiness = false"
  exit 1
}

Option C — Policy-as-code (OPA)
Fail if CBOM contains quantum-vulnerable key establishment and crypto.yml.status.rollout isn’t at least pilot.

policy/mlkem.rego

package pqc.gate

deny[msg] {
  some f
  f := input.cbom.findings[_]
  some a
  a := f.algos[_]
  lower(a.algo) == "rsa"  # or "ecdsa"/"ecdh"
  input.crypto.status.rollout == "none"
  msg := sprintf("Quantum-vulnerable KE without ML-KEM rollout plan: %s", [f.file])
}

Step 5 — Generate a remediation backlog (with exit-by dates)

Turn the CBOM into issues with deadlines aligned to your policy.

# tools/backlog_from_cbom.py
import json, datetime, sys
cbom = json.load(open("cbom.json"))
out = []
deadline = (datetime.date.today() + datetime.timedelta(days=180)).isoformat()
for f in cbom["findings"]:
    for a in f["algos"]:
        if a["algo"].lower() in ["md5","sha1","des","rc4","aes_ecb"]:
            out.append({"title": f"Replace {a['algo']} in {f['file']}",
                        "labels": ["crypto-remediation","high"],
                        "exit_by": deadline})
    for k in f.get("key_hints", []):
        if k.get("algo") == "rsa" and k.get("bits", 0) < 3072:
            out.append({"title": f"Rotate RSA key (<3072) in {f['file']}", "labels":["crypto-remediation","med"], "exit_by": deadline})
print(json.dumps(out, indent=2))

Pipe this into your ticket API (GitHub, Jira) to auto-create work with clear “exit by” dates.


Step 6 — Produce audit-ready evidence (signed)

Capture and sign policy + CBOM + logs so audits don’t become archaeology.

# scripts/evidence.sh
set -euo pipefail
mkdir -p evidence
cp policy/crypto-policy.yaml evidence/
cp cbom.json evidence/
git rev-parse HEAD > evidence/policy_commit.txt
tar -czf evidence.tar.gz evidence
# sign with cosign keyless or GPG (choose one)
cosign sign-blob --yes evidence.tar.gz > evidence.sig || gpg --armor --detach-sign evidence.tar.gz

Upload evidence.tar.gz + signature as build artifacts and reference them in your change record.

Control mapping (examples)

  • SOC 2 CC6/CC7: preventive merge gates + detective logs.
  • ISO 27001 A.8 / A.14: crypto policy and secure development controls.
  • PCI DSS 6 & 10: secure coding checks + logging evidence.

Step 7 — Rollout pattern that works

  1. Pilot on one critical service (warn-only → block).
  2. Policy package org-wide (policy + OPA bundles + exception workflow).
  3. Vendor validation (add to your questionnaire):
    • Do your services support ML-KEM (or hybrid) for TLS/KE?
    • Provide your crypto policy and exception ledger with expiry dates.
    • Share a CBOM snapshot and a plan to exit quantum-vulnerable usage.

Sample report — Findings excerpt to check Website Vulnerability

An example of a vulnerability assessment report generated using our free tool provides valuable insights into potential vulnerabilities.
An example of a vulnerability assessment report generated using our free tool provides valuable insights into potential vulnerabilities.

When you’re ready for a deeper review or assessor-ready packaging, explore:

  • Risk Assessment Services — map CI outputs to SOC 2/ISO/PCI and plan the rollout.
  • Remediation Services — close gaps quickly with engineer-friendly fixes.

Risk Assessment Services | Remediation Services | Free Website Security Scanner


GitLab CI equivalents (quick start)

stages: [cbom, policy]

cbom:
  stage: cbom
  image: python:3.11
  script:
    - pip install jq yq || true
    - python tools/cbom_scan.py > cbom.json
  artifacts:
    when: always
    paths: [cbom.json]

policy:
  stage: policy
  image: alpine:3.20
  script:
    - wget -qO opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64 && chmod +x opa
    - apk add --no-cache jq yq
    - jq -n --slurpfile c cbom.json \
          --argfile p policy/crypto-policy.yaml \
          --argfile w exceptions/waivers.yaml \
          '{cbom:$c[0], policy:$p, waivers:$w}' > input.json
    - ./opa eval -I -f pretty -i input.json -d policy 'data.cbom.gate.deny' | tee deny.txt
    - test ! -s deny.txt

Related reads on our blog


Final note

If you want help wiring PQC in CI with merge-blocking ML-KEM gates, an actionable CBOM, and audit-ready evidence, our team can pair with your engineers, pilot on one service, and expand org-wide without slowing velocity.

Start with a quick perimeter check: Website Vulnerability Scanner Online Free
Need assessor-ready packaging? Risk Assessment ServicesRemediation Services


Free Consultation

If you have any questions or need expert assistance, feel free to schedule a Free consultation with one of our security engineers>>

🔐 Frequently Asked Questions (FAQs)

Find answers to commonly asked questions about PQC in CI.

Get a Quote

Leave a Comment

Your email address will not be published. Required fields are marked *

Cyber Rely Logo cyber security
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.