Audit log
What harnessd records, the format of each record, and how to check that nobody has edited or deleted entries.
harnessd keeps an append-only, hash-chained audit log at data_dir/audit/audit.jsonl (mode 0600). It records sign-ins, denials, every write action, account changes from the CLI, agent tool calls and run outcomes. Each record includes the hash of the one before it, so editing or deleting any line breaks the chain.
Verify the chain#
sudo -u harness harnessd audit verify --config /etc/harness/harnessd.yaml
# ok: 18342 record(s), hash chain intactIf a line was changed, removed or reordered, the command exits with an error naming the first broken line:
harnessd: audit chain broken at line 912 of 18342Run it from a cron job or your monitoring, and ship the file to your log store if you want an off-host copy.
Record format#
One JSON object per line:
{"action":"login","actor":"user:maria","detail":{"role":"operator"},"hash":"9c1f…","ip":"10.8.0.31","outcome":"completed","prev":"4b7e…","ts":"2026-11-06T09:14:03Z"}| Field | Description |
|---|---|
ts | ISO 8601 time |
actor | Who: user:<name>, anonymous, cli, system, orchestrator or agent:<task> |
action | What, for example login, secret_set, POST /api/v1/refresh |
outcome | completed, or why it didn't happen |
ip | The client's address, when there is one |
detail | Extra fields: names (never values), project, role, and so on |
prev | The previous record's hash (64 zeros for the first) |
hash | SHA-256 of prev followed by the record without hash |
What's recorded#
| Actor | Actions |
|---|---|
system | startup (with the listen address and whether TLS is on), shutdown |
anonymous | Requests without a valid session (unauthenticated), requests from a browser origin (denied_origin) |
user:<name> | login and logout; every non-GET request as <METHOD> <path> with completed, failed_<status> or forbidden (with the role and the role required); secret_set, secret_remove, login_set, login_remove, settings_change (sections, reset sections, variable names set and removed), container_stop, image_build_start, workspace_remove, project_add, project_remove; wrong step-up codes (bad_totp) |
cli | user_add, user_passwd, user_totp_enable, user_totp_disable, user_disable, user_enable, user_remove |
orchestrator | run_finished, with the project, task, outcome, tokens and seconds |
agent:<task> | tool_call, with the tool name and whether it succeeded |
Sign-in outcomes:
| Outcome | Meaning |
|---|---|
completed | Signed in |
bad_credentials | Unknown user or wrong password (indistinguishable to the client) |
totp_required | Right password, no code sent |
bad_totp | Wrong or reused code |
totp_not_enrolled | An admin without TOTP while require_totp_for_admins is on |
locked_out | The account or IP is locked |
Other logs#
The audit log is for security events. Operational logs (key=value lines like the app's Activity screen) go to standard error, which systemd sends to the journal:
journalctl -u harnessd -fPer-project logs and raw agent streams are under data_dir/logs/.