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:
3. A BIM-governed run — ALLOW and DENY¶
There are two ways to run.
- Without a BIM,
--allowrules 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_DENIEDwith 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 — onerun_id, one chain, onebim_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:
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.