Quick start
The whole procedure, for someone who already knows they want this. Every step is expanded further down — verification, presets, what comes out.
# On a connected machine: fetch the binary, the manifest and both signatures curl -sO https://api.pqcrypta.com/stream/downloads/discovery-agent/linux curl -sO https://api.pqcrypta.com/stream/downloads/discovery-agent/verify/SHA256SUMS curl -sO https://api.pqcrypta.com/stream/downloads/discovery-agent/verify/SHA256SUMS.sig # Verify before it crosses the boundary, then carry all of it in on your media sha256sum -c SHA256SUMS 2>/dev/null | grep linux # Inside the enclave: run it, and read the report that lands beside it chmod +x ./pqcrypta-discovery ./pqcrypta-discovery --offline-report ./assessment --deep-scan
That writes assessment/report.html,
assessment/cbom.json and assessment/inventory.json. Open
the report in any browser on the host — it needs no server and makes no
requests. Add --redact-paths if the CBOM is going to leave the enclave
and your filesystem layout is itself sensitive
(what that does).
What offline mode is
Offline mode is the same binary doing the same discovery, with the submission step replaced by a local assessment. Instead of posting findings to an API and letting the platform score them, the agent scores them itself and writes the finished result next to itself on disk.
Concretely, in this mode the agent:
- opens no outbound connection of any kind — no submission, no telemetry, no update check, no licence call, no DNS beyond what a network target you explicitly configured would need;
- accepts no API URL and no API key. There is no credential to provision for an air-gapped host, and none to leak on one;
- needs no installation. One executable, no runtime and no interpreter. On Linux it carries its own OpenSSL and is built against glibc 2.17, so it runs on Amazon Linux 2, RHEL 7, RHEL 8 and anything newer without a dependency to satisfy inside the enclave. Every release is run on each of those distributions before it is published;
- writes only to the output directory you name, and reads everything else read-only.
Nothing about the enclave leaves the enclave. The only artifacts that exist are the files in your output directory, on the machine you ran it on. Whether they cross your boundary, and after what review, is entirely your decision — the tool has no opinion and no channel.
Everything needed to reach a verdict is compiled into the binary: the compliance rules, the risk model, the remediation-ownership classifier, the parameter-set-to-NIST-level mapping and the CycloneDX writer. There is no rules file to keep current and no lookup to perform.
Who runs it offline — and why
“Air-gapped” is the easiest case to describe, so it tends to be the only one described. It is not the common one. Most hosts that get scanned this way have a perfectly good route to the internet — what they do not have is permission to use it. A machine that cannot reach a collector and a machine that may not send anything to one need exactly the same thing from a scanner, so offline mode is written for the whole set:
- Classified enclaves and SCIFs with a true air gap
- OT / ICS / SCADA at Purdue levels 0–2 — plant floor, safety instrumented systems, historians
- Ships, aircraft, forward-deployed and remote sites; satellite links too constrained to carry telemetry
- Build, lab and staging networks deliberately created with no egress
- Federal systems where nothing leaves the authorization boundary. Sending inventory to an outside service is a boundary change — it can require re-accreditation, and the receiving service to be FedRAMP-authorized
- CUI and classified handling rules: hostnames, certificate subjects and internal topology are themselves controlled information
- Data residency and sovereignty — GDPR and national rules where the data may not cross a border
- PCI DSS cardholder data environments and other segmented zones with tightly controlled egress
- Contracts that bar third-party SaaS from touching a customer's estate
- Vendor security review, DPA or procurement still in flight — the deadline is not waiting for it
- Firewall exception backlog: a new outbound TLS destination needs an approval that takes longer than the scan
- Evaluating the tool before creating an account or provisioning a key
- Third-party and vendor-risk assessments of an estate you do not own
- M&A due diligence on a target company's cryptography
- Incident response, forensics and legal hold, where a point-in-time, self-contained artifact matters more than a live feed
- Red-team and audit deliverables — one engagement, no ongoing telemetry, no credential left behind on the host
- Minimising footprint: no persistent service, no API key stored anywhere
Offline mode is not a reduced tier for difficult hosts. The discovery is identical, the scoring is identical (and pinned by tests to stay that way), and the CBOM is the same CycloneDX 1.6 document the connected platform produces. The only thing you give up is the dashboard and the history that comes from submitting repeatedly — what connected mode adds covers exactly what that is, and the self-hosted stack gets it back without anything leaving your network.
Why an isolated system still owes an inventory
On June 22, 2026 the White House signed Executive Order 14412, “Securing the Nation Against Advanced Cryptographic Attacks”, turning post-quantum migration from guidance into mandate. OMB's binding execution memo, M-26-15, followed two days later: every agency owes a PQC Migration Plan within 120 days — roughly October 22, 2026 — and the memo names its first phase (2026–2027) explicitly as inventorying cryptographic systems.
Scope is federal high-value assets and high-impact systems. Which is to say: the mandate lands hardest precisely on the systems that are hardest to inventory. Those systems cannot be reached by a network scanner, or are barred from sending anything to one, or sit inside an authorization boundary that a third-party collector would change — and they frequently run the oldest cryptography in the estate. None of that is an exemption. The plan is still due. The deadlines still apply, and they apply to exactly the assets that are least convenient to enumerate.
The CBOM language is aimed at tooling
The order directs CISA and NIST to define the minimum elements of a Cryptographic
Bill of Materials, which must “enable the automated assessment of the
cryptographic assets utilized by a hardware or software element.” That
phrase is the bar: a document a human can read is not enough, it has to be
assessable by machine. It is why this agent puts the quantum verdict in
nistQuantumSecurityLevel and a dependencies graph rather
than only in vendor-namespaced properties —
see what the CBOM actually contains.
Not a government-only deadline
The order directs the first proposed contractor FAR rule within 180 days, so anyone selling to the federal government inherits the requirement through procurement and passes it to their own suppliers. If you run a segregated or classified environment on behalf of an agency, the obligation arrives in your contract rather than in the Federal Register. Section 6 goes further, directing shared procurement of PQC tools — the order does not merely permit agencies to buy cryptographic discovery tooling, it organizes how they will buy it.
Offline mode is the compliance path for the systems that have no other one. The host produces its own inventory, its own CBOM and its own readiness assessment, in place, and you carry out a bundle. Nothing about the system has to be exposed — to us or to anyone — in order to satisfy a requirement about that system. Whether the constraint is a missing cable or a signed policy makes no difference to how the tool behaves.
What runs offline
All nine discovery sources run in offline mode — the mode changes what happens to the findings, not how they are found. Worth being precise about one thing: “offline” means no route to us, not necessarily no network at all. An enclave with its own internal LAN can still scan the endpoints and appliances on it; the agent simply never reaches beyond them.
| Source | Needs | What it finds |
|---|---|---|
| Filesystem certs & keys | Nothing | PEM/DER/CRT/CER, RSA/ECC/Ed25519 keys, multi-cert bundles, OpenSSL trust-augmented entries, CSRs and key-exchange parameter files. |
| Native OS certificate store | Nothing | Windows CryptoAPI (MY/Root/CA/TrustedPublisher, LocalMachine and CurrentUser) or the macOS System and login Keychains. Always scanned, no configuration. |
| SSH keys | Nothing | Host and user keys, authorized_keys, known_hosts. |
| Keystores | Nothing |
JKS/JCEKS/PKCS#12/BKS/UBER/Oracle Wallet/NSS, each entry inventoried at store#alias. |
| Binaries | Nothing | The crypto library compiled into an executable or shared object, read from the version banner it embeds — OpenSSL, LibreSSL, BoringSSL, GnuTLS, Libgcrypt, mbedTLS, wolfSSL, NSS, libsodium, across ELF, PE and Mach-O. The only source that sees inside a closed-source vendor binary, where a statically linked end-of-life OpenSSL appears in no certificate inventory at all. Graded on post-quantum capability rather than as an algorithm. The one source whose cost scales with installed software rather than deployed cryptography, so it can be turned off with binary_scan = false — which coverage then reports as not configured, never as clean. |
| Source code (CBOM) | Nothing | Weak-crypto call sites in your own code — MD5, SHA-1, DES, RC4, ECB, undersized RSA. Set code_scan_paths; this is the source-level half of a CBOM and it needs no network whatsoever. |
| Database columns | Local or LAN DSN | Certificates and keys stored in tables. Structural metadata only — the raw value is parsed and discarded. |
| Live TLS endpoints | LAN reachability | A real handshake against host:port inside the enclave, inspecting whatever certificate is actually being served. |
| F5 / NetScaler appliances | LAN + read-only account | The appliance's own certificate store via iControl REST / NITRO, including staged certs bound to nothing. |
A fully isolated single host still gets six of the nine without configuring anything at all — filesystem, native store, SSH, keystores, binaries (read for the crypto library compiled into them) and source code are all local reads. The other three are opt-in and only need to reach machines you already control.
Get it in, verified
An air gap moves the trust problem to the boundary: whatever you carry across, you should be able to prove is what we published. Verify on the connected side, then carry the verified bytes in.
The four binaries
Each is a self-contained executable — no installer and no package manager.
The Linux build links OpenSSL 3.5.7 statically and targets glibc 2.17, so it needs
nothing from the host beyond a C runtime that every Linux since RHEL 7 already has,
Amazon Linux 2 included. macOS and Windows use the platform TLS stacks and carry no
OpenSSL at all. The one place it will not run is a musl distribution such as Alpine,
which is a deliberate trade: a fully static musl build cannot dlopen,
and Oracle database scanning needs exactly that. On a connected Linux or macOS host,
install.sh downloads and
verifies in one step into the current directory (it does not touch anything
system-wide), which is a convenient way to prepare transfer media.
Verify before it crosses
Every release is published with a SHA256SUMS manifest signed
twice: classically with Ed25519, and with post-quantum
ML-DSA-65 (FIPS 204). A tool that tells you your signatures will not
survive a quantum adversary ought to be signed with one that will.
BASE=https://api.pqcrypta.com/stream/downloads/discovery-agent curl -O $BASE/linux # or windows | macos | macos-intel curl -o SHA256SUMS $BASE/verify/SHA256SUMS curl -o SHA256SUMS.sig $BASE/verify/SHA256SUMS.sig curl -o SHA256SUMS.mldsa.sig $BASE/verify/SHA256SUMS.mldsa.sig curl -o pqcrypta-agent-signing.pub $BASE/verify/pqcrypta-agent-signing.pub # 1. classical signature over the manifest openssl pkeyutl -verify -pubin -inkey pqcrypta-agent-signing.pub \ -rawin -in SHA256SUMS -sigfile SHA256SUMS.sig # 2. the manifest pins the binary sha256sum -c SHA256SUMS --ignore-missing
The public keys are published from a second, independent origin — the append-only discovery-agent-keys repository — so you can compare the key you verified with against a copy we do not serve. The full procedure, including ML-DSA verification, is in §2 of the guide.
Verify the manifest signature, not just the hash. A hash alone only proves the file matches a list; the signature is what proves the list is ours. Both checks are cheap and neither needs the enclave.
Configuration presets
With no --config, the agent uses its built-in per-OS path catalog, which is
a good default for a first look. For a repeatable air-gapped scan, carry a TOML file in
alongside the binary: when one is supplied its scan_paths are
authoritative — the agent scans exactly that list and nothing
else, which is usually what an enclave's change control wants to see.
That replacement is deliberate, and it is also the easiest way to under-scan: a
config listing a handful of site-specific directories scans only those,
and the built-in catalog — 256 paths on Linux, 67 on Windows, 64 on macOS
— drops out entirely. Add --with-defaults to scan both,
de-duplicated, with the built-in exclusions merged in so the wider walk still
skips /proc, /sys and WinSxS. Three modes, then: the
catalog alone (no --config), your file alone (--config),
or both (--config … --with-defaults).
The three reference-* catalogs are the built-in defaults written out as
editable files, so no config at all and --config reference-linux.toml
scan the same set. To narrow a scan, delete paths; to widen it, add your own. Two
settings matter more than usual on an isolated host:
keystore_passwords— passwords tried against locked PKCS#12/PFX/JCEKS containers. A container that stays locked is reported as a gap in coverage rather than a clean result, because an unopened keystore and an empty one otherwise look identical. Passwords are never written to any output file.excluded_paths— pairs with--full-disk-scanto keep a whole-volume sweep out of pseudo-filesystems and build caches.
Package caches are pruned without being configured. Directories named
InstallerCache, PackagesCache, DownloadCache
and SoftwareDistribution are skipped wherever they appear, because
every certificate in one is a second copy of a certificate the scan finds where it
is actually deployed. On one Windows workstation a single vendor’s installer
caches contributed 914 assets that were 8 distinct certificates —
the same code-signing and root certificates copied into every package-version
directory, roughly 114 times each, 12.6 % of that host’s entire
inventory. Counting them finds nothing and moves every compliance percentage for
the host. They are matched by directory name rather than by
excluded_paths because they sit under a different vendor path on
every machine, and the match is deliberately narrow: an application’s data
directory is not a cache, and InstallerCacheTools is not
InstallerCache.
Running it
One flag switches the run from “submit” to “assess locally”:
--offline-report <dir>. The directory is created if it does not exist.
Linux / macOS
chmod +x ./pqcrypta-discovery
./pqcrypta-discovery --config linux-web-server.toml \
--offline-report ./assessment --deep-scan
Windows
.\pqcrypta-discovery.exe --config windows-server.toml `
--offline-report .\assessment --deep-scan
Flags that change what an offline run covers:
| Flag | Effect |
|---|---|
--deep-scan |
Raises the directory recursion limit from 10 to 20 levels. Worth it on nested application trees; costs time, not correctness. |
--full-disk-scan |
Adds the filesystem root to the targets. Pair with a config whose
excluded_paths skips pseudo-filesystems and noise. |
--algorithms <list> |
Keep only assets whose algorithm matches, e.g. RSA,ECDSA.
Filters the inventory, not the walk. |
--targets <path> |
Override the config's paths entirely for a one-off scan. |
--verbose |
Debug logging, including every file considered. Useful the first time you run inside an enclave and want to see exactly what was touched. |
Elevation changes coverage, not behaviour. Unprivileged, the agent reports the directories it could not read rather than skipping them silently — those warnings are the honest edge of your inventory. Running elevated closes them.
What it produces
Three assessment artifacts, written to the directory you named, deliberately different for three different readers — a person, a tool, and a system of record — plus two more that let a fourth party check none of them changed on the way out (the evidence bundle).
| File | For | What it is |
|---|---|---|
report.html |
A person | A single self-contained assessment you can open on the host itself. |
cbom.json |
A machine | CycloneDX 1.6 Cryptographic Bill of Materials, schema-valid. |
inventory.json |
Your systems | Every asset, every field, plus the assessment for each. |
One file. Every style rule and every row of data is inlined, so it makes no external request of any kind — no CDN, no font, no analytics, no image fetch. It opens in a browser on a machine that has never been online, and it renders identically on the outside.
What is in it
- Header line — generation time in UTC, the host and its OS,
the scan UUID, and an
OFFLINE / AIR-GAPbadge that marks the provenance of the document. - Five headline figures — total cryptographic assets, post-quantum readiness as a percentage, actionable (you own), critical risk, and high risk. The second and third are the ones that matter: readiness is the migration measure, and “actionable” is the part you can start on without waiting for a vendor.
- By remediation owner — assets grouped into
internal,public_ca,os_vendor,language_ecosystem,hardware_vendorandunknown. This is what turns a flat list into a work plan. - Top algorithms — the estate by algorithm, so “what are we actually running” is answered in one glance.
- Discovery coverage — which of the nine discovery
surfaces this run actually assessed, stated before the findings rather than
left implied by them. Each surface reads as found, scanned-and-clean, never
configured, or not applicable on this platform, and any location the scan was
refused is listed by path and reason and counted as unassessed —
explicitly not as clean. No scanner can honestly claim it found everything;
this is the part that says exactly what was looked at, so the totals below it
carry their own scope. The same block is in
inventory.jsonundercoveragefor machine consumption. - Host cryptographic posture — what the machine itself offers, which bounds what it can migrate to: TPM, Secure Boot, disk encryption, OpenSSL version, the crypto libraries present, and the CPU's crypto instructions. A check that could not run says Not assessed rather than rendering blank, because on an unprivileged run several of them cannot be read and a blank would look like a negative result. A probe that is killed counts the same way: every one is bounded, and a field whose probe ran out of time is reported absent rather than filled with a placeholder. The row is omitted entirely instead of printing “unknown”, which would present a measurement that was never taken as one that failed. On Windows the OS name, version, build and CPU are read from the registry rather than from WMI, which can stall for minutes under the disk load the scan itself creates.
- Full asset inventory — location, algorithm, bits, status, risk, owner and expiry for every asset, sorted worst-first.
It stays usable at scale
A stock Windows host yields well over seven thousand assets. The table is paginated and filterable from data embedded in the page — free-text search plus dropdowns for status, risk and owner, and an actionable only toggle — all of it working with no server, no network and no build step. With scripting disabled the first hundred rows are still present as plain markup, and a print stylesheet renders the whole thing on white for a paper trail.
A CycloneDX 1.6 Cryptographic Bill of Materials, validated against
the published bom-1.6.schema.json with zero errors.
This is the file to hand to an auditor, feed to a tool, or diff against last
quarter's.
The document identifies its own subject
metadata.component names the machine the inventory is about,
not just the tool that produced it — because cbom.json is the file
most likely to travel on its own, separated from the report and inventory that name
the host in prose. A fresh serialNumber per run means two assessments
are never mistaken for one another.
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:9736ddc0-9a4b-440e-821f-ada23574ff26",
"version": 1,
"metadata": {
"timestamp": "2026-08-07T20:26:54.361158110+00:00",
"tools": [{ "vendor": "PQCrypta", "name": "discovery-agent", "version": "1.0.45" }],
"component": {
"type": "device",
"bom-ref": "host:pqcrypta.com",
"name": "pqcrypta.com",
"version": "Ubuntu 24.04.4 LTS"
},
"properties": [{ "name": "pqcrypta:mode", "value": "offline" }]
}
}
The host component also carries the machine's cryptographic posture as
properties — TPM, Secure Boot, disk encryption, OpenSSL
version, CPU crypto features, and whether the scan ran privileged, since an
unprivileged scan has blind spots the reader should know about. CycloneDX has
no schema field for any of that, and properties is precisely what
the spec provides for it. The network inventory is deliberately left
out: addresses, MACs, gateways and subnets stay in
inventory.json, because the CBOM is the artifact most likely to
leave the enclave and an isolated estate's internal topology has no business
travelling with it.
Every asset is a component
assetType selects the shape: certificates carry
certificateProperties, key material carries
relatedCryptoMaterialProperties, source-code findings and parameter
files carry algorithmProperties. The SHA-256 goes in the standard
hashes field over the complete DER, so the same certificate is
identifiable against openssl x509 -fingerprint -sha256 and against your
other tooling.
{
"type": "cryptographic-asset",
"bom-ref": "5cc3d78e…70fd@/etc/ssl/certs/CFCA_EV_ROOT.pem",
"name": "CFCA EV ROOT",
"cryptoProperties": {
"assetType": "certificate",
"certificateProperties": {
"subjectName": "CFCA EV ROOT",
"issuerName": "CFCA EV ROOT",
"notValidBefore": "2012-08-08T03:07:01+00:00",
"notValidAfter": "2029-12-31T03:07:01+00:00",
"certificateFormat": "X.509"
}
},
"hashes": [{ "alg": "SHA-256", "content": "5cc3d78e…70fd" }],
"evidence": { "occurrences": [{ "location": "/etc/ssl/certs/CFCA_EV_ROOT.pem" }] },
"properties": [
{ "name": "pqcrypta:location", "value": "/etc/ssl/certs/CFCA_EV_ROOT.pem" },
{ "name": "pqcrypta:algorithm", "value": "RSA" },
{ "name": "pqcrypta:key_size", "value": "4096" }
]
}
Each bom-ref is unique as the spec requires, scoped by fingerprint
and location: one certificate legitimately appears at many paths on a host
— each vendored cacert.pem, the OS trust store, 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.
The quantum verdict is in spec fields, not ours
This is the part that makes the file assessable rather than merely
readable. Every asset is linked through the dependencies graph to a
shared algorithm component carrying the CycloneDX primitive, the
parameterSetIdentifier, and
nistQuantumSecurityLevel — where
0 is the spec's way of saying “offers no security against a
quantum adversary”.
{
"type": "cryptographic-asset",
"bom-ref": "algorithm:ecc-secp256r1--p-256-",
"name": "ECC-secp256r1 (P-256)",
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": "key-agree",
"parameterSetIdentifier": "secp256r1 (P-256)",
"nistQuantumSecurityLevel": 0
}
},
"properties": [{ "name": "pqcrypta:quantum_vulnerable", "value": "true" }]
}
// …and the edge that makes it reachable from the asset:
{
"ref": "5cc3d78e…70fd@/etc/ssl/certs/CFCA_EV_ROOT.pem",
"dependsOn": ["algorithm:rsa-4096"]
}
A consumer that has never heard of PQCrypta can therefore answer “which of
these assets are quantum-vulnerable, and which primitive family do they belong
to” from standard fields alone. The primitive value also splits
the estate into the two workloads federal guidance treats separately:
kem and key-agree are key establishment;
signature is digital signatures.
Everything the agent found and everything it concluded, with nothing dropped for
presentation. Five top-level keys — scan_id,
host, generated, host_metadata and
assets — with up to 55 fields per asset and
35 fields of host posture.
host_metadata is the machine's own security posture, collected
during the same scan: platform and kernel, TPM, Secure Boot, disk encryption,
SELinux/AppArmor, firewall, antivirus, UAC, SIP/Gatekeeper, OpenSSL version,
crypto libraries, CPU crypto features, virtualisation and container runtime,
plus the host's network interfaces and addresses. On an air-gapped host this is
the only place that information is going to be recorded, so none of it is
dropped.
Four groups of fields
- Identity & location —
location,location_type,asset_type,sha256_fingerprint,hostname,port,owner,application,environment, file size, permissions and modification time. - Cryptography —
algorithm,key_size,curve_name,hash_algorithm,key_format. - Full X.509 detail — subject and issuer broken into CN/O/OU/C/ST/L, serial, validity window, CA flag and path length, self-signed flag, SANs, key usage and extended key usage, CRL distribution points, OCSP URLs, policy OIDs, and both key identifiers.
- The assessment —
compliance_status,risk_level,recommendation,remediation_owner,is_actionableandremediation_note.
{
"location": "/etc/ssl/fated/server/smtp.fated.org.key",
"location_type": "filesystem",
"asset_type": "private_key",
"algorithm": "RSA",
"key_size": 4096,
"sha256_fingerprint": "66c2dc47f67b…d739",
"compliance_status": "legacy",
"risk_level": "high",
"recommendation": "Quantum-vulnerable. Migrate to Hybrid (ML-KEM-1024 + ML-DSA-87) or Pure PQ.",
"remediation_owner": "internal",
"is_actionable": true,
"remediation_note": "You hold the private key — this key material is yours to rotate."
}
And one complete, unedited record, so the shape is not left to
the imagination. This is a real certificate from a real run — the default
self-signed certificate Debian ships — with only pem_encoding
removed for length. Every field present here is present because the certificate
had it; the 55 in the schema are the union across all asset types, not a
requirement each asset meets.
{
"location": "/etc/ssl/certs/ssl-cert-snakeoil.pem",
"location_type": "filesystem",
"asset_type": "certificate",
"certificate_type": "leaf",
"algorithm": "RSA",
"key_size": 2048,
"hash_algorithm": "SHA256-RSA",
"key_format": "X.509 Certificate (PEM)",
"sha256_fingerprint": "f0bbe4e6bd8bc113d10ede5f768e3549b1f10f938356c292bb0bef3cd269734c",
"serial_number": "4d6fd258d6418a5e6bc68c7df5ad1eb3d85c939a",
"subject": "CN=pqcrypta.com",
"subject_cn": "pqcrypta.com",
"issuer": "CN=pqcrypta.com",
"issuer_cn": "pqcrypta.com",
"is_ca": false,
"is_self_signed": true,
"subject_alternative_names": ["DNS:pqcrypta.com"],
"not_before": "2025-08-19T22:21:50Z",
"not_after": "2035-08-17T22:21:50Z",
"owner": "uid:0",
"file_permissions": "644",
"file_size_bytes": 1103,
"last_modified": "2025-08-19T22:21:50Z",
"application": "OS trust store",
"compliance_status": "legacy",
"risk_level": "high",
"recommendation": "Quantum-vulnerable. Migrate to Hybrid (ML-KEM-1024 + ML-DSA-87) or Pure PQ.",
"remediation_owner": "os_vendor",
"is_actionable": false,
"remediation_note": "OS trust store — updated via the system package manager.",
"subject_key_identifier": "355b7bead4b7d3b037f94171d4b017e658363b73",
"metadata": {
"certificate_type": "leaf",
"file_path": "/etc/ssl/certs/ssl-cert-snakeoil.pem",
"is_ca": false,
"version": 2
}
}
Note is_actionable: false with
remediation_owner: "os_vendor". The certificate is quantum-vulnerable
and graded high, but nobody on your side can rotate it — it
arrives and leaves with the OS package. That distinction is what stops a
migration plan from opening thousands of tickets nobody can close, and it is
computed on the host like everything else.
No secret material is ever written. A private key is recorded as an asset — algorithm, size, curve, format, whether it is passphrase-protected — but its key bytes are not copied into any output file. Database column values are parsed for structure and discarded. Keystore passwords you supply are used to open containers and never appear in output.
The evidence bundle
A report answers “what is on this host”. It does not answer the question the person receiving it eventually asks — is this the bundle that scanner produced, unmodified? Two more files, written every run with no flags and no key management, answer that one.
| File | What it carries |
|---|---|
manifest.json |
Provenance: agent version, assessment id, hostname, platform, asset count, whether paths were redacted — and a SHA-256 digest and byte count for each of the three artifacts. |
SHA256SUMS |
The classic format, so sha256sum -c SHA256SUMS checks
the bundle with no tooling from us. It covers
manifest.json as well — without that, a
signature would seal the payload while leaving the hostname,
timestamp and agent version freely editable. |
Signing it, with a key that is yours
Unsigned, the digest list makes modification detectable only if the recipient got that list over a channel they trust — inside a chain of custody that is often exactly the case, which is why it is the default rather than an extra. Where the bundle has to survive an untrusted channel, sign it. The keys are yours: a vendor signature on your assessment would attest to a host we never saw, and our release keys never touch your machine.
# Once: generate a keypair and keep the .key files. An ML-DSA-65 secret key is not
# something `openssl rand` can produce, which is why this exists.
./pqcrypta-discovery --sign-keygen ./keys
# Every run after that
./pqcrypta-discovery --offline-report ./assessment \
--sign-key keys/assessment-ed25519.key \
--sign-key-mldsa keys/assessment-mldsa65.key
# On the far side of the boundary, with stock tools only
cd assessment
sha256sum -c SHA256SUMS
openssl pkeyutl -verify -pubin -inkey assessment-signing.pub \
-rawin -in SHA256SUMS -sigfile SHA256SUMS.sig
Ed25519 answers an adversary who has to break the signature now; ML-DSA-65 (FIPS 204) answers the one who records the bundle today and factors at leisure. Use both where the evidence has to outlive the classical assumption. This is the same structure the agent's own release is signed with, so the verification you ran on the binary coming in is the one you run on the assessment going out.
Scored on the host — and scored the same way
An offline assessment is worth little if it is a lesser assessment. Post-quantum scoring, risk grading, remediation-ownership classification and CBOM assembly all run on the agent, using the same rules the platform applies at ingestion. Ownership in particular is not a second copy of those rules kept in step by hand — that was tried, and it drifted. The agent’s rule table is generated from the platform’s and evaluated with the same matcher, so a rule change reaches both sides from one place. The same holds for the trust-anchor set behind the content-aware pass, which recognises a copy of a public CA certificate as the CA’s wherever it was found: one table on the platform, compiled into the agent from it. The last audit ran every asset in a live inventory through the agent’s own classifier and compared it against the platform’s: identical on all 10,224, and on every asset re-checked since. An air-gapped report reads identically to a connected one for the same asset.
Five compliance statuses, applied identically in both modes:
| Status | Meaning |
|---|---|
| compliant | Standardised post-quantum cryptography — ML-KEM, ML-DSA, SLH-DSA, HQC and friends. |
| legacy | Classical and quantum-vulnerable: RSA, ECC, finite-field DH, EdDSA. Works today, must migrate. |
| deprecated | Quantum-resistant but not the standard — pre-FIPS round-3 parameter sets, for example. |
| forbidden | Broken independent of quantum: MD5, SHA-1 in a verified signature, DES, RC4, ECB, undersized RSA. |
| unknown | Honestly undetermined — an encrypted key whose algorithm is inside the ciphertext, an unrecognised algorithm OID, or a container that could not be opened. Never guessed. |
Unattended & repeatable
An inventory is a maintenance obligation, not a one-off. The agent is a single-shot process with no daemon and no state directory, which makes scheduling it trivial — each run overwrites the previous bundle, or writes to a dated directory if you want the history.
0 3 1 * * /opt/pqcrypta/pqcrypta-discovery \
--config /etc/pqcrypta/agent.toml --deep-scan \
--offline-report /var/lib/pqcrypta/$(date +\%Y-\%m) >> /var/log/pqcrypta-scan.log 2>&1
schtasks /create /tn "PQCrypta Assessment" /sc monthly /d 1 /st 03:00 /ru SYSTEM ` /tr "C:\pqcrypta\pqcrypta-discovery.exe --config C:\pqcrypta\agent.toml --deep-scan --offline-report C:\pqcrypta\assessment"
Running as SYSTEM (or root) is what closes the permission-denied gaps
— unprivileged runs report the directories they could not read rather than
pretending they were empty.
Exit codes
| Code | Meaning |
|---|---|
0 | The bundle was written. Including when individual paths were unreadable — those are reported in the log and are a coverage note, not a failed run. |
1 | The run could not produce a bundle: no mode selected, an unwritable output directory, an unparseable config. |
So a scheduler that only watches the exit status will not notice reduced coverage.
If that matters, watch the log for Access is denied /
Permission denied lines, or diff the asset count between runs —
a sharp drop is the signal.
A third mode: raw findings
Between submitting and a full assessment there is --output <file>,
which writes the discovered assets as raw JSON with no local
assessment — no compliance status, no risk, no ownership, no CBOM.
It exists for pipelines that do their own scoring. If you want the assessment, use
--offline-report; the two are alternatives, not layers.
Carrying results out
What crosses the boundary is your call; the tool has no channel of its own. In practice there are two useful patterns:
- Review inside, carry out the machine-readable pair. Read
report.htmlon the host, then movecbom.jsonandinventory.jsonout for aggregation. Both are plain text and diff cleanly, so they survive whatever review your boundary imposes. - Carry out the CBOM only. It identifies its own subject host, so
it stands alone as a document. If your filesystem layout is itself sensitive,
generate it with
--redact-paths— see below.
Redacting paths from the CBOM
On a real host the paths are not incidental detail: a key filename names an internal
host, a directory names installed software, and the set of them maps the estate.
Passing --redact-paths replaces every path in cbom.json
with a stable surrogate:
./pqcrypta-discovery --offline-report ./assessment --deep-scan --redact-paths # every path becomes path:<16 hex>, derived from SHA-256 of the path itself "bom-ref": "7bfa52e2…78639@path:1b0de366d645ce07"
Do not try to do this with jq after the fact. Paths
appear in five places — components[].bom-ref,
evidence.occurrences[].location, the
pqcrypta:location property, dependencies[].ref, and
components[].name for assets with no subject. Stripping the path
from a ref also destroys its uniqueness: one certificate legitimately sits at
dozens of paths, so on a real 6,939-asset scan a naive strip collapsed
6,975 components onto 1,449 refs and left a document no
consumer can resolve. The surrogate is derived from the path so it stays 1:1,
which is what keeps the refs unique.
Because the surrogate is a hash of the path rather than a counter, it is
stable across runs — two redacted CBOMs from different
quarters still diff cleanly — and an operator inside the enclave who still has
the un-redacted inventory.json can re-derive the mapping by hashing a
known path. No key file has to exist, and none is written.
What it does not remove: certificate subjects, SANs, issuers, the
host's own name and the host posture fields. Those are the substance of a CBOM
— removing them would leave a document that cannot be assessed. Redaction here
is specifically about filesystem topology; if subject names are also
controlled in your environment, the CBOM needs review before export like any other
artifact. report.html and inventory.json keep their real
paths either way: they are your working copies and they stay on the host.
On the outside, the same inventory can be loaded into the Compliance Dashboard alongside connected hosts, so an air-gapped enclave appears in the same estate-wide readiness numbers as everything else rather than living in a spreadsheet nobody re-runs.
Re-running is cheap and produces a directly comparable document: same fields, fresh
serialNumber, new timestamp. Quarterly re-runs turn a one-off audit into a
trend line — which is what “maintain a cryptographic inventory”
actually asks for.
Threat model
If you are putting an unfamiliar binary on an isolated system, the questions are what it can reach, what it can change, and what it leaves behind. Stated plainly, and stated as things you can check rather than things we assert:
| Concern | What holds | How to confirm it yourself |
|---|---|---|
| Data leaving the host | No AF_INET/AF_INET6 socket is ever opened
in offline mode unless you configure a network scan target. On a
524-asset run: 0 internet sockets, against 20
AF_NETLINK (reading local interfaces for the host
posture) and 5 AF_UNIX (name-service lookups that fail
closed). |
strace -f -e trace=socket,connect ./pqcrypta-discovery
--offline-report ./out |
| Writes to the system | Everything outside the output directory you name is opened
read-only. No installer, no service, no registry key, no config
written back, nothing in /etc. |
strace -f -e trace=openat, filtered on
O_WRONLY/O_RDWR |
| Secret material at rest | Private keys are parsed for their properties — algorithm,
size, curve, whether they are passphrase-protected — and the
key material is never written to any output file. Only public
certificates carry a pem_encoding. |
grep -c "PRIVATE KEY" assessment/*.json → 0 |
| Credentials on the host | Offline mode accepts no API key and no API URL. There is nothing to provision and nothing left behind to steal. | Passing --api-key with
--offline-report is rejected |
| Supply chain | Every release is signed twice — Ed25519 and ML-DSA-65 — and the manifest is published to a second, append-only origin so a compromise of one does not go unnoticed. | Verify before it crosses |
| Persistence | The process reads, classifies, writes its output directory and exits. Nothing schedules itself, nothing survives the run. | ps after exit; no unit, task or cron is created |
What we cannot promise, and will not claim: the agent reads whatever the account running it can read. Run it unprivileged and it sees less; run it as root and it can read private key files, which is the point but is also a decision you are making. The pitfalls below cover what each choice costs you in coverage.
Prove the air gap yourself
Everything above is a claim we make about our own software, which is exactly the kind of claim you should not have to take on faith. The property is observable, so here is the procedure for observing it — on your host, with your tools, before the binary goes anywhere that matters.
# Run the agent in a network namespace that has no interfaces at all. # Nothing in here can reach anything, even if it wanted to. unshare -rn ./pqcrypta-discovery --offline-report ./assessment --targets /etc/ssl/certs # A complete bundle came out of a process with no network. That is the whole proof. ls assessment/ && (cd assessment && sha256sum -c SHA256SUMS)
verify-offline.sh
runs that and eleven other checks, and ships in the agent repository as
scripts/verify-offline.sh. It is POSIX sh with no
dependencies, because it has to run on whatever is already inside the
enclave.
$ sh verify-offline.sh ./pqcrypta-discovery
1. The binary
PASS Agent is executable: ./pqcrypta-discovery
2. No API endpoint is configured
PASS PQCRYPTA_API_URL is not set
PASS PQCRYPTA_API_KEY is not set
3. What this host can currently reach
Non-loopback interfaces: 2
Default route: default via 66.179.95.1 dev ens6
4. A scan with no network namespace at all
PASS Assessment completed inside a network namespace with no interfaces
5. Sockets it actually opened
connect/sendto/sendmsg calls (incl. local unix sockets): 97
PASS Zero AF_INET / AF_INET6 connections attempted
6. What came out
PASS report.html produced (84434 bytes)
PASS cbom.json produced (645395 bytes)
PASS inventory.json produced (1609441 bytes)
PASS report.html references nothing external — it opens with no network
PASS cbom.json declares CycloneDX 1.6
7. The bundle describes itself
PASS SHA256SUMS verifies against every file it lists
PASS SHA256SUMS covers manifest.json, so provenance is sealed too
PASS manifest.json records the run as having made no network access
Result
PASS — no check failed (0 skipped)
Section 7 above needs an agent that writes the evidence bundle
(SHA256SUMS, manifest.json). Against an earlier
release the script reports SKIP there with the reason, not
FAIL — an older bundle is not a bad one. Everything in
sections 1–6, including both checks that carry the weight, runs
against any version.
What each check is actually worth
| Check | What it proves |
|---|---|
| Namespace run | The strongest one. unshare -n gives the process a
network namespace containing nothing but a down loopback — no
addresses, no routes, no resolver. A full assessment coming out of
that did not use a network, because there was none to use. |
| Syscall trace | Direct observation rather than inference: every
connect, sendto and sendmsg
the process makes, with the count of those to an IP address
separated from local unix sockets. The number that must be zero is
the IP one. |
| External references | report.html is grepped for any src,
href, @import or url()
pointing at a URL. One would mean the report phones home the moment
somebody opens it, which is a different leak from the scan itself. |
| Bundle integrity | sha256sum -c against the bundle's own
SHA256SUMS, plus a check that the list covers
manifest.json — otherwise the provenance record
would be the one file nothing protects. |
Read the SKIPs. A host without unshare or
strace cannot run the two checks that carry most of the weight,
and the script says SKIP rather than quietly passing. A run that is
all PASS and SKIP has proven considerably less than one that is all PASS.
Run it somewhere with both if you can, then carry the binary you tested.
What it costs to run
Measured on a 4-core Linux host, agent v1.0.38, warm page cache. Your numbers will differ with disk speed and how much of the filesystem you point it at, but the shape holds: the scan is I/O-bound, and assessment and writing are a rounding error next to walking the tree.
| Scan | Assets | Wall time | Peak RSS | report.html | cbom.json | inventory.json |
|---|---|---|---|---|---|---|
| Targeted — five cert/key directories | 504 | 0.3 s | 31 MB | 100 KB | 804 KB | 2.1 MB |
Deep scan — /etc /opt /root /home /var/www,
source-code CBOM on, binaries read for embedded libraries |
8,762 | 263 s | 343 MB | 1.6 MB | 15 MB | 31 MB |
--deep-scan is worth reaching for deliberately rather
than by default. The time bar is marked approximate on purpose — the
targeted run finishes in a fraction of a second, so dividing by it magnifies
ordinary run-to-run variation, and re-measuring can move that multiple by a
factor of two in either direction. What holds is the shape: time outgrows assets
by roughly threefold, and assets outgrow memory. Absolute figures are in the
table above.
Two things worth planning around. Memory scales with asset count,
because the full inventory is held to assess and write it — budget roughly
40 KB per asset. And inventory.json is the large
artifact: if your transfer medium or review process has a size limit, the
CBOM is under half the size and the report is smaller still. A full-disk scan on a
large fileserver is the case to pilot before scheduling — start targeted, add
paths, and use --deep-scan deliberately rather than by default.
Common pitfalls
The ways an offline run most often comes back looking wrong, and what each one actually means.
- Far fewer assets than expected — check the privilege you ran
with. Unprivileged, the agent cannot read
/etc/ssl/private, other users'~/.ssh, or most keystores, and it reports what it could reach without complaining loudly. The report's Host Cryptographic Posture panel states the privilege the scan ran at, and several posture checks read Not assessed rather than a verdict when they were unreadable — that is the tell. - Keystores counted but empty. A JKS or PKCS#12 the agent could
not open is recorded as a container with an unknown verdict, not
silently skipped, and not scored as if it were clean. Supply passwords via
keystore_passwords,keystore_passwords_fileorkeystore_passwords_commandin the config and the entries inside it become assets in their own right. - Nothing found on a path you were sure about. Exclusions are
applied before the walk. Check
exclude_pathsin your preset — the OS catalogs ship with sensible exclusions that can still surprise you on a non-standard layout — and remember the scan follows the config, so--targetson the command line replaces the configured list rather than adding to it. - Depth limits on deep scans. Without
--deep-scanthe walk stops at a bounded depth. A certificate buried deep in an application tree will not appear until you enable it, and that is a deliberate default — deep scans on large trees cost real time. - A run that finds nothing at all on network targets. Offline mode still scans hosts on your own LAN if you configure them, but a target that speaks STARTTLS rather than immediate TLS needs the right port preset; a raw handshake against a plaintext greeting returns nothing. The mail presets handle ports 25, 587, 143 and 110 correctly.
- Expecting the exit code to mean “clean”. It does not. See exit codes — a successful run of a non-compliant estate still exits 0, because the scan worked.
Questions people ask
The questions that come back most often about running this on a host with no route out. If yours is not here, ask it at the bottom of the page — answers that turn out to be useful to more than one person end up in this list.
Do I have to be air-gapped to use offline mode?
No. A physical air gap is only the most obvious reason. Offline mode is written for any host that cannot reach a collector or is not permitted to send data to one, and the second case is far more common: federal systems where nothing may leave the authorization boundary and where routing inventory to an outside service would be a boundary change requiring re-accreditation; CUI or classified environments where hostnames, certificate subjects and internal topology are themselves controlled information; data-residency and sovereignty rules that forbid the data crossing a border; PCI cardholder data environments and other segmented zones; contracts that bar third-party SaaS from touching a customer estate; OT and ICS networks at Purdue levels 0 to 2; vendor security review or a firewall exception still in flight; and one-off work such as vendor-risk assessment, M&A due diligence, incident response or a red-team deliverable where you do not want a credential or a persistent service left on the host. The agent behaves identically in every one of those cases.
Does the agent contact anything at all in offline mode?
No. In offline mode the agent opens no outbound connection of any kind. It takes no API URL and no API key, performs no telemetry and no update check, and resolves no hostnames unless you explicitly configure a network TLS target for it to scan. Everything it needs to classify an asset — the compliance rules, the risk model, the remediation-ownership logic and the CycloneDX writer — is compiled into the binary.
What does offline mode actually produce?
Five files in the directory you name. Three are the assessment: report.html, a single self-contained assessment with all styling and data inlined, no external requests, filtering and pagination that work with no server; cbom.json, a CycloneDX 1.6 Cryptographic Bill of Materials that validates against the published bom-1.6.schema.json with zero errors, carrying an algorithm component and dependency edge for every asset plus nistQuantumSecurityLevel; and inventory.json, every discovered asset with all 55 fields and its local assessment — compliance status, risk level, recommendation and remediation owner. Two more make it evidence rather than merely a report: manifest.json, recording agent version, assessment id, host, platform and a SHA-256 digest of each file; and SHA256SUMS, which covers all four and so seals the provenance record along with the payload. Sign it with a key you hold and the bundle carries its own integrity out of the enclave.
Is the offline assessment the same as the online one?
Yes by construction. The scoring, risk grading, remediation-ownership classification and CBOM assembly all run on the agent. None of it is a second copy that has to be kept in step. The compliance and risk verdict — status, risk level and the remediation sentence you read — comes from a single scorer that the agent and the platform’s ingestion path both call, so there is no second implementation that could disagree; the remediation-ownership rules are generated from the same rule table the platform applies at ingestion and evaluated with the same matcher, so a rule change reaches both sides from one place. The same holds for the trust-anchor set behind the content-aware pass, which recognises a copy of a public CA certificate as the CA’s wherever it was found: one table on the platform, compiled into the agent from it. The last audit classified every asset in a live inventory through the agent’s own classifier and compared it with the platform’s: identical on all 10,224, and on every asset re-checked since. An air-gapped report reads identically to a connected one for the same asset.
Do I need root or Administrator to run it?
No, but privilege decides coverage. Elevation changes what the agent can read, never what it does: unprivileged it cannot open /etc/ssl/private, other users’ ~/.ssh or most keystores, and it records what it could reach rather than failing. The report states the privilege the scan ran at, and posture checks it could not evaluate read Not assessed instead of passing. If an offline run comes back far emptier than you expected, that is the first thing to check — see common pitfalls.
Will it change anything on the host?
No. The agent reads; it does not write anywhere except the output directory you name. There is no installer, no service, no registry key, no dropped configuration and nothing left running when it exits — it is one static executable that you can delete afterwards. That property is the reason it is accepted on hosts where installing an agent would be its own approval process.
Do I need a licence, an account or an API key?
Not for offline mode. The agent is free to download and --offline-report accepts neither an API URL nor an API key — there is nothing to activate and nothing to check in with. The files it writes are yours. A licence applies only to the self-hosted platform tier described at the end of this page.
Our filesystem layout is itself sensitive. Can I keep paths out of the CBOM?
Yes — run with --redact-paths. Locations are replaced with a stable salted digest, so an asset can still be tracked across scans and correlated between the CBOM and the inventory, but the directory structure does not leave the enclave. See redacting paths for exactly which fields change and what you lose.
How do I verify the binary before I carry it across the boundary?
Every release publishes a SHA256SUMS manifest signed twice — Ed25519 and post-quantum ML-DSA-65 (FIPS 204). Verify the signature over the manifest, then verify the binary against the manifest, both on the connected side before anything crosses. The public keys are also published from an independent origin, the append-only discovery-agent-keys repository, so you can compare against a copy we do not serve. Full procedure in get it in, verified.
What does a run cost in time and memory?
On a 4-core Linux host: a targeted scan of five certificate directories found 504 assets in 0.3 s at 31 MB peak RSS; a deep scan of /etc /opt /root /home /var/www with source-code CBOM enabled, reading every binary for the crypto library compiled into it, found 8,762 assets in 263 s at 343 MB. Budget roughly 40 KB of memory per asset, and note that inventory.json is by far the largest of the three assessment artifacts — the CBOM is a little under half its size. Full figures.
Does it need a configuration file?
No. With no --config the agent uses the built-in catalog for the operating system it is running on, and the native certificate stores — Windows CryptoAPI, the macOS Keychains — are always scanned with no configuration at all. Presets exist to narrow or extend a scan for a particular role, not to make it work. The published presets are all downloadable.
Can I get the results into the platform later?
The bundle stands on its own — the report is the deliverable and the CBOM is the machine-readable artifact for whatever consumes it next. If a network becomes available later, the same binary run without --offline-report submits to an API instead, and if the enclave is large enough to have its own network you can run the whole platform inside it (self-hosted). Which route you take is a boundary decision, not a licensing one.
How do I report a bug from a host that has no network?
You write it down on the host and carry it out, the same way you carried the binary in. There is a plain-text field report template to take in with you; fill it in with whatever editor exists in the enclave, bring it back, and the form below will parse it straight back into its fields. Nothing in that file is transmitted by itself, and anything you paste is scrubbed in the browser — with a preview of exactly what will be sent — before it goes anywhere.
Can I run the full platform inside my own network instead of using a vendor SaaS?
Yes. The self-hosted deployment is a Docker Compose stack of three containers — PostgreSQL 16 with pgvector (internal only), the Rust API on port 3003, and the Compliance Dashboard on port 8080 — so discovery agents submit to your API and no data leaves your network. It also ships as a transferable air-gap bundle: images are built on a connected host, carried in on removable media and loaded with no registry pull. The licence is signed with ML-DSA (Dilithium3) and verified offline at startup against a key compiled into the binary, so there is no callback, activation server or heartbeat.
Why generate a CBOM now if the federal minimum elements are not defined yet?
Because the hard part is the inventory, not the file format. Producing a CBOM means having discovered, parsed and classified the cryptographic assets across every surface you can reach — that work is format-independent and takes the longest. Re-issuing an existing inventory in a new schema is a re-export; building the inventory for the first time is a project. CycloneDX 1.6 is a published, machine-validatable standard available today, and PQCrypta will emit the federal format as well once its minimum elements are defined.
“But the CBOM format isn't settled yet”
True — and not a reason to wait. Executive Order 14412 (June 2026), “Securing the Nation Against Advanced Cryptographic Attacks”, directs CISA and NIST to define the minimum elements of a Cryptographic Bill of Materials: the fields a CBOM must carry to enable automated assessment of cryptographic assets. Those minimum elements have not been published yet. The migration deadlines they support have: key establishment by December 31, 2030, digital signatures by December 31, 2031.
Why the format is the easy part
A CBOM is a serialisation of an inventory. Producing one means you have already discovered the cryptographic assets across every surface you can reach, parsed each one deeply enough to know its algorithm and parameter set, and classified it. That work is format-independent, and it is the part that takes quarters rather than an afternoon.
Once you hold it, re-issuing in a different schema is a re-export — a mechanical transformation of data you already have. Building the inventory for the first time is a project. An organisation that waits for the format spends the intervening years without an inventory and then still has to build one, on a shorter clock, against the same deadlines.
What we commit to
- Today the agent emits CycloneDX 1.6, validated against the
official schema, with the spec-standard fields —
primitive,parameterSetIdentifier,nistQuantumSecurityLevel, adependenciesgraph — that make automated assessment possible rather than merely promised. - When CISA and NIST publish the minimum elements, PQCrypta will emit that format too, from the same inventory, as an additional export — not a replacement. CycloneDX will keep working for everyone whose tooling already speaks it.
- Because the inventory is the durable asset and the export is derived, that transition is a re-export of scans you already ran. Nothing you inventory now is wasted by a format that lands later.
The clock is on the migration, not the paperwork. Every quarter spent waiting for a schema is a quarter not spent finding the 4,096-bit RSA key on a host nobody remembered, or the SHA-1 signature in a chain that still verifies. Run the scan. Get the CBOM. Reformat it later — that part is a script.
Ready to start? Grab a verified binary, take a config preset, and produce your first assessment in about the time it takes to read this page. The full guide covers connected operation, every scan type and the complete configuration reference.
When you do have a network
Offline mode is complete for one host at one moment. What it cannot do is
accumulate. Point the same binary at an API with
--api-url and --api-key instead of
--offline-report, and the findings stop being a file and start
being an estate.
| Offline | Connected | |
|---|---|---|
| Discovery & parsing | Identical | Identical |
| Scoring & ownership | On the host | At ingestion — same rules, pinned by parity tests |
| Scope | One host, one run | Every host, every run, in one inventory |
| Change over time | Compare files yourself | Assets that disappear are retired; staleness tracked per device |
| CBOM | Per host, on disk | Per host or estate-wide, filtered, on demand |
What connected mode adds
- The Compliance Dashboard — total assets and devices, the compliance split, quantum readiness with a qualitative stage label rather than a bare percentage, organization-controlled versus vendor-controlled progress, and a Discovery Coverage checklist. Most cards jump straight into a pre-filtered inventory.
- Estate-wide CBOM export — one CycloneDX document across every host, filtered by any combination of source, device, algorithm, certificate role and compliance status. CSV and JSON report exports share the identical filter set, so a selection always describes the same rows.
- A policy engine — compliance rules evaluated against every stored asset at ingestion and on demand, so “no RSA under 3072 in production” becomes a check rather than a convention.
- Threat-intelligence correlation — CISA KEV and NVD data matched against the OpenSSL versions your scans actually observed, so a vulnerable library is a current finding rather than a mailing-list post.
- SIEM delivery — real webhook POST or RFC 3164 syslog to a registered destination, so findings land where your analysts already look.
- Lifecycle — assets a scan no longer observes are retired automatically, per device, with a guard that refuses to write off most of a device at once because that is the signature of a narrowed scan, not a decommissioning. Per device rather than per scan, so a certificate deleted from an appliance is retired on the evidence of the scan that reached it, without that same scan being read as evidence about the host it ran from.
Everything above is in the full guide; the overview covers all nine discovery sources.
Portable: run the whole platform yourself
There is a third option between “one file on one host” and “send it to a vendor”: run the entire platform inside your own network. The self-hosted deployment is a Docker Compose stack of three containers — database, API and dashboard — and no data ever leaves your network. It is the tier for data-sovereignty requirements, ATO scope limits, and enclaves that can have an internal network but no route to a vendor.
| Component | Image | Exposed |
|---|---|---|
| PostgreSQL 16 + pgvector | pgvector/pgvector:pg16 |
Internal only — never published to the host |
| API (Rust) | pqcrypta/api |
Port 3003 |
| Compliance Dashboard (Apache + PHP) | pqcrypta/dashboard |
Port 8080 |
cd deploy/onprem cp .env.example .env # set the secrets: openssl rand -hex 32 # drop your licence at license/license.json docker compose up -d --build # Dashboard http://<host>:8080/dashboard/ # API health http://<host>:3003/health
The schema and policy seed apply automatically on first start. Agents anywhere on your network then submit to your API rather than ours:
./pqcrypta-discovery --config agent.toml \
--api-url http://<host>:3003 --api-key <key>
It installs air-gapped too
The same stack ships as a transferable bundle, which is the case this page cares about most: an enclave large enough to have its own network can run the connected product entirely inside the boundary — dashboard, history, estate-wide CBOM and all — with nothing crossing it.
# on a connected build host deploy/onprem/scripts/build-airgap-bundle.sh # → pqcrypta-onprem-airgap-<date>.tar.gz (+ .sha256) # carry it in, verify the checksum, extract, then on the disconnected host scripts/install-airgap.sh # loads images — no registry pull, ever
Every image and the signed agent artifacts are inside the tarball; nothing is fetched from the internet at install time or afterwards.
The licence does not phone home either
A self-hosted licence would be worthless if validating it required the network it exists to avoid. The licence is a JSON envelope signed with ML-DSA (Dilithium3) and verified offline at API startup against a public key compiled into the binary — no callback, no activation server, no heartbeat. A missing or expired licence degrades to a grace mode rather than stopping work; only a tampered one is refused outright.
Requirements are modest: a Linux host with Docker Engine and the Compose plugin, roughly 2 vCPU and 4 GB RAM to start, scaling with inventory size.
Self-hosting is a licensed tier rather than a public download — talk to us and we will issue a licence and the bundle. If you only need the assessment and not the platform, everything above this section still applies: the agent alone is free to download, and everything it writes is yours.
Report a bug, or tell us what it should do
Offline mode has an awkward property: the person best placed to tell us something is broken is standing in front of a machine that cannot tell us anything. There is no crash telemetry to fall back on here — by design, the agent sends nothing, ever — so a fault inside an enclave only reaches us if somebody carries it out. This is the route for that, and it is built to work the same way the binary does: something to take in, something to bring back, and nothing leaving a machine that you have not read first.
Take a field report in
A plain text file with the questions already on it. Fill it in on the affected host with whatever editor exists there — no tooling, no network, nothing to install.
Download the template ↓Bring it back, don’t retype it
Paste the filled-in file into the form below, or load it from your transfer media, and it parses straight back into the fields. The file is inert on its own — it transmits nothing by itself.
Jump to the import ↓Nothing goes out unread
Anything you paste is scrubbed in this browser — addresses, hostnames, usernames, e-mail addresses, key material — and the exact text that will be sent is shown to you first. The server strips credential-shaped content again regardless of what the browser did.
A security flaw is not a bug report. If you have found something exploitable in the agent, the signing chain or the platform, please do not put it in this form — it lands in an ordinary queue. Mail contact@pqcrypta.com directly and we will take it from there.
Received
Your reference — keep it if you want to follow up: