Wheelhouse docs

System — audit#

The Audit tab is the agent's own record of what was asked of it: who, from where, with which role, what operation, and whether it worked. It is one of three separate records the product keeps, and the narrowest of the three in scope and the strongest in attribution.

GET /api/audit?limit=300 returns the newest entries first; the tab polls every 10 s. The default limit is 300 and the tab uses it.

What an entry contains#

FieldMeaning
timestampRFC 3339, in UTC, stamped by the agent at the moment of the write.
actorThe account name, the token name, or reconcile-loop for the desired-state loop.
roleviewer, operator, admin, or system for the loop.
remote_ipThe client address, as the agent determined it.
operationThe operation name — the table below.
pathConfiguration path or subject, when the operation has one.
commandsRendered commands. Populated by the reconcile loop; the browser-driven paths do not fill it.
successWhether it worked.
errorThe router's own words when it did not.

The table on screen shows five of those: when (as a relative time), the operation, the path, an ok/failed badge, and the error.

Every operation that is recorded#

OperationWritten by
login, login (oidc), logout, denied …Authentication, including refusals.
bootstrap-adminThe first-run administrator being created.
change-password, totp-enable, totp-disable, revoke-sessionAccount changes.
create-user, delete-user, create-token, revoke-token, update-settingsAdministration.
oidc-link, oidc-provision, oidc-unlinkSingle sign-on identity handling.
configure, commit, commit-confirm, save, rollback, config-loadThe configuration plane.
power-reboot, power-poweroffPower.
image-add, image-delete, image-set_defaultBoot images.
app-pull, app-prepare, app-update, app-restart, app-image-deleteApp actions.
feature-enable, feature-disableFeature modules.
license-set, license-removeLicence changes.
fleet-configureA configuration write to a remote router in fleet mode.

Both outcomes are recorded. A failed commit, a refused login and a rollback the router rejected are all entries, with the reason in the error column — which is what makes this usable as a record rather than as a success log.

Where it is stored#

An append-only file, one JSON object per line, at audit.jsonl in the agent's data directory — /config/wheelhouse/audit.jsonl on an appliance. Each entry is written and fsynced immediately.

That last detail is deliberate: an audit log whose last entries are lost to the power cut that followed the action they describe is not one you can testify from, and the cost is a few hundred bytes — agent/store.go.

PropertyValue
File<data-dir>/audit.jsonl, mode 0600
RotationAt 8 MB, keeping exactly one previous generation as audit.jsonl.1
Maximum on diskTwice the rotation size
Served from memoryThe newest entries, up to the retention setting, capped at 10 000
Retention settingaudit_retention on Agent settings; default 2000, floor 100

The in-memory tail is what GET /api/audit serves. Older entries stay in the file and are readable there; they are not reachable through the API. Setting audit_retention above 10 000 is honoured as far as 10 000 and logs a warning saying exactly that, because a number the settings page echoes back and the agent does not honour is worse than a refusal.

On start-up the agent reads back the tail, previous generation first, so a restart shortly after a rotation still serves a full window. A line torn by a power cut is skipped; the entries either side of it are still good.

What it cannot do#

Four limits, stated plainly because each of them is the sort of thing a compliance question turns on.

  • It cannot leave the box. There is no export, no forwarding, no syslog sink and no API for shipping it anywhere. The journal can be shipped to a collector (Logs); the audit log cannot.
  • It is not tamper-evident. It is a plain file with no hash chain, no signature and no external anchor. Anyone with root on the router can edit it.
  • It records what went through the agent, and only that. A change made at the console appears in the commit history and the journal, and not here.
  • Entries beyond the retention window are not served. They are in the file. Reading them means reading audit.jsonl on the box.

Reading it from the API#

bash
curl -sk -H "Authorization: Bearer $T" \
  "https://<router>:8443/api/audit?limit=50" | python3 -m json.tool
json
[
  {
    "timestamp": "2026-09-02T10:14:07Z",
    "actor": "kate",
    "role": "operator",
    "remote_ip": "192.0.2.31",
    "operation": "commit",
    "success": true
  }
]

The route needs only the viewer role, which is deliberate: everyone who can see the router can see who changed it.

On the box itself, the file is the API's superset:

bash
sudo tail -n 50 /config/wheelhouse/audit.jsonl | python3 -m json.tool

See also#


Checked against agent/store.go, agent/main.go, agent/admin.go, agent/authhttp.go, agent/apps.go, agent/parity.go, agent/license.go, agent/desired.go, agent/oidc.go, ui/src/pages/System.tsx.

Updated 2026-09-02 manual system audit security