fix: write-ahead audit entry so calls stop when the audit log stops accepting writes (SEC-03)
This commit is contained in:
1 parent
5b622e64e2
commit
43eebd3089
8 files changed
+134
-15
No files matched your search
@@ -115,8 +115,9 @@ One token verifier accepts either kind. The dispatcher turns the verified token
|
||||
|
||||
## Audit
|
||||
|
||||
Every tool call, including failed and invalid ones, produces exactly one event in the
|
||||
append-only, hash-chained audit log. Only argument names are recorded, never values. Details and
|
||||
Every tool call, including failed and invalid ones, produces exactly one result event in the
|
||||
append-only, hash-chained audit log, after a write-ahead event that makes a call fail closed when
|
||||
the log stops accepting writes. Only argument names are recorded, never values. Details and
|
||||
integrity validation are in [audit-logging.md](audit-logging.md).
|
||||
|
||||
## Request flow
|
||||
@@ -127,7 +128,9 @@ integrity validation are in [audit-logging.md](audit-logging.md).
|
||||
schema, and resolves the identity.
|
||||
4. The executor calls Gitea with the identity's token.
|
||||
5. The response is bounded, redacted and masked, and wrapped in the envelope.
|
||||
6. One audit event is written and the result is returned.
|
||||
6. One audit result event is written and the result is returned. (A write-ahead
|
||||
`tool_call_started` event is written right after the tool lookup in step 3; when it cannot be
|
||||
written the call is refused.)
|
||||
|
||||
## Module map
|
||||
|
||||
|
||||
+13
-9
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ Repudiation
|
||||
|
||||
| Threat | Mitigation | Implemented in |
|
||||
|---|---|---|
|
||||
| Unrecorded tool calls | Exactly one hash-chained audit entry per call; a call is refused when the audit log cannot be opened; startup fails on an unwritable log | `server.py`, `audit.py`, `transports.py` (write failures after startup: SEC-03) |
|
||||
| Unrecorded tool calls | Exactly one hash-chained result entry per call, preceded by a write-ahead `tool_call_started` entry; a call is refused when either the logger or that write fails; startup fails on an unwritable log | `server.py`, `audit.py`, `transports.py` |
|
||||
| Undetected edits of the log | SHA-256 hash chain, `scripts/validate_audit_log.py` | `audit.py` (unkeyed chain: SEC-11) |
|
||||
|
||||
Information disclosure
|
||||
|
||||
Reference in new issue
Block a user