Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

docs/v2/10-security.md

10. Security

Post-quantum status

Safe defaults

Zero-egress guards

What may dial out is decided by the build and the RON, and refused by name otherwise:

code what is refused
holger-offline-1 any outbound dial of a Holger with network: Offline, before a socket opens (pull-through, mirror, twin follower, estate link, WireGuard tunnel); each refusal is audited (server/lib/src/egress.rs, check)
holger-offline-2 a RON that says network: Offline but needs the network: a *-remote repository, a mirror:, a twin role, recharge_from, online intel, a pull-through source with a url, a sync upstream with a url, an estate build (server/lib/src/lib.rs, validate_network). In a downstream build, an offline downstream that names its upstream is refused too, and the process then makes no network call at all
holger-offline-3 a remote repository that names no upstream address
holger-downstream-1 a downstream config that asks for more than its one upstream Holger
holger-downstream-2 a downstream's dial to anything but its upstream
holger-boundary-1 a boundary: OneWay Holger that names a way out (a mirror, an export door, a sync receipt, an outbound target)
holger-boundary-2 boundary: Fetch in a build without the fetch feature

The downstream build (holger-downstream) carries the on-prem gateway's rules (server-lib feature gateway): one upstream at most, no upload, and an omitted boundary: is OneWay, always checked. The proofs:

cargo xtask ci runs the server library with the gateway set (sync, fetch, downstream-wg, supply-chain, gateway) as its own step, gateway-set (xtask/src/ci.rs).

Signed apt and dnf metadata

The deb and rpm chain views, and archive-backed deb and rpm repositories (ron_archive_path), sign their metadata with the box's repository key, so apt and dnf check it in their normal configuration (chapter 3):

Each signature is over the exact index the repository serves, and is made again when that index changes (server/lib/src/signed_index.rs). Holger signs the metadata, not the packages; an rpm's own signature is its vendor's. The key signs only:

Make the key once per box, then restart:

holger repo-key create --config holger.ron    # prints the public key
holger repo-key show --config holger.ron

Tests (server/lib/tests/the_chain_serves_apt_dnf_helm_maven_go_views.rs): a_box_with_a_repository_key_signs_its_deb_and_rpm_metadata, a_box_without_a_repository_key_serves_no_signature_and_says_how_to_make_one, and real_apt_and_dnf_verify_the_signed_metadata_and_refuse_it_tampered (real apt on Debian trixie and microdnf on Fedora 42; one changed byte or stripped signatures are refused), an_archive_backed_deb_and_rpm_repository_signs_its_metadata_too, real_apt_and_dnf_verify_an_archive_repositorys_signed_metadata, a_metadata_file_through_the_chain_never_stands_in_for_the_signed_index and a_downstream_signs_its_views_with_its_own_key_and_its_console_names_it. The signer's security review is in the lane handback hol-sign-review.md (HOL-SIGN-2).

Dependency advisories

cargo deny --locked check (advisories, bans, licences, sources; policy in deny.toml) runs in the security workflow (.forgejo/workflows/security.yml) and as the deny step of cargo xtask ci. .cargo/audit.toml holds the same ignore list as deny.toml; the test xtask/tests/audit_follows_deny.rs keeps them equal.

The admin listener

An endpoint can serve the admin plane on its own listener: ron_admin_url (gRPC) and ron_admin_http_url (HTTP).

Revoked client certificates

ron_tls.crl names a CRL file (PEM or DER) or a directory of them. The gRPC and HTTP doors that verify clients against client_ca check it:

Key files and hardware signers

Coming: the same key-file check for the operator tools' keys (holger-cli bundle approve|finalize --key, the intel signer files).

A crafted bundle cannot crash the native verify

A bundle's znippy index is Arrow IPC. A hostile one (an attacker-chosen 1 TiB body length, buffer overruns, schema or boolean-buffer panics) used to abort or panic verify_bundle_file before any signature was checked.

OpenZL allocation cap

OpenZL took its internal buffer sizes from the frame: a crafted 36-byte frame made Holger allocate and fill 1 GiB.

The time floor

An airgap box judges a bundle's expiry, its maximum age and its certificates against its clock, and a clock can be rolled back from the console or the BIOS. So Holger judges every import at the later of its clock and its time floor, <data_root>/holger-time/floor: the highest trusted time it has seen (server/lib/src/time_floor.rs).

The operator's steps:

  1. On the online side, make a token and the policy that trusts its signer (minted under --signer-dir on first use, keys 0600): holger time-token make --out time.holger.znippy --signer-dir ./time-signer --trust-policy-out time-trust.ron (--at <RFC 3339> for another time, --valid-days N, default 7).
  2. Carry time-trust.ron to the airgap side once; carry each token like any bundle: holger bundle import --config holger.ron --bundle time.holger.znippy --policy time-trust.ron, or drop it into the bundle inbox when the box's promotion.intake.trust_policy trusts the token's signer for holger.time-token/v1. The import says TIME FLOOR raised to ....
  3. holger time-token status --config holger.ron says the floor and what set it.

Coming: the time floor for the provenance trust-root bundles and for holger bundle scrub, which still judge by the bare clock.

The audit log

The audit log is audit.jsonl in the audit directory: append-only and hash-chained (seq, prev, hash on every line), with a checkpoint signed by the attestation signer every audit.checkpoint_every records. The chain is the source of truth (server/lib/src/audit/).

Threat model

The system threat model: the assets, the trust boundaries (the online side, the diode or inbox, the airgap side, the downstream, the console, the estate front, the scan sandbox, Heimdall later), the adversaries, every NCSC gap with the code and the test that close it, and the residual risks.

Reporting a vulnerability

SECURITY.md: write to holger@holger.rs, with a subject starting with SECURITY:.

Still in v1