Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m44s
CI / Build container images (pull_request) Successful in 4m42s
CI / Auth e2e pack (pull_request) Successful in 9m15s
CI / Import/export fidelity gate (pull_request) Successful in 59s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
For perimeters that authenticate before the application (ADR 0021 §4). Off unless BOTH AUTH_PROXY_HEADER and AUTH_PROXY_TRUSTED_PEERS are set — nothing about the header is guessed. The peer check runs against the TCP peer address only (a forwarded header is attacker-influenced): a request carrying the header from any other peer is rejected outright and audited as auth.proxy_rejected (catalogue v1.4) — that is a spoof attempt, not a misconfiguration — even when a valid session cookie rides along. From a trusted peer the header IS the identity; a session cookie never escalates beyond it; with the feature off the header is inert. Mapping is explicit (AUTH_PROXY_MAP: username or e-mail); deliberately no just-in-time creation — the header carries no verified address. The mTLS variant (AUTH_PROXY_MODE=mtls-dn) maps the configured attribute (default CN) out of the certificate subject DN the TLS terminator forwards, under the same peer rules. Session-less proxy requests key the read trail per user (user:<id>). The trust boundary is stated in security.md (the section an assessor reads closest), the VS-NfD security documentation and the hardening guide's deploy table. Tests cover all four decisions: off = inert, trusted peer authenticates (username and DN mapping), untrusted peer rejected + audited, no escalation past a session cookie. Refs #215. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
67 lines
3.2 KiB
TypeScript
67 lines
3.2 KiB
TypeScript
/**
|
|
* The audit event catalogue (issue #201): every action id the trail may
|
|
* carry, with the severity the stdout line is stamped with. This const is
|
|
* the CODE half of the published catalogue in
|
|
* `docs/architecture/audit-events.md` — `audit-catalogue.test.ts` fails
|
|
* whenever the two drift, so an id cannot be added, renamed, or removed
|
|
* without its documentation moving in the same commit.
|
|
*
|
|
* Compatibility promise (the reason this exists): ids are never repurposed.
|
|
* New events may be added (minor catalogue version); an id that stops being
|
|
* emitted is retired in the catalogue document, its meaning frozen forever —
|
|
* so an operator's SIEM rules survive our releases.
|
|
*/
|
|
export const AUDIT_EVENTS = {
|
|
'api.token_created': { severity: 'info' },
|
|
'api.token_revoked': { severity: 'info' },
|
|
'api.write': { severity: 'info' },
|
|
'audit.pruned': { severity: 'info' },
|
|
'auth.email_verified': { severity: 'info' },
|
|
'auth.identity_linked': { severity: 'notice' },
|
|
'auth.login_failed': { severity: 'warning' },
|
|
'auth.login_succeeded': { severity: 'info' },
|
|
'auth.password_reset': { severity: 'notice' },
|
|
'auth.proxy_rejected': { severity: 'warning' },
|
|
'auth.signup': { severity: 'info' },
|
|
'backup.restore_requested': { severity: 'warning' },
|
|
'backup.run_triggered': { severity: 'info' },
|
|
'backup.settings_changed': { severity: 'notice' },
|
|
'file.integrity_failed': { severity: 'critical' },
|
|
'grant.created': { severity: 'notice' },
|
|
'grant.deleted': { severity: 'notice' },
|
|
'job.triggered': { severity: 'info' },
|
|
'member.added': { severity: 'notice' },
|
|
'member.removed': { severity: 'notice' },
|
|
'member.role_changed': { severity: 'notice' },
|
|
'page.classification_lowered': { severity: 'warning' },
|
|
'page.classification_raised': { severity: 'notice' },
|
|
'plugin.installed': { severity: 'notice' },
|
|
'plugin.mode_set': { severity: 'notice' },
|
|
'plugin.pond_toggled': { severity: 'info' },
|
|
'plugin.uninstalled': { severity: 'notice' },
|
|
'pond.purged': { severity: 'notice' },
|
|
'quota.override_cleared': { severity: 'notice' },
|
|
'quota.override_set': { severity: 'notice' },
|
|
'read_trail.pruned': { severity: 'info' },
|
|
'settings.changed': { severity: 'notice' },
|
|
'setup.admin_created': { severity: 'notice' },
|
|
'setup.completed': { severity: 'info' },
|
|
'setup.preseeded': { severity: 'info' },
|
|
'setup.smtp_stored': { severity: 'info' },
|
|
'user.deleted': { severity: 'notice' },
|
|
'user.disabled_set': { severity: 'notice' },
|
|
'user.pseudonymized': { severity: 'notice' },
|
|
'user.site_admin_set': { severity: 'notice' },
|
|
'user.verification_resent': { severity: 'info' },
|
|
} as const satisfies Record<string, { severity: AuditSeverity }>;
|
|
|
|
/** Severity vocabulary of the catalogue — syslog-inspired, four levels are
|
|
* enough for rule routing (critical pages someone, warning feeds detection,
|
|
* notice is configuration drift, info is lifecycle noise). */
|
|
export type AuditSeverity = 'info' | 'notice' | 'warning' | 'critical';
|
|
|
|
/** A catalogued action id — the ONLY thing {@link AuditService.record}
|
|
* accepts, so an uncatalogued event cannot be emitted (compile-time), and
|
|
* the doc fence keeps the catalogue document in step (test-time). */
|
|
export type AuditAction = keyof typeof AUDIT_EVENTS;
|