Skip to content

Audit-log /api/ai/triage requests#17

Merged
lai3d merged 1 commit into
mainfrom
claude/triage-audit-log
May 20, 2026
Merged

Audit-log /api/ai/triage requests#17
lai3d merged 1 commit into
mainfrom
claude/triage-audit-log

Conversation

@lai3d
Copy link
Copy Markdown
Owner

@lai3d lai3d commented May 20, 2026

Summary

Closes the last lower-priority gap from the original AI-features review: triage requests left no record. Now every triage that passes RBAC + rate-limit writes an audit_logs row, queryable via GET /api/audit-logs?resource=ai_triage (admin-only).

Logged shape

Column Value
action \"triage\"
resource \"ai_triage\"
resource_id Targeted vps_id (UUID string) when supplied — lets operators filter ?resource_id=<vps> to see who has triaged a given host
details {alert_name, alert_severity, vps_hostname, fired_at, provider, model, available, confidence}
user_id / user_email From CurrentUser (works for both JWT and API-key auth)

What's deliberately not logged

  • Raw description, recent_logs, system_info, ebpf_metrics — these can carry sensitive content (auth tokens, PII, customer data). Audit logs answer "who triaged what", not "what was in the logs".
  • Full LLM response body — also potentially large/sensitive. The confidence + availability summary is enough for accounting.
  • 403 (RBAC denied) and 429 (rate-limit denied) — these are security events, visible in structured warn! / info! logs already. Audit logs are for legitimate operator actions.

Refactor for safety

The handler had three exit paths (no API key → degraded; LLM failed → degraded; LLM succeeded → parsed). Refactored to build the response in a single let response = match ... { ... }; binding so a single log_audit call after the match covers all three. No behaviour change; just makes "every response goes through audit" structurally obvious.

Tests

New integration test `test_triage_writes_audit_log`:

  1. Operator triages a synthetic alert pinned to a fresh vps_id
  2. Admin GETs /api/audit-logs?resource=ai_triage
  3. Asserts total=1, action/resource/resource_id/user_email all correct, and details contains the captured alert metadata + resolved provider (anthropic default) + available: false (test env has no LLM_API_KEY)

cargo build --tests clean locally.

Docs

docs/ai-triage.{en,zh}.md gain an "Audit logging" subsection between rate-limit and OpenAPI, documenting the schema and the deliberate exclusions.

Test plan

  • cargo build --tests — green locally
  • CI runs the new integration test against Postgres + Redis
  • Manual smoke: trigger a triage in dev, check /api/audit-logs?resource=ai_triage shows the row with the expected details

🤖 Generated with Claude Code

Every triage that passes RBAC + rate-limit now writes an audit_logs row
(action=triage, resource=ai_triage, resource_id=vps_id when supplied).
The details payload captures alert metadata, the resolved provider/model,
and the LLM outcome — but NOT the raw description, recent_logs, or
ebpf_metrics, which can carry sensitive content. Audit logs answer
"who triaged what, when, with what outcome", not "what was in the logs".

403 (RBAC denied) and 429 (rate-limit denied) are intentionally NOT
audit-logged — they're security events visible in the warn!/info!
logs, not operator actions.

Refactored the handler to build the response in a single binding so
all three exit paths (no API key, LLM failed, LLM succeeded) flow
through the same log_audit call.

New integration test triages a fake VPS as an operator, then GETs
/api/audit-logs as admin and verifies action/resource/resource_id/
user_email/details all match.

Docs: docs/ai-triage.{en,zh}.md gain an "Audit logging" subsection
documenting the schema and the deliberate exclusions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lai3d lai3d merged commit 464b7a2 into main May 20, 2026
1 check passed
@lai3d lai3d deleted the claude/triage-audit-log branch May 20, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant