Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/docs/features/compliance-audit.md
T

7.0 KiB

Compliance-Audit-Export

Phase 10 Task #67. Tamper-evident, SOC2/HIPAA/GDPR-ready audit log with a self-service export UI for tenants and a system-wide export for platform admins.

Overview

AuditEvent (prisma/schema/core.prisma) is the single normative store for privileged actions across the platform. Phase 10 extended the model with:

Field Purpose
actorEmail denormalized actor email for compliance reports
actorIp first-hop client IP (cf-connecting-ip / x-real-ip / xff)
actorUserAgent UA capped at 512 chars
sourceModule auth / billing / admin / plugin:foo
outcome success / failure / blocked
dataSensitivity public / internal / confidential / pii
regulatoryScope string[]: gdpr hipaa soc2 pci
previousHash predecessor eventHash for the same tenant chain
eventHash HMAC-SHA-256 over canonical payload + previousHash

Existing fields (tenantId, actorId, actorType, action, targetType, targetId, requestId, metadata) remain unchanged.

Hash-Chain

Per-tenant chain: each tenant's events form an independent linked-list keyed by previousHash. Platform-level events (tenantId IS NULL) form their own chain.

eventHash_n = HMAC-SHA-256(AUDIT_CHAIN_SECRET, eventHash_{n-1} || canonicalJSON(payload_n))

canonicalJSON is the JSON encoding produced by canonicalizeAuditPayload in packages/db/src/audit-hash.ts: object keys sorted recursively, undefined dropped, arrays preserve order. Production fails closed unless AUDIT_CHAIN_SECRET contains at least 32 UTF-8 bytes. Local development and tests may use the explicit SHA-256 fallback, but it is never an accepted production configuration. New v2: hashes bind the persisted event ID and creation timestamp; the verifier retains dual compatibility with unversioned legacy hashes. Concurrent writers serialize through a per-chain PostgreSQL advisory transaction lock and an HMAC-authenticated O(1) chain-head row.

verifyAuditChain(events, { secret }) walks the chain oldest → newest and returns the first break with one of:

  • previous-hash-mismatch — chain discontinuity (an event was inserted or deleted)
  • hash-mismatch — payload was modified in-place

Auto-Capture

API (Hono)

apps/api/src/middleware/audit-context.ts registers an AsyncLocalStorage binding on every request and exposes getAuditContext(). Deeper code (createAuditEvent calls in services) automatically picks up actor IP, user agent, request id without threading the request object.

import { createAuditContextMiddleware } from "./middleware/audit-context.js";
app.use("*", createAuditContextMiddleware());

Web (React-Router v7)

apps/web/app/utils/audit/withAuditContext.ts mirrors the API helper for loaders/actions:

export const action = async (args: ActionFunctionArgs) =>
  withAuditContext(args, async () => {
    // ...createAuditEvent calls deeper in the stack pick up actor ctx
  });

For ad-hoc reads extractWebActorIp(request) and extractWebActorUserAgent(request) are exported too.

Export UI

Two routes ship:

  • /admin/audit-export — platform admin, system-wide. Optional tenantId filter.
  • /app/$tenant/settings/audit-export — tenant self-service. tenantId is force-applied server-side; query-string spoofing is silently dropped.

Both stream a CSV up to 50 000 rows (AUDIT_EXPORT_MAX_ROWS). The first line of the CSV is the verifyAuditChain integrity header so the download itself is the auditor evidence package:

# verifyAuditChain: ok=true inspected=12345 firstBreakId=- reason=-
id,createdAt,tenantId,actorType,actorId,…

The HTTP response also carries an X-Audit-Chain-Status: ok|broken header so a programmatic client can branch without parsing the CSV.

Filters:

  • from / to (ISO date, inclusive UTC range; max 1 year recommended)
  • regulatoryScope (multi)
  • actorType (user / admin / api_key / system / plugin)
  • outcome (success / failure / blocked)
  • tenantId (admin variant only)

CSV cells with commas, quotes or newlines are RFC-4180 escaped; arrays join with |; JSON metadata is stringified.

Retention

apps/worker/src/jobs/audit-retention.ts exposes runAuditRetentionJob which the worker host wires to a daily cron.

The retention window is fixed at ten calendar years for every tenant chain and the platform chain. Tenant discovery uses one aggregate query. Deletes run in bounded, keyset-ordered batches; before each batch is deleted, the same transaction writes an append-only HMAC-authenticated checkpoint that binds the last removed event hash and the previous checkpoint MAC. Verifiers and exports authenticate this checkpoint before accepting the retained suffix.

Every non-empty sweep emits a meta-audit (audit.retention.pruned) so the cleanup itself is traceable.

Operator Procedures

Bootstrap

  1. Generate a secret: openssl rand -base64 32.
  2. Set AUDIT_CHAIN_SECRET=<value> in Coolify (platform-wide, never per-tenant).
  3. Apply the migration that creates AuditChainHead and AuditChainCheckpoint before deploying the new writers.

Self-service export (tenant admin)

  1. Navigate to Settings → Audit Export.
  2. Pick date range + filters.
  3. Click Download CSV.
  4. Verify the first line shows ok=true. If ok=false, contact platform support — chain integrity has been compromised.

Forensic verify (platform admin)

# In a node REPL with the export CSV:
const { verifyAuditChain } = await import("@hivegrid/db/audit-hash");
// load events from CSV/DB, then:
verifyAuditChain(events, { secret: process.env.AUDIT_CHAIN_SECRET });

Tampering detection drill

psql ... -c "UPDATE \"AuditEvent\" SET metadata = '{\"tampered\":true}' WHERE id = '<id>'"

Re-export the tenant — the integrity header now reports ok=false reason=hash-mismatch firstBreakId=<id>.

Roadmap

  • Admin per-tenant retention-policy override JSON
  • S3-glacier auto-archive before retention delete
  • Pre-built SOC2 / GDPR / HIPAA report templates (PDF with signature hash)
  • CLI: pnpm audit:verify-chain --tenant <id> direct DB walker
  • Outbox-driven audit writes for transactional consistency with business writes (Phase 03 #42 integration)
  • prisma/migrations/20260517180000_10_audit_event_compliance/
  • packages/db/src/audit-hash.ts — chain helpers
  • packages/db/src/audit-events.server.ts — single write-path
  • apps/api/src/middleware/audit-context.ts — API auto-capture
  • apps/web/app/utils/audit/withAuditContext.ts — web auto-capture
  • apps/web/app/routes/admin/audit-export.tsx — admin export UI
  • apps/web/app/routes/app.$tenant/settings/audit-export.tsx — tenant export UI
  • apps/worker/src/jobs/audit-retention.ts — retention worker