Skip to main content

Audit & tamper-evidence

Every decision Belay makes leaves a record, and those records are built to resist quiet tampering by a compromised agent. This page covers the hash-chained audit log, the evidence packs you can build and verify, the honeypot canaries, and — honestly — the limits of self-protection when running unprivileged.

When to read this

Read this when you care about proving what happened: for incident review, for CI evidence, or simply to understand why the audit log can't be silently edited out from under you. It builds on Verdicts — the decisions recorded here are the verdicts described there.

The hash-chained audit log

Belay appends every decision to a local audit log at ~/.belay/audit.ndjson (one JSON row per line). Each row is hash-chained to the one before it:

hash = sha256(prev_hash + canonical_json(row))

canonical_json is a recursively key-sorted, whitespace-free encoding of the row, so the hash is stable and reproducible. Because each row's hash folds in the previous row's hash, the log behaves like a chain: you cannot alter, reorder, or delete a row in the middle without breaking every hash that follows it. Tampering becomes detectable, not invisible.

note

The audit log lives under the invoking user's home directory because Belay runs as that user, never root. Its self-protection layers specifically treat this file as a protected path (see below). On Windows the data directory is %ProgramData%\Belay instead of ~/.belay.

You can inspect recent rows directly:

belay status # the most recent audit rows (last 20)
belay logs -n 100 # the last N rows
note

The audit-view commands are belay status and belay logs. There is no belay audit command.

Evidence packs: build and verify

For a portable, checkable record — for example to attach scanner results to a CI run or hand off to a reviewer — Belay produces evidence packs.

belay evidence build # write a tamper-evident pack
belay evidence verify # recompute and check it

evidence build snapshots your last 500 audit-log rows into findings + a manifest.json that lists a SHA-256 hash per file in the pack (the SARIF file it writes today is a fixed empty stub, not real scanner output). evidence verify recomputes those hashes and compares them against the manifest, so any post-hoc edit to a file in the pack is caught. The pack is a self-contained, verifiable snapshot.

tip

For CI code-scanning, feed the scanner's own SARIF 2.1.0 output (belay scan --format sarif) into your pipeline directly — the evidence build SARIF file is a placeholder, not a substitute for it.

Honeypot canaries

Belay plants canary files — decoys that no legitimate workflow should ever touch. There's a fake AWS credentials file (with a decoy access-key sentinel) and an .env sentinel token (BELAY_CANARY_TOKEN). Any read or egress of their sentinel bytes trips a Critical verdict immediately.

The idea is simple: a legitimate agent has no reason to open a fake credentials file, so a read of one is a high-confidence signal that something is enumerating or exfiltrating secrets. The canary turns a stealthy secret-hunting step into a loud, recorded event.

Self-protection layers

Belay guards its own rule catalog, audit log, and binary against a possibly-compromised agent using complementary layers:

  • Command layer. On hook-visible tool calls, a direct write or edit to a protected path (the rule catalog, the audit log, the belay binary) is denied. A Bash command that applies opaque external content (for example git apply, git am, or patch) is surfaced for human review as an ask.
  • Kernel layer (optional, privileged, Linux). With the opt-in eBPF sensor (--features ebpf), a process that writes a protected file outside the hook path is caught via a kernel write event and the reflex kills the process. This is detection and reactive kill, not pre-write blocking.
  • Startup integrity check (unprivileged). The build embeds a SHA-256 of the rule catalog. At daemon startup, a drifted on-disk copy triggers a loud alert. The running binary's compiled-in rules are unaffected regardless.

Honest limit: no unprivileged pre-write blocking

Belay runs unprivileged by design. True syscall-level blocking — denying a write in the kernel before it happens — requires CAP_SYS_ADMIN/root and is intentionally out of scope.

What you get instead is a layered, mostly tamper-evident posture: writes through the hook path are blocked at the command layer; out-of-band writes are (on Linux, with the privileged sensor) detected and reacted to; and the audit log's hash chain plus the evidence manifests make after-the-fact tampering detectable. The design goal is that tampering cannot happen silently, not that an unprivileged process can veto every possible kernel write.