The heimdall command line
Heimdall is a defensive sandbox. Milestone 1 runs wasm jobs only.
heimdall run --job wasm --module <guest.wasm> --trust-root <root.der>... --in <dir> --out <dir>
[--policy <file>] [--pin-out <file>] [--pin-blake3 <hex>]
[--decoder <decoder.wasm> [--decoder-pin-blake3 <hex>]]
[--job-id <32 hex>] [--nonce <64 hex>] [--param key=value]...
[--seccomp generated|none] [--seccomp-sha256 sha256:<hex>]
heimdall verify-result <out>/result.signed --key <key file | key record>
[--files <dir>] [--job-id <32 hex> --nonce <64 hex>]
heimdall profile probe|gen|check <workload> [--profiles <dir>] [--libc glibc|musl]
[--binary <file>] [--only <run>]...
--job nativeprintsnot in milestone 1and exits 2.--modulenames the guest; its signature<guest>.wasm.p7sand hash pin<guest>.wasm.blake3sit beside it and are verified against the--trust-rootcertificates before load. There is no unsigned mode: without a trust root the job does not start.--decodermakes the job a split guest (GATLING-WASM.md):--moduleis then the verifier and--decoderits decoder module, signed and pinned exactly like the guest (<decoder>.wasm.p7s,.wasm.blake3, and--decoder-pin-blake3as the caller pin). The decoder is loaded first; any refusal of it saysdecoder refused: ...and exits 3 before anything runs. The policy keydecoderssets how many decoder instances run at once. Verdicts, outputs andverify-resultare the same as for the single-instance guest.--in,--outand--policyfollow RUN.md and POLICY.md.--job-idand--noncelet the caller bind the result to its own request; both are random when absent and printed either way.--seccomp generated(the default of a release build) runs the guest in a worker process under the checked-in, generatedheimdall-wasm-hostseccomp profile for this build's arch and libc (unknown call = kill); the parent holds the key, re-checks the worker's outputs and signs. The result saysGenerated { profile_digest }(sha256:of the profile's.oci.json). A worker killed by the filter gives a signed Refusedheimdall-seccomp-1, no outputs, exit 4.--seccomp none(the default of a debug build: the profiles are generated from the release build) runs it in-process with no filter (Protection::None).HEIMDALL_SECCOMP=generated|nonesets the default; the flag wins.--seccomp-sha256demands that digest: another one exits 3 before anything runs. No profile for this arch/libc withgenerated: exit 3. The worker gets onlyPATH=/usr/bin:/binandRUST_BACKTRACE=0as its environment (the probe's), none of the caller's.runprintspinned-key:,seccomp:,job-id:,nonce:,result:andverdict:lines on stdout.--pin-outalso writes the pinned key record to a new file.verify-resultchecks the format, the pinned key id and boot id, the Ed25519 signature and the result's well-formedness; with--job-idand--nonce, that the result is for that job; and that the files in--files(default:files/beside the result file, when it exists) are exactly the listed outputs with matching sha256 and length.
heimdall profile (spec §3)
The syscall profile of a workload is probed, generated and checked from profiles/<workload>/workload.toml (the binary per libc and the probe corpus; format in heimdall_probe::workload). See SECCOMP.md.
proberuns every corpus run in learning mode (seccomp USER_NOTIF, log and allow) and writes<arch>-<libc>.probe.json: every distinct call with the tests that made it.genreads the probe file and writes<arch>-<libc>.rs(aconsttable),.json(the same profile plus the review: near-miss calls, W|X mappings left out, bare allows) and.oci.json. A person reviews the diff before it is committed.checkprobes again and exits 1 when a call is outside the checked-in.json, printingnew call <call> (kill_process), made by test <run>, or when.rs/.oci.jsondo not match.json.
Exit codes: 0 done / check passed, 1 check failed, 2 usage error, 3 the workload, binary or a probe run failed (a run that times out or ends with another exit code than its expect_exit fails the probe, naming the run).
heimdall trace (dynamic-trace jobs)
heimdall trace run --layout <dir> --image <sha256:..> [--tracer <heimdall-trace>] --in <dir> --out <dir>
--result <file> [--work <dir>] [--pin-out <file>] [--wall-secs 120] [--memory-mib 1024]
[--pids 256] [--max-bytes 4194304] [--env K=V]... [--writable <dir>]... -- <command>...
heimdall trace verify <result> --key <pinned key> --files <out dir> [--max-bytes <n>]
heimdall trace diff <baseline.json> <trace.json> [--max-bytes <n>]
run traces the command in a HeimdallRunner job with no network (heimdall-container, its shim found as for every HeimdallRunner) and prints the outcome as one JSON line ({"outcome":"traced",...} or {"outcome":"unknown","reason":...}); --tracer first adds the static tracer to the image as one more layer. verify checks a trace job's signed result and trace.json. diff prints what the trace does beyond the baseline (JSON). Exit codes: 0 traced / clean diff, 1 Unknown / new behaviour or a trace not complete, 2 usage, 3 a file, the key or the runner could not be read or started. See API.md.
Exit codes
| Code | run |
verify-result |
| 0 | the job ran and was Accepted | the result verified and says Accepted |
| 1 | — | the result did not verify (signature, key, boot, ticket, outputs) |
| 2 | usage error, or not in milestone 1 | usage error |
| 3 | the job could not start (policy, trust roots, guest load, input, out dir, --pin-out) |
the result or key file could not be read, or the key record is malformed |
| 4 | the job ran and was Refused (the signed result was written) | the result verified and says Refused |