Audit log
The audit log is Sondar’s own record of what happened on the server: every
sign-in, every change and every search, written by the server as it happens.
It lives in its own index, _audit, which nobody can delete, write into or
cut short, and each record is chained to the one before it so that a record
edited or removed afterwards shows up when the log is verified.
Every edition records the audit log and can view, filter, export and verify it. Pro adds keeping it for longer than 90 days (Licensing).
What is recorded
Section titled “What is recorded”| Kind | Written when | What it says |
|---|---|---|
| Request | an HTTP request that changes something is answered; or a read of the audit log itself, a read that takes data out (an export or download, an uploaded or lookup file), or a sign-in or sign-out | who (user), what (action, module, the handler that served it), the method and path, the status the server sent and whether that was a success, the TCP peer address and, separately, the address the client claimed in X-Forwarded-For |
| Search | a search ends | the job, the user, where it came from (search, dashboard, alert_rule, export_task, …), the query, its time range, how it ended (completed, failed, terminated, timeout), how long it took, how many events and rows it returned, whether a row limit cut it, and the error; what it read — the indexes searched, any it named but may not read, which were masked, the row-level limits applied, and whether the search time limit cut its range; and, for a search the system ran, who for (schedule:…, alert:…, export:…) |
| Loss notice | records could not be written | how many were lost |
Not recorded: reads that change nothing and take no data out — the license,
settings, job progress, message and status reads the pages repeat while they are
open, which would otherwise fill the log with the product talking to itself (who
searched what is the search record, written once when the search ends); the
bodies of requests the product does not audit by name (a body can carry a
password), data sent to the ingest endpoints (HEC, OTLP, /api/v1/data,
Prometheus remote write), and the product’s own housekeeping searches (run as
its internal user with no more specific source, such as metric synchronization
and log-pattern lookups). Searches an export runs are recorded, and say which
one. The searches a rule or a schedule runs — alert, aggregation and
metric-generation rules, scheduled searches — are recorded when something
changes: the first run of each of its searches, any run that ends differently
from the last (completed, failed, terminated, timeout), every run that fails, and at least one run a day for each rule, so the log
shows the rule is still running. A run that repeats the last one exactly within
the day is not recorded. Searches that run without a job are recorded too — Prometheus
remote read, the gRPC query stream, the data-masking report — under part: query, without a row count.
Reading it
Section titled “Reading it”Settings → Advanced Settings → Audit Log. Filter by time range, user, action, kind and outcome; open a row to see the whole record. It needs the Audit Log permission: List to read and verify, Download to export. The admin role has it; analysts do not.
Read-only access for auditors (Pro). The built-in auditor role holds that
permission and nothing else: an auditor sees the audit log — every page of it,
export and verification — and no data, no settings and no other page. Giving
it to someone needs Pro, by any route: the user’s roles, a role’s member list, a
group that carries it, or a sign-in setting (an SSO group mapping or default
role) that hands it out. Taking it away is always allowed (it takes effect within
15 seconds for someone already signed in, as any role change does), and an
assignment made under Pro keeps working when Pro ends.
Without Pro, an admin can still hand an auditor the evidence: a JSON Lines export, which re-verifies outside Sondar, and the last hash from Verify.
The same records are in SonQL: repo="_audit".
The API:
| Endpoint | |
|---|---|
GET /api/v1/audit/records | newest first; from, to (epoch ms), user, action, kind (request, search, notice), outcome (ok, failed, lost), size (up to 500), cursor (the previous page’s next) |
GET /api/v1/audit/export?format=jsonl | every matching record, whole, with its chain fields — at most 100,000 per export; the X-Audit-Export-Truncated header says when more matched. format=csv gives the summary columns for a spreadsheet |
GET /api/v1/audit/verify | each node’s chain over from/to |
Reading, exporting and verifying the audit log are themselves recorded: each page of records someone opens is one record (the page never reloads on its own).
How the records are protected
Section titled “How the records are protected”- Nobody deletes the index. Deleting the
_auditrepo, deleting or closing its indices through the internal admin API, removing its alias, and SonQL| deleteover it are all refused. - Nobody writes into it. Only the server writes the audit log: ingest into
_auditand SonQL| export repo="_audit"are refused. - Nobody cuts it short. It is deleted by age only, never by a capacity limit; it keeps at least 7 days; it cannot follow the global retention setting; it keeps its raw text; and under disk pressure it stops and alerts rather than deleting old records to make room.
- Losses are recorded. A record that cannot be written is retried with the same chain position; one that never makes it leaves a gap in the numbers and a loss notice saying how many were lost. Up to 200,000 records wait in memory between writes; past that the overflow is counted in the next notice.
Shortening the retention is always allowed, down to 7 days.
Verifying it
Section titled “Verifying it”Each node numbers its records 1, 2, 3, … and links each to the one before:
hash = SHA-256(previous hash + "\n" + the record's raw text). Verification
reads every record in order and reports, per node:
- Edited — a record whose hash no longer matches its text;
- Missing — numbers with no record (deleted, or lost before they were written; a loss also left a notice);
- Repeated and broken links.
It also counts records in the window that carry no chain fields at all: the server chains every record it writes, so one without was not written by the server’s audit writer, and the log is reported as not intact.
Verification also returns each node’s last hash. Keep it somewhere outside
Sondar: a later verification that still reaches that hash shows that nothing
before it changed. A JSON Lines export carries prev, hash and raw for
every record, so the same check can be run outside Sondar.
What this is not: tamper-proof. Someone able to rewrite the underlying index directly could rewrite a node’s chain from an edit onwards; the last hash you kept elsewhere is what exposes that. And deleting the newest records leaves no gap until the node writes again.
How long it is kept
Section titled “How long it is kept”90 days by default. Every edition can set anything from 7 to 90 days, or keep
a longer setting that Pro made; raising it above 90 days needs Pro. Set it in
the _audit index’s settings, or over the API in milliseconds:
PUT /api/v1/repos/_audit with {"retention": 34560000000} is 400 days (a string
such as "400d" is refused, and the refusal names the unit). When a Pro subscription or trial ends, a longer
retention already set keeps working; it can be shortened but not raised again.
How much it holds. Each change, each read that takes data out or opens the audit log, and each search a person runs is one record (a search is two: it is submitted, and it ends). The pages’ own status reads add nothing, and a rule whose runs keep ending the same way adds one record a day, not one per run. Measured 12 Oct 2026 on a fresh install with its two built-in alert rules (every 3 minutes) and an admin signed in: 70 of the status reads the pages repeat, sent over the API, wrote no records (the previous release wrote 70); five minutes on the search page wrote only its two searches; and five rule runs wrote two — the first run of each rule.