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:
curl -o pqcrypta-discovery https://api.pqcrypta.com/stream/downloads/discovery-agent/linux
chmod +x pqcrypta-discovery
curl -o pqcrypta-discovery https://api.pqcrypta.com/stream/downloads/discovery-agent/macos
chmod +x pqcrypta-discovery
curl -o pqcrypta-discovery https://api.pqcrypta.com/stream/downloads/discovery-agent/macos-intel
chmod +x pqcrypta-discovery
Invoke-WebRequest https://api.pqcrypta.com/stream/downloads/discovery-agent/windows -OutFile pqcrypta-discovery.exe
curl -sSL https://api.pqcrypta.com/stream/downloads/discovery-agent/install.sh | bash
Every binary is built on a native per-platform CI runner (Windows on Windows, both macOS builds on genuine Apple hardware). 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)
# (if your openssl.cnf loads an older oqsprovider and the key fails to load,
# prefix with OPENSSL_CONF=/dev/null to use the built-in provider)
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-missingWindows (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'$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()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:
| Mode | Flag | Use when |
|---|---|---|
| Submit | --api-url + --api-key (or in config) | Normal operation — results flow into the Compliance Dashboard. |
| Raw JSON | --output results.json | You want the raw inventory as a file to process yourself. |
| Offline / air-gap | --offline-report ./assessment | Isolated 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 ./assessmentCommand-line flags
| Flag | Meaning |
|---|---|
--config <file> | Config file (default agent-config.toml). Its scan_paths are authoritative. |
--api-url / --api-key | Submission 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-scan | Slower, more thorough traversal. |
--full-disk-scan | Add the filesystem root (/ or C:\). Pair with a config whose excluded_paths skips pseudo-filesystems and noise. |
--output <file> | Write raw JSON instead of submitting. |
--offline-report <dir> | Air-gap bundle: report.html + cbom.json + inventory.json, no network. |
--verbose | Debug 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.
| Key | Type | Purpose |
|---|---|---|
api_url / api_key | string | Submission target (omit for --output/--offline-report). |
scan_paths | [string] | Directories/files to scan for certs, keys, SSH keys, keystores. Authoritative. |
excluded_paths | [string] | Opt-in exclusions. A run skips nothing unless listed (recommended before --full-disk-scan). |
deep_scan | bool | Thorough traversal. |
code_scan_paths | [string] | Source trees to scan for weak-crypto calls (CBOM). Off by default. |
[[network_targets]] | table | host + port — a live TLS handshake inspecting the cert actually served. |
[[database_connections]] | table | name, db_type, dsn — parses cert/key column values, never storing the raw value. |
[[appliance_targets]] | table | F5/NetScaler management-API scan (§6). |
5. What It Scans
Seven distinct sources, each real and tested:
- Filesystem certs & keys — PEM/DER/CRT/CER and RSA/ECC/Ed25519 private keys, parsed for full X.509 detail.
- SSH keys — host and user keys (openssh-key-v1, authorized_keys, known_hosts), picked up from
.sshdirs automatically. - Keystores — JKS/JCEKS/PKCS#12/BKS/UBER/Oracle Wallet/NSS, opened and each entry inventoried at
store#alias. - Live TLS endpoints — a real handshake against
host:port, inspecting whatever certificate is actually being served right now. - Database columns — sampled cert/key column values are parsed for structural metadata, then discarded (never stored).
- Appliance stores — F5/NetScaler certificate stores over their management APIs (§6).
- Source code (CBOM) — calls into weak primitives (MD5, SHA-1 for security, DES/3DES, RC4, ECB, undersized RSA keygen).
Every asset is then classified by post-quantum status (compliant / legacy / forbidden), risk, and remediation ownership — who can actually fix it (you, a public CA, an OS vendor, a package manager, or a hardware vendor).
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):
- F5 BIG-IP — iControl REST (
/mgmt/tm/sys/file/ssl-cert) - NetScaler / ADC — NITRO API (
/nitro/v1/config/sslcertkey)
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 certnsroot 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 ./assessmentThis writes a self-contained bundle to ./assessment/:
| File | What it is |
|---|---|
report.html | A single self-contained compliance report (all styling inlined, no external requests) — safe to open on an air-gapped host. Post-quantum readiness, risk breakdown, remediation ownership, full inventory. |
cbom.json | A CycloneDX 1.6 Cryptographic Bill of Materials. |
inventory.json | Every asset with full detail and its local assessment. |
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:3003The 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.
| Piece | Where |
|---|---|
| Deployment package | deploy/onprem/ — compose, Dockerfiles, .env.example, DB schema, scripts, README |
| Connected install | docker compose up -d --build after filling .env and dropping your license.json |
| Air-gap install | scripts/build-airgap-bundle.sh → carry tarball → scripts/install-airgap.sh |
| Point the API base | PQCRYPTA_API_BASE (dashboard) and --api-url (agents) |
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). Online, the dashboard's Reports
tab and /crypto-assets/export/cbom produce the same CycloneDX 1.6 export, 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.
9. Security & Least Privilege
- Read-only. The agent never writes to, modifies, or deletes anything it scans.
- No secrets leave the host in offline mode. Database column values and private-key secret material are never transmitted; only structural metadata (algorithm, size, expiry) is kept.
- Appliances need only a read-only role (F5 Auditor / NetScaler read-only command policy).
- Run as an unprivileged user where possible — it only reads the paths you list. Elevated rights are needed only to read protected key directories.
- Credentials in config are plaintext —
chmod 600any config holding appliance or database passwords.
10. Troubleshooting
| Symptom | Cause / fix |
|---|---|
| "Specify one of --api-url, --output, or --offline-report" | No mode chosen — pass one of the three (§3). |
| Appliance scan returns HTTP 401 | Wrong 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 connect | Run from a host that can reach the management interface; set verify_tls = false only for a self-signed mgmt cert. |
| Scan finds nothing | scan_paths is authoritative — confirm the paths exist and aren't all under excluded_paths. |
| Too much noise | Enable excluded_paths (pseudo-filesystems, temp, package caches) before a --full-disk-scan. |
| One appliance is unreachable but the rest matter | That's fine — an unreachable appliance only warns; the rest of the scan completes. |