Skip to main content
Version: 0.9.17 — archived documentation

Audit logging

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​

All audit rows are written to the same AuditLog Prisma table, regardless of which code path produced them. Two write paths feed it:

PathEntry pointSinkUsed for
DB auditlogAudit (@delphi/api → AuditLogFactory / AuditLoggingService / DatabaseLoggingStrategy)AuditLog table onlyCRUD on managed resources — users, teams, apps, endpoints, SIP/egress trunks, platform settings, base numbers, tools, flow definitions.
Security/auth eventlogSecurityEvent / logAuth (@delphi/logger StructuredLogger → createPrismaLogPersistenceSink)AuditLog table and canonical OTLP to SigNozAuthentication (login/logout/password/lockout), rejected connections, access denials, privilege escalation, security-policy changes, security-log access.

Because the security/auth path also ships the record over OTLP, those rows are the ones available for SIEM export and appear as recordKind: Audit in SigNoz. The DB-only logAudit path is queryable through the Access logs UI.

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)
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.

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) — full name + changedBy + IP/UA for 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​

@delphi/logger defines the action constants (AuditActionTypes / SecurityEventAction):

ActionCategoryWhen
LOGIN / LOGIN_FAILEDAUTHSuccessful / failed sign-in.
LOGOUT / SESSION_ENDAUTHSign-out / session timeout or admin-forced end.
PASSWORD_CHANGEAUTHPassword changed (self or admin).
PASSWORD_RESET_REQUESTED / _SUCCEEDEDAUTHReset flow stages.
ACCOUNT_LOCKED / ACCOUNT_UNLOCKEDAUTHAuto-lockout after threshold failures / unlock on reset.
REJECTED_CONNECTIONSYSTEMUnauthenticated request rejected by a protected procedure.
ACCESS_DENIEDSECURITYAuthenticated request forbidden (insufficient role, expired password, Super Viewer write).
PRIVILEGE_ESCALATION_ATTEMPTROLE_MANAGEMENTA team role was changed (any direction); flagged on escalation to ADMIN.
PLATFORM_ROLE_CHANGE_STARTED / _SUCCEEDED / _DENIEDROLE_MANAGEMENTPlatform-wide role grant/change/revoke lifecycle.
SECURITY_LOGS_ACCESSEDSECURITYSuper Admin opened the Access logs feed.
SECURITY_POLICY_MODIFIEDSECURITYA platform security setting was changed.
SYSTEM_MANIPULATION_ATTEMPTSECURITYTamper / manipulation attempt detected.
SUPER_ADMIN_RECOVERY_ELEVATED / _DENIEDSECURITYBreak-glass elevation by the setup/recovery command.
PASSWORD_RESET_BY_SETUP_CLIAUTHAudited Standard User recovery performed by the setup CLI.
CREATE / UPDATE / DELETE(per resource)CRUD on managed resources via logAudit.

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.

Retention and export​

  • Retention — Tasker prunes audit rows older than 180 days by default (AUDIT_LOG_RETENTION_DAYS); see Ops service.
  • SIEM export — Security/auth rows are also shipped over OTLP and can be fanned out to an external SIEM; see Security event export.

See also​