Skip to content

Getting started with naZelo Trust

A guided first success. You will install the stack, run one sealed command, then run a BIM-governed command that shows an ALLOW and a DENY, and finally verify the sealed chain offline.

Each step ends where nazelo-trust doctor says the next capability is READY. Full setup detail is in INSTALL.md.

1. Install

pip install nazelo-trust        # pulls in nazelo, pimatika, pyyaml, click
nazelo-trust doctor             # → Isolate READY

For sealing you also need the litatoli binary on PATH (or point $NAZELO_TRUST_LITATOLI_BINARY at it) and its keys:

litatoli keygen                 # writes ~/.config/litatoli/{signing.key,ed25519.key}
nazelo-trust doctor             # → Seal READY

doctor reports the capabilities in order — isolate → seal → egress → three bricks — and tells you exactly what each remaining one needs. For egress enforcement and the BIM path, follow the eBPF and pimatika-cli steps in INSTALL.md.

2. A sealed run

# Isolated + sealed, no network (the default policy is deny-all):
nazelo-trust run -- pytest -q
# run_id: 9f2c… · exit=0 · no egress (deny-all) · sealed → …/chain.jsonl

naZelo isolates the command, Litatoli seals the run into a signed chain, and egress is deny-all by default. If the command passes but the evidence cannot be sealed, naZelo Trust reports NOT SEALED — never a green pass.

To allow specific egress without a BIM, --allow rules are enforced by naZelo's own eBPF filter at the IP/port level. This turns on eBPF enforcement and requires the eBPF tooling; naZelo Trust refuses to run an egress-granting workload it cannot enforce:

nazelo-trust run --allow api.github.com --allow port:443 -- ./deploy.sh

3. A BIM-governed run — ALLOW and DENY

There are two ways to run.

  • Without a BIM, --allow rules are enforced by naZelo's eBPF filter at the IP/port level — isolation and sealing, no policy engine.
  • With a signed BIM, PimaTika loads and verifies it and decides each egress semantically (target, purpose, data_class, method, payload), sealing an EGRESS_ALLOWED / EGRESS_DENIED with a reason the packet layer could never give (PURPOSE_MISMATCH, TARGET_NOT_IN_USAGE_CONTRACTS, …). The allowed targets are then projected into naZelo's eBPF filter. PimaTika decides, naZelo enforces, Litatoli seals — one run_id, one chain, one bim_digest.
nazelo-trust run --bim policy.signed.yaml --code ./app --probe https://1.1.1.1 -- ./app
# PimaTika (bim blake3:7c39…) decided:
#   ALLOW  https://example.com
#   DENY TARGET_NOT_IN_USAGE_CONTRACTS  https://1.1.1.1
# run_id: … · exit=0 · PimaTika-decided, naZelo-enforced · sealed → …/chain.jsonl

Is it the program the BIM was signed for?

A BIM names the digests of the code, its configuration and its dependencies. --code is required with --bim: the run compares the code about to run with the BIM before the sandbox starts. --config and --dependencies add the other two:

nazelo-trust run --bim policy.signed.yaml \
  --code ./app --config ./config.json --dependencies ./vendor -- ./app/main
# … · admission: not-required · workload: verified

A file or directory that differs from what was signed stops the run before anything executes, and the refusal is sealed in the chain (nazelo.workload.refused, with the reason workload_digest_mismatch). The build fills the digests with pimatika-cli digest-path <path>, which uses the same format. The verified code digest is also the artifact digest an --intoto attestation binds to, so --artifact-digest is no longer needed.

A committed, reproducible version of this run — with the signed BIM, the probe, the runner, and a captured sample chain — is in examples/conclusive/ and explained in CONCLUSIVE-TEST.md. Run it:

sudo env \
  LITATOLI_KEY_FILE=~/.config/litatoli/signing.key \
  LITATOLI_ED25519_KEY_FILE=~/.config/litatoli/ed25519.key \
  PATH="/path/to/litatoli/dir:$PATH" \
  examples/conclusive/run.sh

Why not just nazelo run?

nazelo run is the engine, à la carte: it can seal to Litatoli and enforce egress with its eBPF filter, but by default it seals nothing (a null recorder). naZelo Trust is the opinionated integration: sealed by default, egress deny-all by default, and it refuses to report a run as passed when its evidence could not be sealed.

4. Verify the chain offline

The same run_id is stamped onto every PimaTika egress decision, every sandbox event, and every sealed chain entry (ADR-0005). Hand it to your dashboards, your CI, or Litatoli directly, and one query returns everything that happened in one run — the semantic decisions and the sandbox lifecycle, in one signed chain.

PUB=$(cat examples/conclusive/sample-chain.pubkey)
HEAD=$(cat examples/conclusive/sample-chain.head)
litatoli verify-chain --log-file examples/conclusive/sample-chain.jsonl --expected-pubkey "$PUB" --expected-head "$HEAD"
# → overall_ok: True · pinned-leaf · head pinned (end-truncation would fail)

Query the whole run by its run_id — a structured, signed, top-level field, not buried in the payload:

litatoli query --log-file examples/conclusive/sample-chain.jsonl --run-id RUN-CONCLUSIVE
# the PimaTika ALLOW and DENY, the naZelo lifecycle, and the sealed output — one chain

One page for one run

nazelo-trust report reads the same chain back as one page, in the order a reviewer asks: was it the program the BIM was signed for, what did PimaTika decide, what did the filter drop and let through, and does the record hold.

nazelo-trust report examples/conclusive/sample-chain.jsonl \
  --pubkey examples/conclusive/sample-chain.pubkey \
  --head   examples/conclusive/sample-chain.head -o run.html

It runs litatoli verify-chain itself, against the key and head you pass. Without --pubkey, the page opens with NOT CHECKED above everything else, and the command exits 1: a chain checked against the key it carries proves only that it agrees with itself. Exit 0 means every check the page can make passed; 2 means the file could not be read as one run (codes chain_unreadable, chain_empty, run_ambiguous, run_not_found). --json prints the same report as data, each section with a status: verified, failed, recorded, not_checked or absent.

The program's own output is on the page, labelled as its own account: a program saying it was blocked is not evidence that it was, and the filter's counts sit in their own section. The page runs no script and loads nothing, so it opens offline. The assurance level and an attestation's witness receipt are not on it; nazelo-trust verify checks those.

Machine output and the Python API

Bring your own correlation id (for example your CI run) and get machine output:

nazelo-trust run --run-id "$CI_RUN_ID" --json -- cargo build

If you are in a Python codebase:

from nazelo_trust import TrustRun

result = TrustRun(preset="ci").run(["pytest", "-q"])
assert result.ok                 # command passed AND evidence sealed
print(result.run_id, result.evidence_log)

Next

  • A real service, end to end — an ONNX service with a vendored library that phones home: built, signed, run, read back, written as cluster policies, and attacked twice.
  • Operating limits — what each control proves and where it stops. Read before trusting a result.
  • Disclosure policy — what leaves the machine. Read before enabling any export.
  • Integration contract — what the three engines guarantee together, and what they do not.