Files
AegisGitea-MCP/docs/audit-logging.md
T
Latte cf3d2e17d3 fix: fail closed on an unwritable audit log, bound the rate limiter and harden startup
The audit logger is opened at startup (exit 2 on failure) and obtained before
every call, which is refused with audit_unavailable otherwise. Audit records only
take validated path and sudo values and use the request id as correlation id. The
rate limiter evicts idle keys, caps tracked keys, audits the first excess per key
and window and uses the settings passed to the app. FORWARDED_ALLOW_IPS is now a
setting, and startup warns about a missing host allow-list and unavailable OAuth.
2026-10-14 16:24:51 +02:00

3.7 KiB

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.

What an event contains

Each line is one JSON object:

Field Meaning
timestamp UTC, ISO 8601
event_type tool_invocation 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)
entry_hash SHA-256 over the previous hash and this entry

A tool invocation payload records:

  • the tool name, its domain, the HTTP method and the path template (for example /repos/{owner}/{repo}/issues, never the filled-in values);
  • the identity: Gitea login and mode (admin-token or oauth), and the sudo user when one was given;
  • the HTTP status when Gitea answered, and the outcome: success, or the error kind (not_found, forbidden, invalid_argument, internal_error, ...);
  • the duration in milliseconds and the correlation id;
  • the argument names, sorted. Argument values are never logged, so request bodies, secret values and file contents do not reach the log.

All payloads also pass through secret masking before they are written (SECRET_DETECTION_MODE other than off).

Integrity

Each entry hashes the previous one, so editing, deleting or reordering a line breaks the chain from that point on. Validate it with:

make validate-audit                                   # default path
uv run python scripts/validate_audit_log.py --path /var/lib/aegis-mcp/audit.log

Exit code 0 means the chain is intact; 1 lists the lines that do not verify. The default path is AUDIT_LOG_PATH, else DATA_DIR/audit.log.

The chain proves that history was not altered after the fact. It does not stop someone with write access to the whole file from rewriting it consistently from the start, so also ship the log to storage the server cannot modify, and compare the latest entry_hash against a copy you keep elsewhere.

Location and permissions

The log is written to AUDIT_LOG_PATH, default DATA_DIR/audit.log (/var/lib/aegis-mcp in the 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.

Rotation

The server appends to one file and does not rotate it, because rotating mid-stream would break the chain. Rotate externally with logrotate and copytruncate, so the open file handle keeps appending:

/var/lib/aegis-mcp/audit.log {
    weekly
    rotate 12
    compress
    missingok
    notifempty
    copytruncate
}

The validator expects a chain to start at GENESIS, so only the first segment verifies as it is. For a later segment, check that its first prev_hash equals the last entry_hash of the previous segment. Validate segments before archiving them, and keep them in order.

Operating it

  • Alert on bursts of forbidden or unauthorized outcomes and on tool calls by unexpected identities.
  • Review calls to admin, secret and delete tools. The tool name makes them easy to filter.
  • Run the integrity check regularly and during incident response.