Appearance
Audit & Compliance
Every privileged action in SeyalRun — a login, a session, a command a filter blocked, a credential reveal, a job run, an agent's API call — writes a row to a single tamper-evident audit log.
What makes it tamper-evident
Each row stores three extra fields:
| Field | Meaning |
|---|---|
seq | Strict monotonic position in the chain |
prev_hash | The previous row's entry_hash |
entry_hash | SHA-256(seq, prev_hash, canonical(payload)) |
Because each hash binds the row to its position and its predecessor, editing or deleting any historical row breaks every hash after it. An administrator who quietly rewrites their own tracks leaves the chain broken at exactly the row they touched.
The payload is canonicalized (sorted keys, no whitespace) before hashing, so the hash is reproducible — recomputing it later gives the same answer or tells you the contents changed.
Verifying the chain
GET /api/v1/audit/verifyRecomputes the chain over every chained row and reports the first break: ok: true, or ok: false with the offending seq. The Audit Logs page verifies on load and shows the result.
From the shell:
sh
ops/rebaseline-identity-db.sh --check # runs the real verify_chain() over the logVerify the property, not a proxy
Counting rows — or counting rows that have a hash — is not verification. Do not present an audit log as evidence until verify_chain() has actually returned ok. Note also that a chain of zero rows passes vacuously: generate real activity first.
Append-only at the database
The application never updates or deletes audit rows, and the database enforces it: triggers reject UPDATE and DELETE on the table outright, with a hint explaining why.
That's why the retention endpoint only counts rows past the threshold rather than removing them — deleting one would break the chain for everything after. Archival, when you need it, must copy rather than move; genuinely removing rows means dropping the trigger, verifying, archiving and re-creating it, which is a deliberate, auditable sequence rather than an incidental DELETE.
What a row contains
Beyond actor, action, resource and timestamp, rows carry:
result— success or failure. Failed attempts are the ones you most want.session_id— correlates an entry to the session it happened in.seq— its position in the chain.
All three are bound into the entry hash and exposed through the API, so the Audit Logs page and any external consumer can see them.
Log shipping
Admin → Log Backend routes log categories to external systems — Elasticsearch and S3 are supported, with connection tests built in. Backend secrets (es_api_key, s3_secret_access_key) are vault-encrypted at rest and masked on read; they are never returned in clear.
The same configuration decides whether session recordings tier to S3 (see Sessions).
Log redaction
libs/securelog strips password, secret, token, vault, and authorization fields from all structured logs across every service. It's a blanket protection applied at the logging layer, not something each call site opts into — so a new endpoint can't leak a secret into the logs by forgetting to.
Housekeeping
automation-service runs a scheduled housekeeping loop for retention and maintenance tasks. Jobs degrade to a successful no-op when the backend they'd use (S3, Elasticsearch) isn't configured, so a minimal deployment stays green instead of alarming on work it was never asked to do.
Agent actions are in the same log
An AI agent's calls go through api-gateway like everything else, so they land in this same chain, attributed to the token's owner. There is no separate agent log to reconcile. See AI Agents & MCP.
Compliance artifacts
The repository ships control mapping, a risk register, and staging-validation evidence under docs/compliance/, versioned alongside the code they describe.
