[VS-NfD] Define a stable event catalogue for syslog/SIEM export #201

Closed
opened 2026-07-30 01:43:36 +02:00 by fable-5 · 1 comment
Collaborator

Plan reference: 20-massnahmenplan.md -> Phase 2 (pulled from roadmap)
ADR: n/a
Effort: L (3–4 AT)
Depends on:

Context

The code part is small — structured JSON on stdout already exists. The
value is a stable catalogue: fixed event ids with documented semantics
and fields, so the operator can write SIEM rules that survive our updates.
Without that contract, every release silently breaks their detection.

Current state

  • apps/api/src/audit/audit.service.ts defines
    AuditEvent.action: string — the doc comment calls it a "stable
    dot-namespaced id" but nothing enforces it; it is a free-form string.
  • 34 distinct actions are in use today (auth.login_failed,
    grant.created, plugin.installed, settings.changed, …) across 37
    call sites in apps/api/src.
  • The service comment states the deliberate boundary: "Content activity
    (pages, files, exports, labels) intentionally stays log-only — the trail
    answers 'who changed access/configuration', not 'who edited what'."

Acceptance criteria

  • The action set becomes a typed union (or equivalent) so an unknown id
    cannot be emitted; the existing 34 ids keep their names.
  • A published catalogue documents per event: id, trigger, severity,
    actor semantics, target semantics, and every field — versioned, with
    a stated compatibility promise (ids are never repurposed).
  • Log output is structured JSON with a stable field set suitable for
    forwarding; the documented forwarding path (container stdout ->
    operator's collector) needs no application-side syslog client.
  • A test fails when an event is emitted that the catalogue does not
    describe — the fence that keeps documentation and code together.
  • The catalogue lives in docs/architecture/security.md or a dedicated
    file referenced from #228 and #230.

Out of scope

An application-side syslog/TLS shipper, log signing, and read events (M6).

**Plan reference:** `20-massnahmenplan.md` -> Phase 2 (pulled from roadmap) **ADR:** n/a **Effort:** L (3–4 AT) **Depends on:** — ## Context The code part is small — structured JSON on stdout already exists. The value is a **stable catalogue**: fixed event ids with documented semantics and fields, so the operator can write SIEM rules that survive our updates. Without that contract, every release silently breaks their detection. ## Current state - `apps/api/src/audit/audit.service.ts` defines `AuditEvent.action: string` — the doc comment calls it a "stable dot-namespaced id" but nothing enforces it; it is a free-form string. - 34 distinct actions are in use today (`auth.login_failed`, `grant.created`, `plugin.installed`, `settings.changed`, …) across 37 call sites in `apps/api/src`. - The service comment states the deliberate boundary: "Content activity (pages, files, exports, labels) intentionally stays log-only — the trail answers 'who changed access/configuration', not 'who edited what'." ## Acceptance criteria - [ ] The action set becomes a typed union (or equivalent) so an unknown id cannot be emitted; the existing 34 ids keep their names. - [ ] A published catalogue documents per event: id, trigger, severity, actor semantics, target semantics, and every field — versioned, with a stated compatibility promise (ids are never repurposed). - [ ] Log output is structured JSON with a stable field set suitable for forwarding; the documented forwarding path (container stdout -> operator's collector) needs no application-side syslog client. - [ ] A test fails when an event is emitted that the catalogue does not describe — the fence that keeps documentation and code together. - [ ] The catalogue lives in `docs/architecture/security.md` or a dedicated file referenced from #228 and #230. ## Out of scope An application-side syslog/TLS shipper, log signing, and read events (M6).
fable-5 added this to the M25 — VS-NfD: hardening & supply chain milestone 2026-07-30 01:43:36 +02:00
fable-5 added the
area:ops
effort:L
vs-nfd
labels 2026-07-30 01:43:36 +02:00
Author
Collaborator

Implemented in PR #261 (commit 000d110, CI run 526 green, fast-forward merged per Stefan's standing go-ahead for the M25 block).

Evidence against the acceptance criteria:

  • Typed union: AUDIT_EVENTS in apps/api/src/audit/audit-actions.ts covers all 36 action ids in use (the issue counted 34; audit.pruned from #196 and file.integrity_failed from #199 joined since); every id keeps its name; an uncatalogued emission is a compile error.
  • Published catalogue: docs/architecture/audit-events.md v1.0 - per event: id, trigger, severity, actor semantics, target semantics, every field; versioned with the stated compatibility promise (ids never repurposed; additions = minor; removing a retired row = major + release notes).
  • Structured JSON with stable field set documented (msg selector 'audit: ', severity, actor, targetType/targetId, per-event fields); forwarding path = container stdout -> operator's collector, deliberately no application-side syslog client. Audit lines now carry the catalogue severity as routing hint (pino level unchanged, no DB migration).
  • Fence: audit-catalogue.test.ts parses the document's event tables and fails on id or severity drift - verified in both directions (fabricated id turned both tests red).
  • Referenced from security.md (Logging) and recorded on #228 and #230.

CD verification follows on the main-push run.

Implemented in PR #261 (commit 000d110, CI run 526 green, fast-forward merged per Stefan's standing go-ahead for the M25 block). Evidence against the acceptance criteria: - Typed union: AUDIT_EVENTS in apps/api/src/audit/audit-actions.ts covers all 36 action ids in use (the issue counted 34; audit.pruned from #196 and file.integrity_failed from #199 joined since); every id keeps its name; an uncatalogued emission is a compile error. - Published catalogue: docs/architecture/audit-events.md v1.0 - per event: id, trigger, severity, actor semantics, target semantics, every field; versioned with the stated compatibility promise (ids never repurposed; additions = minor; removing a retired row = major + release notes). - Structured JSON with stable field set documented (msg selector 'audit: <id>', severity, actor, targetType/targetId, per-event fields); forwarding path = container stdout -> operator's collector, deliberately no application-side syslog client. Audit lines now carry the catalogue severity as routing hint (pino level unchanged, no DB migration). - Fence: audit-catalogue.test.ts parses the document's event tables and fails on id or severity drift - verified in both directions (fabricated id turned both tests red). - Referenced from security.md (Logging) and recorded on #228 and #230. CD verification follows on the main-push run.
Sign in to join this conversation.
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: stwaidele/dorfteich#201
No description provided.