We turned PostQ into a real scanner this week
Seven days. Fourteen commits across four repos. One through-line: PostQ stopped being a post-quantum TLS checker and became a unified cryptographic and application-security scanner — with SDKs, a CLI, and a dashboard that all see the same findings.
The TL;DR
A week ago postq scan could tell you whether your TLS handshake was quantum-safe and not much else. As of today it can find your hardcoded AWS keys, your SQL-injection sinks, your eight-year-old lodashwith three open RCEs, your cert that’s about to expire, and your missing HSTS header — in addition to every quantum-vulnerable algorithm we already knew about. Same scanner. Same dashboard. Same five-line CI gate.
This is the post that catches you up on everything that landed in the last week.
1. Secrets detection — gitleaks, but PQ-aware
The first thing your security review actually catches in a code review is a hardcoded credential. PostQ now does too.
A new services/scanner/secrets.ts module ships with 14 high-precision rules covering AWS access keys, GitHub PATs, OpenAI / Anthropic / Stripe / Slack / Google / Supabase tokens, our own pq_live_ keys, PEM-encoded private keys, and the catch-all api_key="…" assignments. Every match is gated on Shannon entropy and a placeholder allowlist (your-key-here, xxxxx, etc.) to keep false positives near zero, and the preview is redacted before it leaves your machine.
Why ship this in a quantum scanner? Because the people we’re asking to do PQ migration are the same peoplewho are running gitleaks and trufflehog in CI today. We don’t want them to run two tools.
2. OWASP Top 10 code patterns
A new codepatterns.ts module covers 25+ rules against the OWASP Top 10:
- A02 (cryptographic failures)— MD5, SHA-1, ECB mode, PKCS#1 v1.5,
Math.random()in security contexts. - A03 (injection)— raw SQL string concatenation,
eval(), shell command interpolation, rawinnerHTMLsinks, JWTalg:none. - A05 (security misconfiguration)— TLS verification disabled (
rejectUnauthorized: false,InsecureSkipVerify: true), wide-open CORS. - A07 (identification failures)— bcrypt cost factors below 10.
- A08 (software & data integrity) — raw
pickle.loadon untrusted input. - A10 (SSRF)— user-controlled URLs into outbound HTTP clients.
Every finding ships with its CWE, its OWASP category, and a remediation hint. The dashboard uses them to render a cleaner severity histogram, and the SDKs and CLI surface them right next to the PQ findings so the dev who opened the report sees one list, not two.
3. SCA powered by OSV.dev
A new sca.ts walks your manifests and queries OSV.dev for known vulnerabilities across seven ecosystems: npm, PyPI, Go, RubyGems, crates.io — with parsers for package.json, requirements.txt, pyproject.toml, Pipfile, go.mod, Gemfile, and Cargo.toml.
We batch the OSV query, then hydrate per-CVE details only for hits — so a 200-dependency repo costs you one round-trip plus a handful for the actual vulnerabilities. First test repo lit up 11 CVEswe hadn’t known about (lodash, express, minimatch). It’s on by default for every scan code.
4. CycloneDX 1.6 CBOM export
Every scan now generates a CycloneDX 1.6 Cryptography Bill of Materials. Hit GET /v1/scans/:id/cbom (or pq.scans.cbom(id)in any SDK) and you get a CBOM JSON document with every algorithm, key size, and certificate the scan touched — ready to drop into your SBOM pipeline.
Why CBOM matters: it’s the format the US government, Linux Foundation, and the broader supply-chain-security community have standardized on for cryptographic inventory. You generate it once, you can answer “where am I still using RSA-2048?” across your whole org without grepping logs.
5. Server-side URL scans — the hybrid CLI
Until this week, postq scan url did the TLS handshake locally. It worked, but every time we shipped a server-side scanner upgrade (HNDL scoring, the new PQ probe, hybrid ML-KEM detection) the CLI was a step behind.
New endpoint: POST /v1/scans/url. Mirror of /v1/scans/cloud: pass a target, the API runs the full scanner (TLS + cert + PQ probe + HNDL + hygiene, see #6), persists the scan, and returns the inline findings. The CLI now calls this by default. Your offline / air-gapped workflows get a --local flag that drops back to the legacy in-process scanner.
Translation: every server-side scanner improvement we ship from now on lights up in postq scan url the moment we deploy. No CLI release required.
6. TLS hygiene & cert lifecycle
The PQ readiness score answered “will Shor’s algorithm break this in 2030?” It didn’t answer “is your cert about to expire on Saturday?” The new hygiene.ts module fixes that:
- HSTS — missing header, weak
max-age(<6 months), noincludeSubDomains. - Cert expiry windows —
≤ 7d Critical,≤ 30d High,≤ 60d Medium. PagerDuty-grade warnings, not just “expires in 5 days” in fine print. - Cert lifetime > 398 days— the CA/Browser Forum cap. If you’re shipping a public-trust cert longer than that, browsers will start distrusting it.
- Missing SANs— CN-only certs are rejected by every modern browser. We catch the connection that’s silently failing in production.
- Weak DH groups(<2048 bits) and undersized ECDHE curves (<256 bits) — Logjam-style attacks.
All of this flows through the same finding pipeline, so the dashboard, SDKs, and CLI got it for free.
7. SDK + CLI parity in one push
We added two new pieces of the API contract this week: GET /v1/scans/:id(full scan record — HNDL, cert, TLS, normalized findings) and the /cbom endpoint above. Then we shipped them to all three SDKs in one commit:
// JavaScript / TypeScript const detail = await pq.scans.get(scanId); const cbom = await pq.scans.cbom(scanId); # Python detail = pq.scans.get(scan_id) cbom = pq.scans.cbom(scan_id) // .NET ScanDetail detail = await pq.Scans.GetAsync(scanId); JsonElement cbom = await pq.Scans.GetCbomAsync(scanId);
New types across all three: HndlAssessment, CertificateInfo, TlsInfo, ScanFindingRow, ScanDetail. Same field names, same shapes, idiomatic in each language. JS npm run build + 12 tests green; Python pytest 14 green; .NET builds with zero warnings.
8. Bulk scans for CI
The CLI now takes a target file:
# infra/hosts.txt # core platform api.example.com auth.example.com billing.example.com # data plane warehouse.example.com postq scan url --target-list infra/hosts.txt --concurrency 8
One-line CI gate, version-controlled host inventory, exit code 2 on Critical / High — same gate you already have for scan code and scan cloud.
What this means for you
If you came to PostQ for post-quantum cryptography, nothing has changed about the PQ story — if anything it’s sharper (HNDL scoring, hybrid ML-KEM detection, CBOM export). What changed is that you now have one scanner that covers PQ readiness and classical AppSec hygiene. Same dashboard, same API key, same CI gate. The cryptographic modernization story now runs on the same rails as your day-to-day security review.
And because every new rule lives server-side and flows through the API, you don’t need to upgrade anything to get the next batch.
What’s next
- OCSP stapling + Certificate Transparency log checks — the next layer of TLS hygiene.
- SARIF output for
scan code— one-click GitHub code-scanning integration. - LLM-assisted triage — using an LLM to explain why a finding is exploitable in your stack and suggest the exact diff. Detection stays deterministic; reasoning gets the model.
- Continuous diff alerts— flag the moment a new RSA-2048 key, expiring cert, or fresh CVE appears in any connected workspace.
Try it in 60 seconds
brew install PostQDev/tap/postq postq auth login --api-key pq_live_… # the new server-side URL scanner (HNDL + hygiene + PQ probe) postq scan url example.com api.example.com # bulk postq scan url --target-list infra/hosts.txt --concurrency 8 # code: secrets + OWASP + SCA + crypto, all in one pass postq scan code . # cloud: AWS or Azure postq scan cloud aws --account 123456789012 \ --role-arn arn:aws:iam::123456789012:role/PostQScanner
Generate an API key at /settings/api-keys. The dashboard, SDKs, and CLI all share it.
Six things shipped, four repos pushed, one weekend. We’re just getting started — tell us what you want next.