Tracing and checking packaged apps
Every packaged application raises two questions: what it actually touches on the machine, and whether it actually works. The trace commands answer the first question, payload checks answer the second, and this post presents both with worked examples.
The Tebako team
github.com/tamatebakoEvery packaged application raises two questions that hand-written, disposable diagnostics answer poorly: what the payload actually touches on the machine, and whether the payload actually works. Tebako ships a toolkit for each — the trace commands and payload checks — and this post presents both with worked examples. Both are also covered as step-by-step walkthroughs in the guides: Trace a packaged app and Check a payload.
The trace toolkit: see what your payload touches
Every tebako interception point — mount, path dispatch, dlopen,
exec, materialize, jail verdict, resolve — now emits one structured
JSON event onto a stream inside the virtual-filesystem layer. The
stream is identical on Linux, macOS, and Windows, and it records
operating-system facts only, so every runtime is covered equally.
Turn it on with one environment variable:
TEBAKO_TRACE=/tmp/capture.jsonl
Tracing is deliberately safe to leave on: if the capture channel fails, tebako prints a warning to stderr and nothing else happens — the application’s exit code and behavior are never affected.
Figure 1 — Trace cover compares an outside capture with the inside stream, and a payload check declares its own acceptance test; these are the two tools of this release.
Four commands read that stream:
-
tebako trace run <pkg>— discovery: it runs a package with the stream armed and drafts a manifest fragment; the fragment records the host paths the package touched (asneeds:grants), the files it read through raw file descriptors (asmaterialize:candidates), and the shared libraries and host executables it loaded. The draft is commented YAML, and tebako never applies it automatically; the operator reviews it, adjusts read and write permissions, and records why each grant exists. -
tebako trace explain <capture.jsonl>— diagnosis: it replays a capture through the chain of steps a file access goes through (mount → manifest read → resolve → materialize → OS bind) and names the first step that failed, with a likely cause. Exit code 0 means the chain is green; exit code 1 names the failing step. -
tebako trace cover— coverage: it produces the report described below. -
tebako trace import procmon <capture.csv>— the Windows path, which is also described below.
A worked example: the escape the jail cannot show you
Suppose a packaged application has been jailed with a deny policy and
behaves correctly. One question remains: whether anything reaches the
host below the virtual filesystem entirely — through a native
library’s own fopen, or through a probe made by the system loader.
The jail journal cannot answer it, because an access that bypasses the
virtual filesystem is invisible from inside it by definition.
The answer is to compare an inside capture with an outside capture of the same run:
$ tebako trace run ./myapp --capture inside.jsonl --out draft.yaml -- serve ./public
$ # …the same run again, captured outside the process…
$ tebako trace cover --inside inside.jsonl --outside retrace.json --prefix /mnt/tfs
escape /mnt/tfs/secret/keys.pem func=open tid=603 pid=601 class=read
escape /mnt/tfs/tmp/.lock func=openat tid=604 pid=601 class=read
$ echo $?
1
Two files under the mounted prefix were read through a path the inside stream never saw — reads that bypassed the interception layer and hit the host’s files directly. Exit 0 means no escapes; exit 1 lists them, so the verdict can gate CI. Stderr adds the coverage percentage per surface and which layer produced the capture.
One caveat, which the report also states: an outside capture can only
certify escapes that go through the layer it watches. A capture at the
libc boundary (retrace) certifies libc-routed escapes; certifying raw
syscalls and loader-internal probes takes a kernel-layer capture
(--layer kernel).
On Windows: import a Procmon capture
On Windows, the outside capture that works today is Procmon’s CSV export. An in-process converter normalizes it into the same JSON shape:
PS> tebako trace import procmon .\sassc-run.csv > outside.json
tebako: trace import: entries=1832 bad-rows=0
PS> tebako trace cover --inside inside.jsonl --outside outside.json --prefix C:/pkg/scss --pid 9012 --layer kernel
escape C:/pkg/scss/_a.scss func=QueryOpen tid=0 pid=9012 class=probe
escape C:/pkg/scss/_hidden.scss func=CreateFile tid=0 pid=9012 class=probe
That output is a real case: a native Sass importer probing for partials with raw Win32 calls, underneath every libc hook — the kind of access that only a kernel-layer capture can see. The libc-boundary alternative on Windows (retrace’s live-injection backends) does not work at retrace v2.14.0, because the injector crashes inside the child’s engine boot. Until that failure is fixed upstream, the Procmon path carries Windows coverage, and a CI check watches for the fix so that the project notices when either side changes.
Payload checks: does it work, written down
A check is the payload’s own acceptance test, declared in its in-image manifest and carried inside its image; it states that, given the declared needs, the payload performs its one real task. Integrity verification proves that the bytes are the publisher’s; a check proves that the payload works once the bytes are present.
Here is the canonical example — the metanorma formula’s Homebrew test, written as a declaration:
checks:
html-xml:
entry: /bin/metanorma
argv: ["--type", "iso", "{scratch}/test-iso.adoc", "--agree-to-terms"]
fixtures: /__tpkg__/check/html-xml
expect: { exit: 0, files: ["test-iso.xml", "test-iso.html"] }
timeout: 180
tebako check runs it against an installed payload on the local
machine, against a just-pressed image in feedstock CI (--runtime /
--runtime-image), against a pressed package, or against a
composition document (which can declare checks for the composition
itself):
$ tebako check metanorma
slice metanorma: html-xml PASS 41s
slice metanorma: pdf SKIP (no jvm in the composition)
$ echo $?
0
A few deliberate rules keep checks useful:
-
A missing prerequisite skips with a clear reason, and never fails. A check fails only when its prerequisites are present and its behavior is wrong.
-
Assertions are deliberately simple: files must exist and be non-empty, plus one optional stdout pattern. Fragile byte-for-byte golden outputs are not supported on purpose.
-
Any failure exits with code 79 (
EX_TEBAKO_CHECK) and names the expectation that failed. -
--recordruns the checks under the record policy, so the same acceptance test doubles as needs discovery;--keep-scratchpreserves the scratch directory while a failing check is being debugged.
What’s next
Two things remain open, and both are known. First, the Windows
libc-layer live capture waits on the upstream retrace fix described
above. Second, the metanorma feedstock’s html-xml gate turns on once
the feedstock re-pins its workflows to this CLI — the check engine
shipped with its first release, and feedstocks consume released
binaries only, because
releases are the interface between them. Wiring checks into
tebako press and tebako install gates is the remaining work.
Get it
brew upgrade tamatebako/tap/tebako
Alternatively, download the binaries for the target platform from the releases page. Every asset is built and tested in CI before it is uploaded.
The team welcomes reports of surprising behavior in the issue tracker.