Skip to main content
Version: Next — legacy draft snapshot

Audit logging

Retained Next source

This page preserves the merged Next documentation snapshot at e1ddcf6940adb7b619ce509063749e740eecc795. It can describe implementation beyond the source revision selected for the rebuilt Next guides. Retain the implementation-scope notes below and confirm your deployed version before applying these details. This is not a current-release availability or deployment claim. For the task-based entry point, see the rebuilt guide.

Next implementation scope

The transaction fields and explicit security-event tagging described below refer to platform PR 554 (internal implementation reference), reviewed at commit 300e8a9956ada4ac628fd5fceda13855510a73b0. This scope note concerns explicit security tagging, not the separate direct-write audit-export fix in platform PR 551 (internal implementation reference). Confirm each implementation independently. These changes are not a statement that the current released platform or a customer deployment contains them. Confirm the deployed implementation before relying on these fields; source checks do not establish live ingestion or delivery.

Delphi records privileged and security-relevant actions as audit rows so operators can reconstruct who did what, from where, and when. Every row carries the per-event fields required by BSR 4.1.1 / 4.1.2: timestamp, event type, the origin IP address, the actor's user ID, and (where applicable) a human-readable name and a self-describing changedBy block.

This page describes the data model and the events the platform emits. For the Super Admin UI that reads these rows, see Access logs; for forwarding them to an external SIEM, see Security event export.

Where audit rows land​

Web audit records are persisted in the AuditLog table and exported with the case-sensitive attribute recordKind: Audit. The entry point determines how persistence and export are coordinated:

PathEntry pointPersistence and export
Managed-resource auditlogAudit → AuditLogFactory / AuditLoggingService / DatabaseLoggingStrategyShared logger writes the DB audit and emits to SigNoz. This path is not DB-only.
Security/auth eventlogSecurityEvent / logAuthShared logger writes the DB audit and emits to SigNoz.
Direct Web audit writePassword reset, platform-role management, user/registration/team routesExports the persisted record after the DB write succeeds; records written inside a transaction export only after commit. Export preserves the original timestamp and exposes the persisted row ID as auditLogId; it does not insert another DB row.

The direct-write coverage below belongs to the VGWK-126 / SCRUM-801 fix. Confirm that your deployment includes that fix before expecting these events in SigNoz; this page does not establish which customer environment has received it. Earlier builds can contain the DB record without its SigNoz export.

The logger uses canonical OTLP delivery when enabled and a stdout mirror that the collector deduplicates. When direct OTLP logging is disabled, stdout is the canonical path. Export is best effort, not a durable DB outbox: a successful user operation does not fail because telemetry delivery fails, and a crash or transport failure can leave a committed DB audit without a SigNoz record. There is no automatic replay or historical backfill. Rollbacks do not export the direct-write transaction's audit records.

Web event coverage​

These are action/category pairs from Web call sites, not a promise that every declared logger constant has an active producer. Both action and category values are case-sensitive.

Web eventActionCategoryExport path
Sign-in, failed sign-in, sign-out (including Microsoft SSO)LOGIN, LOGIN_FAILED, LOGOUTAUTHExisting shared logger
Automatic lockoutACCOUNT_LOCKEDAUTHExisting shared logger
Password changePASSWORD_CHANGEAUTHExisting shared logger; see naming note below
Password reset request and completionPASSWORD_RESET_REQUESTED, PASSWORD_RESET_SUCCEEDEDAUTHDirect-write fix
Unlock through password resetACCOUNT_UNLOCKEDAUTHDirect-write fix
Platform-role lifecyclePLATFORM_ROLE_CHANGE_STARTED, PLATFORM_ROLE_CHANGE_SUCCEEDED, PLATFORM_ROLE_CHANGE_DENIED, PLATFORM_ROLE_SESSION_CLEANUP_FAILEDROLE_MANAGEMENTDirect-write fix
Team membership and role changesCREATE, UPDATE, DELETEROLE_MANAGEMENTExisting team-management logger; direct-write fix for primary UPDATE audit through user editing
Additional team-role security eventPRIVILEGE_ESCALATION_ATTEMPTSECURITYExisting shared logger; SECURITY category, separate from primary ROLE_MANAGEMENT audit
User administration and profile changesCREATE, UPDATE, DELETEUSERDirect-write fix
InvitationsTEAM_INVITATION_SENT, WELCOME_INVITATION_SENTUSERDirect-write fix
Registration email verificationEMAIL_VERIFICATION_FAILED, EMAIL_VERIFICATION_SUCCEEDED, EMAIL_VERIFICATION_RECOVEREDREGISTRATIONDirect-write fix
Team deletionDELETETEAMDirect-write fix
Rejected unauthenticated requestREJECTED_CONNECTIONSYSTEMExisting shared logger
Forbidden request / security-log access / security-policy changeACCESS_DENIED, SECURITY_LOGS_ACCESSED, SECURITY_POLICY_MODIFIEDSECURITYExisting shared logger
Managed-resource changesCREATE, UPDATE, DELETE as supported by each resourceAPP, FLOW_DEFINITION, ENDPOINT, BASENUMBER, SIP_TRUNK, EGRESS_TRUNK, EGRESS_ROUTE, PLATFORM_SECRET, PLATFORM_TOOL, TEAM_TOOLExisting logAudit
Session cleanup failure during password reset/changeSESSION_ENDAUTHExisting shared logger; details.reason is password_reset_session_cleanup_failed or password_change_session_cleanup_failed
Session-slot cleanup failure in the shared cleanup helper used by Web deletionSESSION_SLOT_CLEANUP_FAILEDSECURITYDirect-write fix; emitted if session tracking cleanup fails after the state transition commits

Configuration backups also export CREATE / CONFIG_BACKUP; restore failures export UPDATE / CONFIG_RESTORE_FAILURE. Successful restores use the affected resource categories and their change actions. A restore-failure event is emitted outside the failed transaction and describes the failure; it is not evidence that the resource changes committed.

Password-change naming: the password-change flow already emits AUTH / PASSWORD_CHANGE through the shared logger. It also stores a transaction-specific DB audit named PASSWORD_CHANGED. Do not search only for PASSWORD_CHANGED to verify the existing export, and do not treat the two names as interchangeable. The fix preserves the existing PASSWORD_CHANGE export without adding a second password-change export for that DB row.

CLI recovery and background-job audits are separate from this Web coverage table. Declaring an action constant does not guarantee export from those paths.

Per-event fields (BSR 4.1.1 / 4.1.2)​

Each row has top-level columns (indexed, used for filtering) and a JSON details block (the self-describing payload).

Top-level columns​

ColumnSourceNotes
timerow creation timestampUTC. Satisfies the BSR time / date requirement.
actionthe event type (LOGIN_FAILED, PRIVILEGE_ESCALATION_ATTEMPT, …)Satisfies the BSR type of event requirement. See the action catalog.
userIdthe actor's session user id, or 'anonymous' / 'unknown'Satisfies the BSR user ID requirement. Anonymous for failed-auth paths.
usernamethe actor's full name ("First Last"), falling back to emailHuman-readable actor. Consistent across all security/CRUD call sites.
categoryAUTH, ROLE_MANAGEMENT, SECURITY, SYSTEM, …Groups events in the UI and SIEM routing.
entityId / appIdthe affected resource, when applicableUsed for resource-scoped correlation.

details block​

The details JSON object carries the self-describing payload. The actor-context helper injects the security-relevant fields:

FieldMeaningInjected by
ipOrigin IP of the request (x-forwarded-for first value, else x-real-ip). BSR IP address of origin.getActorDetails(ctx)
userAgentOrigin User-Agent header.getActorDetails(ctx)
emailActor's email.getActorDetails(ctx)
userNameActor's full name ("First Last", or email if names are missing, or "unknown").getActorDetails(ctx)
changedBy{ userId, name, email } — self-describing who performed the change, for SOC escalation.getActorDetails(ctx)
targetUserIdThe impacted user's id for role-management rows — the person whose role changed, distinct from the actor.getTargetDetails(db, userId)
targetNameImpacted user's full name ("First Last", or email if names are missing, or "unknown").getTargetDetails(db, userId)
targetEmailImpacted user's email.getTargetDetails(db, userId)
old / newBefore/after snapshot for CRUD rows.the call site
reasonFree-text reason for denials / failures.the call site

MSISDN is recorded in the details block only for telephony-side events; the web/admin audit surface does not carry one (BSR MSISDN is N/A there).

Actor context and origin injection​

Call sites do not assemble email / userName / changedBy / ip / userAgent by hand. They spread getActorDetails(ctx) into the details object and the helper fills them in:

import {getActor, getActorDetails} from '@delphi/api/services/logging/actorContext';

logSecurityEvent(
'PRIVILEGE_ESCALATION_ATTEMPT',
{id: ctx.session.user.id, username: getActor(ctx)?.userName ?? undefined},
{
targetUserId: input.userId,
oldRole: teamUser.role,
newRole: input.role,
...getActorDetails(ctx), // email, userName, changedBy, ip, userAgent
},
'info',
);

getActorDetails reads the origin even for unauthenticated contexts, so failed-login, rejected-connection, and access-denied rows still carry the requesting IP and User-Agent per BSR 4.1.2 — there is no actor in those paths, but there is always an origin.

The origin is derived from request headers by getRequestMeta:

  • ip — first value of x-forwarded-for, else x-real-ip
  • userAgent — the user-agent header
  • requestId — x-request-id or x-correlation-id (carried for correlation, not a BSR field)
note

getActorDetails lives in @delphi/api and reads headers from the tRPC ctx. The @delphi/auth package carries its own equivalent (getRequestMeta) used by the authentication handlers; both derive the origin the same way.

Role-management and user rows also record the target — the person whose team membership, role, or account changed — via the target-context helpers (getTargetDetails(db, userId) for a fresh lookup, targetDetailsFromUser(user, userId) for an already-fetched row), which inject targetUserId / targetName / targetEmail into details. This keeps the impacted user distinct from the actor: the top-level userId/username columns and the changedBy block describe who performed the change, while the target* fields describe who was affected.

What is audited (BSR scope)​

The platform audits the security-event scope defined for Delphi. The table maps each scope item to the action emitted and where the origin / actor come from.

ScopeEventActionOrigin / actor
bFailed loginLOGIN_FAILEDIP/UA from the auth handler; user id of the attempted account (or anonymous).
cPrivilege escalation / any role changePRIVILEGE_ESCALATION_ATTEMPT, PLATFORM_ROLE_CHANGE_*getActorDetails(ctx) for the actor + getTargetDetails(db, userId) for the impacted user, on every role change, not only escalations to ADMIN.
dRejected connection (unauthenticated request to a protected op)REJECTED_CONNECTIONtRPC securityAuditMiddleware — getActorDetails(ctx) injects IP/UA even for anonymous requests.
eAccess restriction violation (authenticated but forbidden)ACCESS_DENIEDSame middleware — actor (if any) + IP/UA.
fIgnore / done (access granted, no event)—Successful authorization produces no audit row by design.
hAccess to security logsSECURITY_LOGS_ACCESSEDgetActorDetails(ctx) — the Super Admin viewing the audit feed is itself audited with actor + IP/UA.
iSecurity policy modification + SIP trunk modificationsSECURITY_POLICY_MODIFIED, CRUD UPDATE on SIP/egress trunksgetActorDetails(ctx) — actor full name + changedBy + IP/UA for both platform settings and trunk changes.

Authentication successes and account-lockout events (VGWK-90 / BSR 2.4.7) are also recorded: LOGIN, LOGOUT, SESSION_END, PASSWORD_CHANGE, PASSWORD_RESET_REQUESTED / _SUCCEEDED, ACCOUNT_LOCKED / _UNLOCKED.

Action catalog​

The broader action catalog includes logger constants and events outside the Web surface. Use the Web event coverage table above to check implemented Web export paths; this catalog alone is not export verification.

@delphi/logger defines the action constants (AuditActionTypes) and the audit categories (AuditCategories). The last column is the optional security tag for filtered external forwarding, not whether the record can reach SigNoz. It is described in Security event export:

ActionCategorysecurity_categoryWhen
LOGIN / LOGIN_FAILEDAUTHauthenticationSuccessful / failed sign-in.
LOGOUT / SESSION_ENDAUTHauthenticationSign-out / session timeout or admin-forced end.
PASSWORD_CHANGEAUTHauthenticationPassword changed (self or admin).
PASSWORD_RESET_REQUESTED / _SUCCEEDEDAUTHauthenticationReset flow stages.
ACCOUNT_LOCKED / ACCOUNT_UNLOCKEDAUTHauthenticationAuto-lockout after threshold failures / unlock on reset.
REJECTED_CONNECTIONSYSTEMauthorizationUnauthenticated request rejected by a protected procedure.
ACCESS_DENIEDSECURITYauthorizationAuthenticated request forbidden (insufficient role, expired password, Super Viewer write).
PRIVILEGE_ESCALATION_ATTEMPTSECURITYauthorizationAdditional security event for a team-role change attempt; primary audits use ROLE_MANAGEMENT.
PLATFORM_ROLE_CHANGE_STARTED / _SUCCEEDED / _DENIEDROLE_MANAGEMENTauthorizationPlatform-wide role grant/change/revoke lifecycle.
PLATFORM_ROLE_SESSION_CLEANUP_FAILEDROLE_MANAGEMENTauthorizationRole change applied, but clearing the target's Redis session tracking failed.
SECURITY_LOGS_ACCESSEDSECURITYauditSuper Admin opened the Access logs feed.
SECURITY_POLICY_MODIFIEDSECURITYauditA platform security setting was changed.
SYSTEM_MANIPULATION_ATTEMPTSECURITYauditTamper / manipulation attempt detected.
SUPER_ADMIN_RECOVERY_ELEVATED / _DENIEDSECURITYauthorizationBreak-glass elevation by the setup/recovery command.
PASSWORD_RESET_BY_SETUP_CLIAUTHauthenticationAudited Standard User recovery performed by the setup CLI.
CREATE / UPDATE / DELETE(per resource)— (not explicitly security-tagged)CRUD on managed resources via logAudit.
category and security_category are different vocabularies

category is the audit vocabulary — AUTH, ROLE_MANAGEMENT, FLOW, NUMBER, SECURITY, SYSTEM. It is what this page and the Access logs UI group by. security_category is the export vocabulary — authentication, authorization, audit, certificate — and it is what an external SIEM receives. Do not conflate them: SUPER_ADMIN_RECOVERY_* is SECURITY in one and authorization in the other, by design. The export column is sourced from packages/logger/src/security-classification.ts, which is the single source of truth for it; a test pins it against the collector's own literals so the two cannot drift.

Searching and displaying rows​

The Access logs UI searches across both the top-level username column and the details block (details.email, details.actor.email, details.targetEmail, details.ip, details.msisdn). Changing a row's top-level username to a full name therefore does not break email-based search — the email remains searchable through details.email, which getActorDetails injects.

The display column prefers details.userName (the full name) and falls back to the top-level username, then details.email, so the UI shows a human-readable name regardless of which field the call site populated.

For role-management and user rows, the User column renders the impacted user from the target* fields (name, falling back to email), with the user ID shown as a caption beneath. The acting administrator is shown in Details as Changed by: <name> (<email>), normalised from details.changedBy (or the legacy details.actor block on platform-role rows), so a role grant/change/revocation always names both who was affected and who acted. Rows recorded before the target identity was written still resolve the impacted user's name and email from the stored entityId at read time.

Find and verify an event in SigNoz​

  1. Generate a fresh event in the deployment under test. Record its UTC time, action, actor and affected resource; use a test account for password or role workflows.
  2. Open SigNoz Logs and select a time window covering that operation.
  3. Add an attribute filter recordKind equals Audit. Some views display this key as attributes.recordKind. Do not use lowercase audit for this value (logType is a different field).
  4. Add action equals the exact uppercase action from the table, for example PASSWORD_RESET_SUCCEEDED.
  5. Narrow by category equals AUTH (or ROLE_MANAGEMENT, USER, etc.), then userId or entityId if available. Start without actor filters for anonymous registration and reset requests.
  6. Expand a matching record. Compare action, category, actor, target and time with the DB audit. Direct-write exports also include auditLogId for matching the persisted row. Treat details as structured event data, not the authoritative name of the event; redaction can change sensitive fields.
  7. For role changes, check lifecycle stages individually. A committed denial is an audit event; it does not imply that the role changed. A rolled-back transaction should not produce its direct-write export.

For a reset-completion search, configure these three equality filters together:

AttributeValue
recordKindAudit
categoryAUTH
actionPASSWORD_RESET_SUCCEEDED

Missing or unexpected results​

SymptomCheck
Only login/logout events appearConfirm the deployed Web version includes the direct-write fix; older builds omit some DB-written events. Remove action/category filters before checking all audit records.
Password change cannot be foundSearch PASSWORD_CHANGE, not only PASSWORD_CHANGED or lowercase password_change.
Event exists in DB but not SigNozCheck the operation time, SigNoz retention, service/actor filters and collector/OTLP health. Export is best effort; existing DB rows are not automatically replayed.
No DB audit existsCheck whether the workflow reached an audited stage or its transaction rolled back. Export cannot create a committed audit for a rolled-back transaction.
Repeated role-change-looking recordsCompare exact actions: a primary UPDATE, a secondary PRIVILEGE_ESCALATION_ATTEMPT, and platform-role lifecycle stages represent distinct audit records.
Identical transport copies appearInspect audit.delivery_role and log_transport; confirm the deployed collector removes stdout mirrors when direct OTLP is canonical.
Sensitive values differ from stored dataExport uses the shared redaction path. Do not disable redaction merely to make a search match.

See Monitoring in SigNoz and SigNoz operations for transport troubleshooting. Saved example logs can confirm field names and known event shapes, but do not prove that another category or the current deployment exports successfully; verify with a fresh workflow.

Retention and export​

  • Retention — Tasker prunes audit rows older than 180 days by default (AUDIT_LOG_RETENTION_DAYS); see Ops service.
  • SIEM export — Exported Web audit records can be fanned out to an external SIEM; see Security event export.

See also​