PQ CRYPTA PLATFORM

🏠 Main

🧪 Interactive Apps

📰 News

🛡️ PQ Crypta Proxy

👤 Account

⟨ QUANTUM ERROR PORTAL ⟩

Navigate the Error Dimensions

← Discovery Agent
Complete Guide
Dashboard →

PQCrypta Discovery Agent — Complete Operator Guide

1. Overview

The Discovery Agent is a single, read-only, cross-platform Rust binary that inventories every cryptographic asset it can reach — certificates and keys on disk, SSH keys, live TLS endpoints, database columns, F5/NetScaler appliance certificate stores, and weak-crypto calls in source code — then classifies each by post-quantum readiness and remediation ownership. It has no installer, no daemon, and no dependencies: download it, point it at a config, run it once.

It operates in one of three modes: submit results to the PQCrypta API, write a raw JSON file, or produce a complete self-contained assessment entirely offline (§7).

2. Install

Binaries are public — no account or API key required to download. Pick your platform:

🐧 Linux — x86_64 curl -o pqcrypta-discovery https://api.pqcrypta.com/stream/downloads/discovery-agent/linux chmod +x pqcrypta-discovery
🍎 macOS — Apple Silicon (arm64) curl -o pqcrypta-discovery https://api.pqcrypta.com/stream/downloads/discovery-agent/macos chmod +x pqcrypta-discovery
🍏 macOS — Intel (x86_64) curl -o pqcrypta-discovery https://api.pqcrypta.com/stream/downloads/discovery-agent/macos-intel chmod +x pqcrypta-discovery
🪟 Windows — x86_64 (PowerShell) Invoke-WebRequest https://api.pqcrypta.com/stream/downloads/discovery-agent/windows -OutFile pqcrypta-discovery.exe
⚡ One-line bootstrap (Linux / macOS) curl -sSL https://api.pqcrypta.com/stream/downloads/discovery-agent/install.sh | bash

The Windows binary is built on a Windows CI runner and both macOS binaries on genuine Apple hardware — the arm64 build natively, the Intel build cross-compiled to x86_64 on that same Apple Silicon runner. The Linux binary is built on PQCrypta's own Linux build host. Every one of them is covered by the signed SHA256SUMS manifest below, which is what actually pins what you received. The agent needs no runtime, no admin rights to run, and touches nothing it scans — it only reads.

2a. Verify Your Download

Every release is hybrid-signed — a classical Ed25519 signature and a post-quantum ML-DSA-65 (NIST FIPS 204) signature over a SHA256SUMS manifest. The install.sh path verifies the Ed25519 signature (against a key pinned in the script) and the checksum before it ever marks the binary executable — it fails closed. For the removable-media / air-gap workflow, verify by hand on the destination host after transfer:

# Fetch the binary + integrity files BASE=https://api.pqcrypta.com/stream/downloads/discovery-agent curl -o pqcrypta-discovery $BASE/linux curl -o SHA256SUMS $BASE/verify/SHA256SUMS curl -o SHA256SUMS.sig $BASE/verify/SHA256SUMS.sig curl -o SHA256SUMS.mldsa.sig $BASE/verify/SHA256SUMS.mldsa.sig # Fetch the public keys from a DIFFERENT origin than the binary — the # out-of-band key repo at github.com/PQCrypta/discovery-agent-keys KEYS=https://raw.githubusercontent.com/PQCrypta/discovery-agent-keys/main curl -o signing.pub $KEYS/pqcrypta-agent-signing.pub curl -o signing-mldsa.pub $KEYS/pqcrypta-agent-signing-mldsa.pub # 1. Classical: verify the Ed25519 signature over the manifest (any OpenSSL) openssl pkeyutl -verify -pubin -inkey signing.pub -rawin \ -in SHA256SUMS -sigfile SHA256SUMS.sig # 2. Post-quantum: verify the ML-DSA-65 signature (OpenSSL 3.5+ / FIPS 204). # OPENSSL_CONF=/dev/null is set here on purpose: if your openssl.cnf loads an # oqsprovider it hijacks ML-DSA and the key fails to load with # "ossl_store_handle_load_result:unsupported". Ignoring the config file forces # OpenSSL's own FIPS 204 implementation. Harmless if you have no such provider. OPENSSL_CONF=/dev/null \ openssl pkeyutl -verify -pubin -inkey signing-mldsa.pub -rawin \ -in SHA256SUMS -sigfile SHA256SUMS.mldsa.sig # 3. Confirm the binary matches the now-trusted manifest sha256sum -c SHA256SUMS --ignore-missing

Windows (PowerShell)

$BASE = "https://api.pqcrypta.com/stream/downloads/discovery-agent" Invoke-WebRequest "$BASE/windows" -OutFile pqcrypta-discovery.exe Invoke-WebRequest "$BASE/verify/SHA256SUMS" -OutFile SHA256SUMS Invoke-WebRequest "$BASE/verify/SHA256SUMS.sig" -OutFile SHA256SUMS.sig Invoke-WebRequest "$BASE/verify/SHA256SUMS.mldsa.sig" -OutFile SHA256SUMS.mldsa.sig # Public keys come from a DIFFERENT origin than the binary — the # out-of-band key repo at github.com/PQCrypta/discovery-agent-keys $KEYS = "https://raw.githubusercontent.com/PQCrypta/discovery-agent-keys/main" Invoke-WebRequest "$KEYS/pqcrypta-agent-signing.pub" -OutFile signing.pub Invoke-WebRequest "$KEYS/pqcrypta-agent-signing-mldsa.pub" -OutFile signing-mldsa.pub # Signature verification (needs openssl on PATH; OpenSSL 3.5+ for the ML-DSA step) openssl pkeyutl -verify -pubin -inkey signing.pub -rawin -in SHA256SUMS -sigfile SHA256SUMS.sig openssl pkeyutl -verify -pubin -inkey signing-mldsa.pub -rawin -in SHA256SUMS -sigfile SHA256SUMS.mldsa.sig # Checksum — compare against the SHA256SUMS entry for the .exe (Get-FileHash -Algorithm SHA256 pqcrypta-discovery.exe).Hash.ToLower() Select-String -Path SHA256SUMS -Pattern 'pqcrypta-discovery.exe'
Bootstrap trust out-of-band. The commands above already fetch the keys from different infrastructure than the binary: the third-party-hosted repo github.com/PQCrypta/discovery-agent-keys, which also keeps an append-only history of every release's signed manifest. (The same keys are mirrored at $BASE/verify/ on api.pqcrypta.com for environments that can't reach GitHub — if you use that mirror, fingerprint checking below is essential, since key and binary then share an origin.) Before trusting a fetched key, cross-check its fingerprint against the other anchors, each on separate infrastructure: (1) this documentation page (served from pqcrypta.com); (2) the GitHub repo above; and (3) a DNSSEC-signed DNS TXT record on _pqcrypta-signing.pqcrypta.com — the zone is DNSSEC-signed (DS in the .com root), so that record is cryptographically authenticated end-to-end, not spoofable by an on-path attacker:
delv TXT _pqcrypta-signing.pqcrypta.com # expect: "fully validated"
# The three published fingerprints (all three must agree): # Ed25519 (classical) sha256(pqcrypta-agent-signing.pub) c46d2fac1a3039e9a8aee8637e21eb39d6558da25a5814b97a29f7fb9237dee0 # ML-DSA-65 (FIPS 204, PQC) sha256(pqcrypta-agent-signing-mldsa.pub) 68ceb2dc10b4789eabef1f903d879408a15b3fe8cf2dc15eec3cb839fb981ddf # verify a downloaded key matches (Unix): sha256sum signing.pub # (PowerShell): (Get-FileHash -Algorithm SHA256 signing.pub).Hash.ToLower()
If a fetched key's fingerprint doesn't match all independent sources, do not run the binary.
The post-quantum signature is the point: we sign our own supply chain with the same hybrid classical-plus-ML-DSA transition we recommend to customers. Both signatures cover the same manifest, so a classical-only verifier is fully protected today, and a quantum-capable one gets FIPS 204 assurance — verifiable entirely offline, no certificate authority involved.

The scanner ships its own CBOM

The agent publishes a CycloneDX 1.6 Cryptographic Bill of Materials of itself at verify/agent-cbom.json (covered by the same signed SHA256SUMS). It documents, honestly, the cryptography the scanner itself uses: its transport is standard classical TLS 1.2/1.3 (via rustls), and its release supply-chain signatures are hybrid classical + ML-DSA-65. So the tool that inventories your cryptography discloses its own — answering the FAR-rule question about your vendors' cryptography before it's asked.

3. Run Modes

Exactly one of these three is required each run:

ModeFlagUse when
Submit--api-url + --api-key (or in config)Normal operation — results flow into the Compliance Dashboard.
Raw JSON--output results.jsonYou want the raw inventory as a file to process yourself.
Offline / air-gap--offline-report ./assessmentIsolated network — a complete report + CBOM computed locally, no network (§7).
# Submit to the dashboard ./pqcrypta-discovery --config agent-config.toml \ --api-url https://api.pqcrypta.com --api-key YOUR_KEY # Raw JSON, no submission ./pqcrypta-discovery --config agent-config.toml --output results.json # Offline air-gap bundle, zero network ./pqcrypta-discovery --config agent-config.toml --offline-report ./assessment

Command-line flags

FlagMeaning
--config <file>Config file (default agent-config.toml). Its scan_paths are authoritative — they replace the built-in catalog rather than adding to it.
--with-defaultsUse with --config to scan your paths and the built-in per-OS catalog (256 paths on Linux). Without it, a config listing three site-specific directories scans only those three.
--api-url / --api-keySubmission endpoint and key (also settable in config or the PQCRYPTA_API_URL/PQCRYPTA_API_KEY env vars).
--targets <t>Replaces the config's scan_paths (or the built-in defaults) for this run — the given targets become the authoritative list, not an addition to it.
--deep-scanSlower, more thorough traversal.
--algorithms <list>Comma-separated algorithm filter — report only matching assets. Same as algorithms_filter in the config.
--no-binary-scanSkip reading binaries for embedded crypto libraries for this run. Reported as a gap in coverage, never as a clean result.
--full-disk-scanAdd the filesystem root (/ or C:\). The built-in exclusions (pseudo-filesystems, temp dirs, WinSxS) still apply, so this widens the walk without churning through them — but if your config overrides excluded_paths, make sure it keeps them.
--output <file>Write raw JSON instead of submitting.
--offline-report <dir>Air-gap bundle: report.html + cbom.json + inventory.json, no network.
--submit-batch-size <n>Assets per submission request (default 100). Lower it only for a link that cannot carry the larger request bodies.
--submit-concurrency <n>Submission requests in flight at once (default 6). Lower it to ease load on a busy API server.
--allow-bulk-retirementConfirm that assets missing from this scan really are gone. A scan reports an inventory, and anything a device previously reported but no longer does is retired — but a scan run with narrowed scan_paths or --targets looks identical to a device that lost those assets. When a scan would retire more than half of what a device previously reported, the server refuses and leaves the stored inventory standing; this flag is how you say the assets really are gone. Small, ordinary retirements always proceed without it. Judged per device rather than per scan, so an appliance-only run is measured against that appliance's own store and not against the scanning host's untouched filesystem inventory.
--verboseDebug logging (including per-connection TLS timing).

4. Configuration Reference

The agent is fully config-driven. With --config, that file's scan_paths are exactly what gets scanned — nothing hidden. With no --config it falls back to a built-in per-OS default set, which is the same reference-{linux,windows,macos}.toml you can download and edit. Start from a role preset and adjust.

KeyTypePurpose
api_url / api_keystringSubmission target (omit for --output/--offline-report).
scan_paths[string]Directories/files to scan for certs, keys, SSH keys, keystores. Authoritative.
binary_scanboolRead executables and shared objects for the crypto library compiled into them (default true). The only surface that sees inside a closed-source vendor binary — and the only one whose cost scales with installed software rather than deployed cryptography, since it reads the string sections of every binary in scope. Set false where that budget does not exist; coverage then reports it as not configured, never as clean.
excluded_paths[string]Trees to skip. The built-in catalog already excludes pseudo-filesystems and temp dirs (plus WinSxS on Windows); setting this key replaces that list rather than extending it, so keep those entries when you override it.
deep_scanboolThorough traversal.
code_scan_paths[string]Source trees to scan for weak-crypto calls (CBOM). Off by default.
[[network_targets]]tablehost + port — a live TLS handshake inspecting the cert actually served. Ports that begin in plaintext and upgrade (SMTP 25/587, IMAP 143, POP3 110) are negotiated via STARTTLS automatically; implicit-TLS ports (443/465/993/995) handshake directly.
[[database_connections]]tablename, db_type, dsn — parses cert/key column values, never storing the raw value.
[[appliance_targets]]tableF5/NetScaler management-API scan (§6). Keys: name, appliance_type (f5 or netscaler), mgmt_url, username, password, verify_tls.
excluded_tables[string]Per [[database_connections]]. Tables to skip entirely. Point this at the inventory tables of a database that stores this tool's own results (crypto_assets, asset_scans) — every column in those reflects something already discovered elsewhere, so scanning them yields duplicate self-referential findings rather than new information.
excluded_columns[string]Per [[database_connections]]. Bare column names skipped across all tables, for when only specific columns are self-referential rather than a whole table.
submit_batch_sizeintAssets per submission request (default 100). Request-body size is the one submission limit that depends on the scanned material, so a host full of large certificate chains may want a smaller value.
submit_concurrencyintSubmission requests in flight at once (default 6).
keystore_passwords[string]Extra passwords tried against locked PKCS#12/PFX/JCEKS containers, on top of the built-in defaults (empty, changeit, password). Order does not matter and none are mapped to a specific store — the first that opens a container wins. A password is never submitted to the API, never logged, and never leaves the machine; only the already-cleartext certificate metadata inside is reported. A container that stays locked is reported as a gap in coverage rather than a clean result — its risk is raised and the finding names this setting as the fix, because an unopened keystore and an empty one otherwise look identical in an inventory.
keystore_passwords_filestringPath to a newline-separated password file (blank lines and # comments ignored), so the main config stays shareable while secrets sit in a tighter-permissioned file.
keystore_passwords_commandstringCommand whose stdout supplies passwords — e.g. aws secretsmanager get-secret-value --query SecretString --output text, op read op://vault/item/password, or gpg -d passwords.gpg. Resolved once at startup. A fourth source, the PQCRYPTA_KEYSTORE_PASSWORDS env var (newline-separated), merges with all of the above.
algorithms_filter[string]Report only assets matching these algorithms. Equivalent to --algorithms.

5. What It Scans

Nine distinct sources, each real and tested:

Every asset is then classified by post-quantum status, risk, and remediation ownership — who can actually fix it (you, a public CA, an OS vendor, a package manager, or a hardware vendor). Ownership is not decided by location alone: a copy of a public CA certificate is still the CA's wherever it sits on disk, so a final pass checks the certificate itself — roots by SHA-256 against the known trust anchors, intermediates by whether they chain to one. That pass only ever moves work off your list; it never marks something actionable that the location rules did not. Five compliance statuses are possible: compliant (standardized PQC or symmetric), legacy (classical and quantum-vulnerable — RSA, ECC, Ed25519, DH), deprecated (quantum-resistant but pre-standard, e.g. round-3 Dilithium on the pre-FIPS OID arcs), forbidden (broken regardless of the quantum timeline — MD5, SHA-1, DES, RC4, ECB, DSA, sub-2048-bit RSA), and unknown (an algorithm the scan could not determine, such as a passphrase-protected key, reported as unknown rather than guessed).

6. F5 / NetScaler Appliances

F5 BIG-IP and Citrix NetScaler are closed devices — no agent runs on them. Instead the agent queries each device's own management API remotely, from any host that can reach the management interface (a jump host is typical):

A read-only account is sufficient (F5 Auditor role / NetScaler read-only command policy). Beyond the certificate store, the agent collects the device's own facts — software version, platform (Virtual Edition detection), CPU/memory, management IP, self-IPs, subnets, gateway, and interfaces with MACs — so each appliance appears as its own first-class device in the dashboard. It also resolves, per certificate, which virtual servers actually serve it, or flags it as an unbound spare — the difference between a live cert and a forgotten one, which a live TLS scan of the VIP can't see. Factory certs (F5's ca-bundle/f5-irule/default, NetScaler's ns-server-certificate) classify as hardware-vendor; certs your org installed classify as internal.

[[appliance_targets]] name = "F5 BIG-IP Production" appliance_type = "f5" # "f5" or "netscaler" mgmt_url = "https://f5-mgmt.example.com" username = "readonly-user" password = "your-password-here" verify_tls = true # false only for a self-signed mgmt cert
On modern NetScaler CPX, the nsroot password is randomly generated per container — read it with docker exec <container> cat /var/deviceinfo/random_id.

7. Offline / Air-Gap Mode

For isolated or classified networks, one flag produces a complete assessment entirely on-host with zero network access — no API key, no callback:

./pqcrypta-discovery --config agent-config.toml --offline-report ./assessment

This writes a self-contained bundle to ./assessment/:

FileWhat it is
report.htmlA single self-contained compliance report (all styling inlined, no external requests) — safe to open on an air-gapped host. Discovery coverage (which of the nine surfaces were assessed, and any location the scan was refused), post-quantum readiness, risk breakdown, remediation ownership, full inventory.
cbom.jsonA CycloneDX 1.6 Cryptographic Bill of Materials, validated against the published bom-1.6.schema.json. assetType selects the shape: certificates carry certificateProperties, key material relatedCryptoMaterialProperties, and source-code findings algorithmProperties. metadata.component names the scanned host, so the file identifies its own subject if it travels separately from the rest of the bundle. Every bom-ref is unique as the spec requires, scoped by location — one certificate legitimately appears at many paths on a host (each vendored cacert.pem, the OS trust store, and the live endpoint serving it), so each occurrence is its own component, carrying evidence.occurrences[].location and a shared pqcrypta:sha256 that correlates them back to one certificate.
inventory.jsonEvery asset with full detail and its local assessment, plus a coverage object stating which discovery surfaces stand behind that list and every location the scan could not read — so a consumer ingesting the file knows its scope, not just its contents. Field-by-field breakdown →
Post-quantum scoring, risk rating, remediation-ownership classification, and CBOM assembly are all computed on the agent — the same logic the platform runs server-side — so an air-gapped report reads identically to an online one. Carry the bundle out on removable media; nothing ever leaves the host over the network. Verified by running under a network namespace with no interfaces at all.

7a. Self-Hosted / On-Prem Platform

Between hosted SaaS and single-binary offline mode is a third option: run the entire platform — API, PostgreSQL, and the Compliance Dashboard — inside your own network, and point agents at it. No inventory, CBOM, or telemetry leaves your environment.

./pqcrypta-discovery --config agent-config.toml --api-url http://your-host:3003

The agent is unchanged — only the target URL differs. The self-hosted tier ships as a docker compose stack (database + API + dashboard) with both a connected installer and a fully air-gapped bundle (images + schema + signed agent artifacts staged into one tarball, zero registry pulls on the destination). The dashboard's API base URL, CSP connect-src, and download links all follow your local API.

PieceWhere
Deployment packagedeploy/onprem/ — compose, Dockerfiles, .env.example, DB schema, scripts, README
Connected installdocker compose up -d --build after filling .env and dropping your license.json
Air-gap installscripts/build-airgap-bundle.sh → carry tarball → scripts/install-airgap.sh
Point the API basePQCRYPTA_API_BASE (dashboard) and --api-url (agents)
The self-hosted platform runs under a license post-quantum signed with ML-DSA (Dilithium3, NIST FIPS 204), verified entirely offline at startup — no phone-home, so it works in classified enclaves. A tampered license fails closed; an expired one continues in grace mode. ML/AI features are an opt-in add-on; the core platform runs without them.

8. Output & CBOM

However you run it, the inventory maps to standards-based output. In offline mode you get the CycloneDX 1.6 CBOM directly (cbom.json), covering exactly the host that was scanned. Online, the dashboard's Reports tab and /crypto-assets/export/cbom emit the same format across your whole estate, filtered by any combination of source, device, algorithm, certificate role, compliance status, and more. CSV and JSON report exports share the identical filter set, so a selection always describes the same rows.

The two are the same standard and the same assessment, not the same bytes: the online export keys bom-ref on the stored asset id, while the offline CBOM — which has no database behind it — keys on fingerprint plus location. Both link every asset to a shared algorithm component through the dependencies graph, and both put the quantum verdict in the spec's own nistQuantumSecurityLevel (0 for anything a quantum computer breaks), so a reader that has never heard of PQCrypta can assess either file. Both carry the certificate's SHA-256 in the standard hashes field, over the complete DER, so the same certificate is identifiable across the two and against openssl x509 -fingerprint -sha256.

9. Security & Least Privilege

10. Troubleshooting

SymptomCause / fix
"Specify one of --api-url, --output, or --offline-report"No mode chosen — pass one of the three (§3).
Appliance scan returns HTTP 401Wrong credentials, or (F5/NetScaler) a freshly created account whose password is flagged expired — reset it once. For CPX, read the generated nsroot password (§6).
Appliance scan can't connectRun from a host that can reach the management interface; set verify_tls = false only for a self-signed mgmt cert.
Scan finds nothingscan_paths is authoritative — confirm the paths exist and aren't all under excluded_paths. A path named with --targets is scanned even if a built-in exclusion covers it.
Scan finds far less than expectedA --config file replaces the built-in catalog. If yours lists only site-specific paths, everything the product knows about by default is out of scope — re-run with --with-defaults to scan both. On one real host the difference was 2,047 assets against 6,224, including 253 private keys the configured scan never saw.
Too much noiseAdd to excluded_paths (build trees, vendored dependencies). Package caches named InstallerCache, PackagesCache, DownloadCache and SoftwareDistribution are already pruned wherever they appear — every certificate in one is a duplicate of one found where it is deployed. Remember excluded_paths replaces the built-in list, so restate the pseudo-filesystems and temp dirs when you set it.
One appliance is unreachable but the rest matterThat's fine — an unreachable appliance only warns; the rest of the scan completes.