NaZelo Trust — the integration contract¶
Version 0.1.0, like every engine it composes. What follows is what the three of them guarantee together, what they need, and what they do not do.
What NaZelo Trust is (and is not)¶
NaZelo Trust is the integrated trust offering that composes three independent products so a run is decided, enforced, and proven under one correlation id:
- PimaTika decides — from a signed BIM — what egress is allowed.
- NaZelo isolates the run, enforces the decision, and observes its own drops.
- Litatoli seals every step into a signed, chain-linked evidence log and verifies it.
NaZelo Trust is not a rename of NaZelo, which remains the critical-runtime
product in its own right (nazelo.dev). NaZelo, PimaTika and Litatoli each remain
usable on their own. NaZelo Trust is the value of the three fitting together.
How this is validated¶
Continuously, by .github/workflows/composition.yml, on every push.
The job clones the three engines, builds the two Rust ones, generates the key
sealing needs, re-signs the example manifests with it, and then demands the
whole stack be present: NAZELO_TRUST_REQUIRE_COMPOSITION_COVERAGE=1 turns a
missing engine into a collection error rather than a quiet skip. It runs the
suite, then the three shipped commands, and fails if doctor ever reports a
capability blocked without naming what blocks it.
This page used to pin four commit SHAs and a validation date instead. The rule it stated was right — a green run on one four-tuple says nothing about a different four-tuple — but nothing enforced it: every component moved and no new baseline entry followed, so the page claimed a validation that no longer described anything. The CI does what the pin was pretending to do, against whatever revision each engine is actually at.
nazelo-trust doctor prints the versions it is speaking to, read at runtime.
Prerequisites¶
- Linux, root (a per-run network namespace needs
CAP_SYS_ADMIN). - Kernel with eBPF +
tc(clsact / BPF classifier). Validated on6.6.87.2-microsoft-standard-WSL2. - Tools on PATH:
bpftool,tc(iproute2),clang,nsenter, and CAP_SYS_ADMIN to enter the sandbox's network namespace. Having the four binaries without the privilege refuses the run, naming the privilege. - The
litatolibinary + its keys (LITATOLI_KEY_FILEBLAKE3,LITATOLI_ED25519_KEY_FILEEd25519). - The
pimatika-clibinary (to sign the BIM).
Reproducible command¶
The portable, deterministic reproducer (local server as ALLOW, 192.0.2.123 as
DENY — no internet, no DNS):
sudo env \
LITATOLI_KEY_FILE=~/.config/litatoli/signing.key \
LITATOLI_ED25519_KEY_FILE=~/.config/litatoli/ed25519.key \
PATH="/path/to/litatoli/dir:$PATH" \
PIMATIKA_CLI=/path/to/pimatika-cli \
examples/ci/run-deterministic.sh
Driven in CI by tests/test_ci_deterministic.py (set NAZELO_TRUST_REQUIRE_E2E=1 so a
privileged job fails instead of skipping). Full walk-through:
docs/CONCLUSIVE-TEST.md.
Guaranteed behaviour¶
For a run under this baseline:
- PimaTika decides. A signed BIM is loaded and verified; each egress intent
yields a sealed
EGRESS_ALLOWED/EGRESS_DENIEDwith a semantic reason (TARGET_NOT_IN_USAGE_CONTRACTS,PURPOSE_MISMATCH, …). - NaZelo enforces and observes. The allowed targets are projected into
the eBPF filter; a non-allowed IPv4 L3/L4 destination is dropped in-kernel and
the enforcer's own per-destination drop count is sealed
(
sandbox.network.blocked→{dst_ip, dropped_packets}). - Litatoli seals and verifies. One chain holds the PimaTika decisions and
the NaZelo lifecycle;
verify-chainaccepts it pinned to the signer and a head receipt. - Causal lifecycle order. The sandbox lifecycle is sealed in causal order:
create → grant → exec → blocked → revoke → destroy. - One correlation id. A single
run_idjoins all of it; the chain is queryable by it (litatoli query --run-id).
Reference values from NaZelo Trust-TP-0.1: DENY 192.0.2.123 → 7 dropped_packets;
order as above; overall_ok: true, 10 signed entries.
Explicit limits¶
- Drop counters are IPv4 only. IPv6 egress is still enforced but its drops are not counted.
- The drop-evidence key is IP-only. It does not distinguish port or protocol, so it cannot separate two policies toward the same IP.
nazelo.workload.outputis a self-report. It is the workload's own account of what it observed (OPEN/BLOCKED), sealed — not an independent enforcer observation. The enforcer's independent evidence issandbox.network.blocked.- The head receipt is unsigned. It catches truncation, but an attacker able to rewrite BOTH the chain and the receipt could make them agree. For a hostile setting, publish the head independently (CI logs, a witness, a second host).
- Seal order ≠ strict temporal order for the tail.
nazelo.workload.outputandnazelo.run.sealedare sealed at collection time (afterdestroy). The causal guarantee is the lifecycle segment (create → … → destroy).
Compatibility rule¶
This is a contract between three moving components plus the harness. A green run on one four-tuple says nothing about a different four-tuple: never mix an engine from one build with the others from another without re-validating.
Re-validating is the composition job. A red one means the current four-tuple is not validated — not that a test is flaky.