fix: write-ahead audit entry so calls stop when the audit log stops accepting writes (SEC-03)

This commit is contained in:
Latte committed 2026-10-14 17:11:53 +02:00
1 parent 5b622e64e2
commit 43eebd3089
8 files changed
+134 -15

No files matched your search

+13 -9
View File
@@ -1,8 +1,10 @@
# Audit logging
Every tool call produces exactly one event in an append-only, hash-chained JSON-lines log,
whether it succeeded, failed at Gitea, failed validation or hit an internal error. Security
events are logged in the same chain.
Every tool call produces exactly one result event (`tool_invocation`) in an append-only,
hash-chained JSON-lines log, whether it succeeded, failed at Gitea, failed validation or hit an
internal error. A call to a known tool is preceded by a small write-ahead event
(`tool_call_started`, see Location and permissions). Security events are logged in the same
chain.
## What an event contains
@@ -11,7 +13,7 @@ Each line is one JSON object:
| Field | Meaning |
|---|---|
| `timestamp` | UTC, ISO 8601 |
| `event_type` | `tool_invocation` or `security_event` |
| `event_type` | `tool_invocation`, `tool_call_started`, `access_denied` or `security_event` |
| `request_id` | Correlation id of the request |
| `payload` | The event data, see below |
| `prev_hash` | `entry_hash` of the previous line (`GENESIS` for the first) |
@@ -56,11 +58,13 @@ The log is written to `AUDIT_LOG_PATH`, default `DATA_DIR/audit.log` (`/var/lib/
image). Keep it on durable storage, readable and writable only by the service account.
The server opens the file for append at startup and exits with code 2 (message on stderr) when
that fails, so it never runs unaudited. Each call also obtains the audit logger before it runs;
if that fails the call is refused with `audit_unavailable` and never reaches Gitea. A write
failure after a call has executed is only logged to stderr: the Gitea side effect already
happened and the caller must still receive the result. The `correlation_id` of a tool entry is
the request id, which is also the entry's `request_id`.
that fails, so it never runs unaudited. The check does not stop at startup: each call to a known
tool first writes a `tool_call_started` entry (tool name, domain and correlation id), and when
that write fails (full disk, revoked mount, closed file) the call is refused with
`audit_unavailable` and never reaches Gitea. Calls resume as soon as writes work again. A write
failure of the final `tool_invocation` entry after a call has executed is only logged to stderr:
the Gitea side effect already happened and the caller must still receive the result. The
`correlation_id` of both entries is the request id, which is also the entry's `request_id`.
## Rotation