Skip to content

[Feature] Audit log primitive for security-relevant actions #217

@Polliog

Description

@Polliog

Feature Description

Introduce a structured audit log that records security-relevant actions performed in the platform: who did what, when, against which target, with what outcome. The default implementation writes to a dedicated PostgreSQL table. The recording API is the same regardless of how the underlying storage is implemented, so future work (immutable storage, external SIEM forwarding) can replace the backend without changing callsites.

Problem/Use Case

Logtide is used to monitor production systems, often by teams with compliance obligations. Today there is no record of administrative actions inside Logtide itself: who created or rotated an API key, who modified a Sigma rule, who accessed a specific log range, who deleted a project. Operators asking "did anyone change this rule recently?" have no answer.

Beyond compliance, an audit log is a good debugging tool: many "weird state" issues are explained by "someone changed a setting last Thursday".

Proposed Solution

A single recording API:

auditLog.record({
  action: AuditAction,
  actor: { type: 'user' | 'apiKey' | 'system'; id: string },
  target: { type: string; id: string },
  outcome: 'success' | 'failure',
  metadata?: Record<string, unknown>,
})

The canonical action namespace (initial set, lives in one file):

  • org.created, org.updated, org.deleted
  • project.created, project.updated, project.deleted
  • apikey.created, apikey.rotated, apikey.revoked
  • user.invited, user.removed, user.role_changed
  • rule.created, rule.updated, rule.deleted
  • pipeline.created, pipeline.updated, pipeline.deleted
  • dashboard.created, dashboard.updated, dashboard.deleted
  • auth.login_succeeded, auth.login_failed, auth.session_revoked
  • data.exported, data.deleted
    Actions are recorded synchronously inside the same transaction as the action itself, where possible. Where not (e.g. login attempts that don't have a transaction), they're recorded asynchronously with at-least-once semantics.

A dashboard view shows the audit log filtered by org, action type, actor, time range — basically a pre-built saved search over the audit table.

Alternatives Considered

  • Use Logtide's own log ingestion as the audit log. Tempting, but problematic: it mixes audit data with application logs, makes retention policies awkward (audit usually needs longer retention than logs), and creates a circular dependency where the audit of the system is stored in the system.
  • External audit log only (forward to SIEM). Doesn't help operators without an external SIEM, and they're a minority of Logtide users.
  • Skip until requested by a paying customer. Adding audit logging retroactively means going through every mutation path and inserting calls. Doing it once at v1.0 is cheaper.

Implementation Details (Optional)

  • Schema: audit_events table with columns id, organization_id, actor_type, actor_id, action, target_type, target_id, outcome, metadata (JSONB), created_at. Indexes on (organization_id, created_at) and (organization_id, action, created_at).
  • Recording uses the current RequestContext to populate organization_id and (where available) actor. Callsites only specify the action and target.
  • Action names are a TypeScript string literal union — typos fail to compile.
  • The audit log is append-only by convention in v1.0 — no UI to delete entries. True immutable storage (WORM) is a downstream concern, gated behind the audit.immutable capability and tackled separately.
  • Retention is configurable but defaults to no automatic deletion. Operators with strict GDPR requirements can configure a retention window per org.
  • A simple GET /api/audit-events endpoint with pagination and filters. No exotic query language needed.

Priority

  • Critical - Blocking my usage of Logtide
  • High - Would significantly improve my workflow
  • Medium - Nice to have
  • Low - Minor enhancement

Target Users

  • Teams with compliance requirements (SOC 2, ISO 27001, GDPR) needing a record of administrative actions
  • Operators debugging "who changed this setting" issues
  • Multi-user organizations wanting accountability and visibility into team actions

Contribution

  • I would like to work on implementing this feature

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions