Connection lost
Trying to reconnect…
Server didn't respond
Recovering…
Docs / Audit & SIEM
The audit trail
Everything that matters leaves a record: every dispatch and its outcome, every approval decision, every policy edit, every credential minted or revoked, every sign-in — even the failed ones. This page covers reading the trail in the dashboard, streaming it to your SIEM, and how the runner-side journal complements it.
What gets recorded
Events are named
domain.action
— policy.evaluated
(one per dispatch, with the decision), approval.approved, policy.updated, membership.role_changed, user.sign_in_failed, …
about fifty types across runs, approvals, policies, packs, runners, credentials, team,
and account changes. Each carries the actor (operator, API key, or runner), the
affected entity, a structured payload (a policy edit records its exact diff), and an
account-scoped timestamp. Audit rows are written in the
same database transaction
as the change they
describe — an action that committed has its audit row, always.
Reading it in the dashboard
Audit in the sidebar: filter by event type or actor kind; every
row expands to the full payload, and references resolve to live labels (the runner's
name, the user's email) so you're not cross-referencing UUIDs. LLM-driven runs carry
their attribution — which client, which key, which session — plus the required reason, so "what did the
agent do last night, and why" is a filter, not an investigation.
Streaming to a SIEM
The export endpoint serves NDJSON — one event per line, cursor-paginated, forward-only.
Mint a key with the
audit:read
scope from the Audit page; that key can read events and nothing else — it can never
list runners or execute an action.
# first pull — everything since a timestamp $ curl -s "https://emisar.dev/api/audit?since=2026-06-01T00:00:00Z&limit=500" \ -H "Authorization: Bearer $AUDIT_KEY" # follow-ups — pass the cursor from the previous response $ curl -s "https://emisar.dev/api/audit?cursor=$NEXT" \ -H "Authorization: Bearer $AUDIT_KEY"
-
—
The next cursor arrives both as an RFC 5988
Link: <…>; rel="next"header and a plainX-Next-Cursorheader — Splunk-style collectors follow the Link, Datadog-style ones read the X-header. No headers on the last page means you're caught up; poll again later with the same cursor. -
—
Pages are capped at 1,000 events; ordering is stable
(
occurred_at, then id), so a poller never misses or double-counts an event. - — Retention follows your plan — 7 days on Free, 90 on Team, 365 on Enterprise. Ship to your SIEM ahead of the window and retention becomes your SIEM's policy, not ours.
The runner-side journal
Independently of the cloud, every runner writes a JSONL line per action attempt to
/var/log/emisar/events.jsonl
— argv hashes, exit codes, redaction counts, output digests — each line chained to the
previous by SHA-256.
emisar audit verify --all
proves the chain (including rotated files). The cloud is the searchable system of
record; the journal is the host-side forensics copy that doesn't depend on us — cutting
or editing a line breaks the chain detectably, though a root attacker can of course
delete the whole file. Ship it with your normal log pipeline if you want both records
off-host.
user.sign_in_failed
spikes
(someone probing your team),
policy.updated
(the rules
changed), pack-trust transitions (new bytes were approved), and on the runner side
validation_failed
/ action_blocked_by_admission
— an agent repeatedly asking for things it can't have is the most interesting signal
you'll get.