PQ CRYPTA PLATFORM

🏠 Main

🧪 Interactive Apps

📰 News

🛡️ PQ Crypta Proxy

👤 Account

⟨ QUANTUM ERROR PORTAL ⟩

Navigate the Error Dimensions

Agent v1.0.45
PQ Crypta logo

PQ Crypta Discovery Agent — Offline Mode

Air-Gapped Cryptographic Assessment, CycloneDX CBOM & Post-Quantum Readiness, Produced On-Host

Air-Gap / Offline Mode

A complete cryptographic assessment, produced with no route out

The systems with the strictest crypto requirements are usually the hardest ones to inventory — either because nothing can reach them, or because nothing is permitted to leave them. Offline mode exists for both: one flag, zero network access, and a finished assessment written to disk beside the binary. No agent to install, no service to run, no callback, no licence check — a single static executable that reads, classifies and writes, then exits.

The output is not a raw dump for someone else to interpret. Everything that makes a finding useful — post-quantum verdict, risk grade, who owns fixing it, and a CycloneDX 1.6 CBOM — is computed on the host, by the same rules the connected product applies.

0 Outbound connections
1 Flag to enable
5 Files produced
55 Fields per asset
35 Host posture fields

Quick start

The whole procedure, for someone who already knows they want this. Every step is expanded further down — verification, presets, what comes out.

bash
# 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.

The same discovery, with one step replaced Both modes begin with the identical discovery stage on your host. In connected mode the agent submits its findings, and the arrow leaves the host to a platform that scores them and shows them on a dashboard. In offline mode the arrow never leaves: the agent scores the findings itself using the same rules compiled into the binary, and writes report.html, cbom.json and inventory.json to disk on the host. Your host Elsewhere Connected Offline Discovery identical in both Submit findings --api-url + key Platform scores rules live there Dashboard estate-wide Scores itself same rules, compiled in report.html cbom.json inventory.json
Everything to the left of the wall is your machine. Connected, the findings cross it and are scored somewhere else. Offline, the same scan is scored by the same rules without anything crossing at all — the difference is one step, not a reduced product.

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:

No route exists Physically isolated
  • 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
Egress forbidden Policy or law says no
  • 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
Not yet approved Waiting on process
  • 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
By choice You'd rather not open one
  • 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.

Oct 22, 2026 Agency PQC Migration Plans due to OMB — phase one is inventory.
~Mar 2027 CISA/NIST guidance (270 days) defining CBOM minimum elements.
Dec 31, 2030 Key establishment must be post-quantum.
Dec 31, 2031 Digital signatures must be post-quantum.

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.

SourceNeedsWhat 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
(passwords for locked ones)
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.

On the connected side
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.

What has to hold before anything crosses the boundary Four checks, all on the connected side. The signing key is compared against the copy published in the independent discovery-agent-keys repository; the SHA256SUMS manifest is verified under Ed25519 and again under post-quantum ML-DSA-65; and the binary's own SHA-256 is matched against that manifest. Only then does the verified binary cross into the enclave, carried on media you control. Connected side — every check happens here Key, two ways served + GitHub Ed25519 over SHA256SUMS ML-DSA-65 FIPS 204, same file SHA-256 binary vs manifest Enclave carried in A hash proves the file matches a list. The two signatures prove the list is ours — one against today's adversary, one against the one that keeps the recording.
Every check is on the connected side, before anything is carried anywhere. The manifest is signed twice because the two signatures answer different questions: Ed25519 for an adversary who has to break it now, ML-DSA-65 for one who records the artifact today and factors at leisure. A tool that tells you your signatures will not survive a quantum adversary ought to be signed with one that will.

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-scan to 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

bash
chmod +x ./pqcrypta-discovery
./pqcrypta-discovery --config linux-web-server.toml \
                     --offline-report ./assessment --deep-scan

Windows

PowerShell
.\pqcrypta-discovery.exe --config windows-server.toml `
                        --offline-report .\assessment --deep-scan

Flags that change what an offline run covers:

FlagEffect
--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).

FileForWhat 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.
report.html the readable assessment

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-GAP badge 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_vendor and unknown. 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.json under coverage for 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.

cbom.json the machine-readable bill of materials

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.

cbom.json — document header
{
  "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.

cbom.json — a certificate
{
  "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”.

cbom.json — algorithm component + dependency edge
{
  "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.

inventory.json the complete record

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 & locationlocation, location_type, asset_type, sha256_fingerprint, hostname, port, owner, application, environment, file size, permissions and modification time.
  • Cryptographyalgorithm, 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 assessmentcompliance_status, risk_level, recommendation, remediation_owner, is_actionable and remediation_note.
inventory.json — one asset (abridged)
{
  "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.

inventory.json — one asset, complete
{
  "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.

FileWhat 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.

Sign an assessment
# 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:

StatusMeaning
compliantStandardised post-quantum cryptography — ML-KEM, ML-DSA, SLH-DSA, HQC and friends.
legacyClassical and quantum-vulnerable: RSA, ECC, finite-field DH, EdDSA. Works today, must migrate.
deprecatedQuantum-resistant but not the standard — pre-FIPS round-3 parameter sets, for example.
forbiddenBroken independent of quantum: MD5, SHA-1 in a verified signature, DES, RC4, ECB, undersized RSA.
unknownHonestly 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.

Monthly, keeping every run — cron
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
Windows Task Scheduler
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

CodeMeaning
0The bundle was written. Including when individual paths were unreadable — those are reported in the log and are a coverage note, not a failed run.
1The 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.html on the host, then move cbom.json and inventory.json out 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.
How an offline assessment reaches an estate-wide view Three zones. On the left, a connected staging machine downloads the signed binary and verifies it. In the middle, inside the isolation boundary, the agent runs and writes report.html, cbom.json and inventory.json, with no connection crossing the boundary. On the right, after your own review, the CBOM and inventory are carried out on removable media and ingested into the compliance dashboard, where the enclave appears alongside connected hosts. Connected staging Isolated — nothing crosses outward Outside, after your review Download binary + SHA256SUMS + 2 sigs Verify signatures Ed25519 + ML-DSA-65 Agent runs --offline-report reads only scores locally report.html cbom.json inventory.json Removable media CBOM + inventory Compliance dashboard same estate-wide numbers carried in carried by hand
The only step that crosses the boundary is one you perform deliberately, with media you control, after reading the report on the host. The agent never has a channel of its own in either direction.

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:

bash
./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:

ConcernWhat holdsHow 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.

The short version
# 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.

Real output, this host, agent 1.0.45
$ 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

CheckWhat 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.

ScanAssetsWall timePeak RSS report.htmlcbom.jsoninventory.json
Targeted — five cert/key directories 5040.3 s31 MB 100 KB804 KB2.1 MB
Deep scan — /etc /opt /root /home /var/www, source-code CBOM on, binaries read for embedded libraries 8,762263 s343 MB 1.6 MB15 MB31 MB
Time does not grow with assets — it grows with tree walked Growth from the targeted scan to the deep scan, as a multiple of the targeted run, on a logarithmic scale. Assets found grow 17 times and peak memory 11 times, but wall time grows roughly 900 times. Assets found Wall time Peak memory 17× ~900× 11× Log scale · 504 → 8,762 assets · 0.3 → 263 s · 31 → 343 MB
Growth from the targeted run to the deep run, as a multiple of the targeted one. Memory tracks the inventory it holds, so it grows roughly with the assets found. Wall time does not: it grows with how much filesystem was walked to find them, which is why --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.
The three artifacts keep the same proportions at any scan size In the targeted scan report.html is 100 kilobytes, cbom.json 804 kilobytes and inventory.json 2.1 megabytes. In the deep scan they are 1.6, 15 and 31 megabytes. Each panel is scaled to its own largest artifact, and the resulting shapes are almost identical: the inventory is roughly twenty times the report, and the CBOM a little under half the inventory, whatever the scan size. Targeted scan — 504 assets Deep scan — 8,762 assets report.html cbom.json inventory.json report.html cbom.json inventory.json 100 KB 804 KB 2.1 MB 1.6 MB 15 MB 31 MB
Each panel is scaled to its own largest artifact, so the shapes are the comparison — and they barely move. Whatever the scan size, the inventory is about twenty times the report and the CBOM a little under half the inventory. If your transfer medium or review process has a size limit, that ratio is the thing to plan against, not the absolute numbers.

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_file or keystore_passwords_command in 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_paths in 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 --targets on the command line replaces the configured list rather than adding to it.
  • Depth limits on deep scans. Without --deep-scan the 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.

Today CycloneDX 1.6 is published, stable, and machine-validatable. We emit it, schema-clean.
~Mar 2027 Federal CBOM minimum elements due — 270 days from the order. A directed deadline, not a shipped spec.
Dec 31, 2030 Key establishment migrated to post-quantum cryptography.
Dec 31, 2031 Digital signatures migrated.

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, a dependencies graph — 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.

 OfflineConnected
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.

ComponentImageExposed
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
Connected install
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:

Point an agent at your own stack
./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.

Build outside, install inside
# 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:

What are you sending?

                    
I filled in the field report on the isolated host

Paste it below, or load the file straight off your transfer media. It is parsed in this browser — the file is never uploaded, only the fields it fills in, and only when you submit.

Stored: what you typed, the time, and the address you sent it from. No account, no cookie, no tracking pixel — leave the email blank and the report is anonymous. Nothing you submit is published as written; if a report turns into a line on this page, we write that line.

The submission won’t go through

Plenty of the networks this page is written for filter outbound POSTs by policy. If that is happening, open the same report as an email instead — it carries the fields you have filled in, scrubbed the same way, and you can read it before you send it.