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

nordisk/heimdall

Heimdall

OpenSSF Best Practices

In Norse myth, Heimdall guards the Bifröst bridge and lets nothing into Asgard unchecked, especially not data.

What it is

Heimdall is a defensive sandbox for inspecting content. All content is untrusted until it is verified. That includes signed content: a digital signature is itself untrusted input until it has been checked, and the check must not be able to attack the checker. So Heimdall parses, verifies and scans in an isolated job:

Pure Rust, static binaries.

Users

Layers

Each job runs inside every layer below; an attacker must break all of them.

Layer What it does
Kernel The node runs a kernel trimmed to what its rows declare (tinyconfig plus the declared needs), so less kernel code can be attacked; a measured trim (to what its programs actually use) is planned.
Container (youki) Namespaces, read-only root, no network unless allowed, all capabilities dropped, no-new-privs, cgroup v2 limits, image checked at every start.
Seccomp allowlist Only the system calls the job really uses, with fixed arguments where possible. Anything else kills the job.
Landlock File access only to the job's input (read) and output (write).
Syscall broker The few risky calls (open, socket, connect) are paused and checked by Heimdall, which opens the file or socket itself, so the job cannot race the check.
Wasm host (optional) The parser runs as signed WebAssembly with no file or network access, under fuel, time, memory and stack limits.
Signed result The verdict and the output hashes are signed with a per-boot Ed25519 key.

Tuning: how the allowlists and the kernel are fitted

Nothing is guessed. Heimdall measures what a program does and allows only that.

  1. Build the program and the node kernel (Build Thing).
  2. Probe: run the program's full test suite and fuzz corpus on a probe kernel, in learning mode. Heimdall records every system call with its important arguments; the probe kernel records which kernel code ran.
  3. Generate: Heimdall merges the runs into the smallest seccomp profile per program, architecture and libc (a Rust table plus reviewable JSON). The kernel side lists kernel options no program used, explained through the Kconfig dependency graph.
  4. Review: a person checks the diff and commits it. Nothing is merged automatically.
  5. Enforce: production runs the checked-in profile; any call outside it kills the job. Kernel options are removed one at a time, each with a full test rerun.
  6. Drift check: every CI run probes again and fails if a program makes a call its profile does not have, naming the call and the test that made it.

Paths a test never reached are missing from the profile, so they fail closed. The probe therefore runs error paths, failover and fuzzing, not only normal use.

Details: docs/API.md (the job API: the JobRunner trait, v0.1.0, and the interim heimdall-plain runner, an ordinary youki container stated as such), docs/SPEC.md (the specification) and docs/BUILD-THING.md (how Build Thing drives the tuning).

Usage

heimdall run --job wasm --module <guest.wasm> --trust-root <key> --in <dir> --out <dir> [--policy <file>]
heimdall verify-result <out>/result.signed --key <pinned key> --files

See docs/CLI.md, docs/POLICY.md and docs/RUN.md.

Standards and specifications

Standard Used for Status
RFC 8032 (Ed25519) Signed results, strict verification Implemented
FIPS 180-4 (SHA-256) Output hashes, key ids Implemented
BLAKE3 Guest module pins Implemented
WebAssembly Component Model, WIT The guest interface; WASI is not linked Implemented
DSSE (in-toto envelope) Bundle verification before payload parsing In progress
RFC 5280 (X.509) Bundle signatures In progress
OCI Runtime Specification The youki container (heimdall-plain) Implemented
Linux seccomp-bpf A fixed default profile, plus a GENERATED per-program allowlist (measured with heimdall-probe, enforced at runtime) for Holger's three job kinds: the verify-bundle job's wasm worker (heimdall-wasm-host, checked in here), Holger's scan worker and clamd (checked in at Holger); see docs/SECCOMP.md Implemented
seccomp USER_NOTIF + SECCOMP_IOCTL_NOTIF_ADDFD The syscall broker (heimdall-broker): open, socket, connect, bind and exec of every heimdall-container job checked, files opened by Heimdall and injected; check-only and deny-only fallbacks Implemented
Landlock LSM (ABI 1–6) heimdall-container: /in read-only, /out write-only, /tmp no exec, no TCP bind/connect (ABI 4), signal and abstract-socket scopes (ABI 6) Implemented
Linux cgroup v2, user namespaces, no_new_privs Resource limits and privilege drop Implemented
NIST SP 800-190 (Application Container Security Guide) Container hardening guidance Followed in design
SLSA v1.0 Build Level 3 Release provenance through the same pipeline as Holger and Gunnar (dwarves release-build, hardware-key signed); see attestations/ (0.1.0, 0.2.0) Implemented

"Implemented" means it is on main with tests; nothing in this table is a certification.

Crates

On crates.io the packages are named nordisk-heimdall-* (the name heimdall-core there belongs to an unrelated project). The library names are unchanged, so code keeps writing use heimdall_core::…:

heimdall-core = { package = "nordisk-heimdall-core", version = "0.1.0" }
Package Library What it is
nordisk-heimdall-core heimdall_core Job, policy and signed-result types, per-boot Ed25519 key, canonical encoding, verification
nordisk-heimdall-wasm heimdall_wasm The wasm host: wasmtime components, no WASI, limits, signed guests
nordisk-heimdall-plain heimdall_plain The interim youki runner (an ordinary container, no Heimdall protections)
nordisk-heimdall-run heimdall_run One wasm job end to end: input dir, guest, output dir, signed result
nordisk-heimdall-cli binary heimdall heimdall run, heimdall verify-result

Get the code

git clone ssh://anonymous@git.gunnar.rs:2222/nordisk/heimdall.git
cd heimdall
cargo build --release

Contributing, security and changes

Licence

AGPL-3.0-only, or a commercial licence from the joint owners Ignalina ApS and Vetra Automation AB (see COMMERCIAL-LICENSING.md). Contributions are accepted under the CLA.

Fuzzing results

Measured 2026-10-30 on main after v0.1.0: every cargo-fuzz target ran 30 minutes (the first six twice), about 1.9 billion inputs in total, with no crash. Run it yourself with scripts/fuzz.sh.

Target What it feeds Inputs tried Crashes
output_name Output file names 466 million 0
pinned_key_text The pinned key line 287 million 0
result_encoding The canonical result encoding 209 million 0
policy_file The TOML policy file 14.4 million 0
verify_result_file A result file given to verify-result 10.7 million 0
signed_result_wire The signed-result wire format 10.6 million 0
wasm_emit_file Guest file names, sizes, traps partway through 4.6 million 0
wasm_guest_load Random bytes through pin, signature and compile 186,000 0

wasm_guest_load is slow by design: it signs and compiles every input.

Files

Newest commits