Context
Surfaced while planning AU-07 (#40), whose route is PATCH /api/v1/tenants/:tenantId/roles/:role/scopes. That endpoint cannot be auto-audited, and the reason generalises to most of the documented API.
AuditAutoInterceptor.resolveResourceId (apps/digital-trust-common-service/src/audit-log/audit-auto.interceptor.ts:145-152) reads only request.params.id:
private resolveResourceId(request: Request): string | null {
const params = request.params ?? {};
if (typeof params.id === 'string' && params.id) {
return params.id;
}
return null;
}
Any mutating route whose resource param has a different name returns null, and enqueueSafe (line 104-110) drops the request with a debug log and no audit row.
Current state
Not a live gap. AUDIT_AUTO_INTERCEPTOR_ENABLED is "false" in charts/digital-trust-common-service/values.yaml:254 and .env.example:147, with no environment overriding it. All 21 mutating routes in the repo today use :id or take no path param, so the interceptor resolves correctly for every route that currently exists.
The exposure is in the API shape already specified in docs/openapi.yaml, which uses named params rather than {id}:
| Path |
Param |
Ticket |
/tenants/{tenantId}/users/{userId} |
userId |
TM-02 (#44) |
/tenants/{tenantId}/connectors/{connectorId} |
connectorId |
TM-07 (#49) |
/tenants/{tenantId}/credential-definitions/{credDefId} |
credDefId |
CA-02 |
/tenants/{tenantId}/credentials/{exchangeId}/accept |
exchangeId |
CA-05 |
/tenants/{tenantId}/credentials/{credentialId}/revoke |
credentialId |
CA-07 |
/tenants/{tenantId}/connections/{connectionId} |
connectionId |
CA-06 |
/tenants/{tenantId}/operations/{operationId} |
operationId |
AG-02 (#76) |
/tenants/{tenantId}/webhooks/{webhookId} |
webhookId |
AG-05 (#79) |
/tenants/{tenantId}/clients/{clientId}/rotate-secret |
clientId |
AU-06 (#39) |
/tenants/{tenantId}/roles/{role}/scopes |
role |
AU-07 (#40) |
None of these would produce an audit row with the interceptor enabled.
The skip is logged at debug (line 106), so enabling the flag yields audit rows for :id routes while credential revocation, client secret rotation, and role/scope changes produce nothing, with no signal that coverage is incomplete.
Work
Notes
actorId is hardcoded to 'system' / AuditActorType.SYSTEM (line 114-115). Once JWT guards roll out (#165) the authenticated principal is available on req.auth, so attribution could be real rather than synthetic. Separate concern, same function; splitting it out if it needs its own discussion.
Filed as Post-MVP: the interceptor ships disabled and no route in the repo today is affected, so this only needs to land before the flag is turned on or before the first named-param controller is written, whichever comes first.
AU-07 (#40) does not depend on this. It writes its audit entry explicitly and marks the controller @SkipAutoAudit().
Related: #142 (interceptor implementation), #141 (domain-level audit.write producers), #27 (PE-04 audit log schema).
Context
Surfaced while planning AU-07 (#40), whose route is
PATCH /api/v1/tenants/:tenantId/roles/:role/scopes. That endpoint cannot be auto-audited, and the reason generalises to most of the documented API.AuditAutoInterceptor.resolveResourceId(apps/digital-trust-common-service/src/audit-log/audit-auto.interceptor.ts:145-152) reads onlyrequest.params.id:Any mutating route whose resource param has a different name returns
null, andenqueueSafe(line 104-110) drops the request with adebuglog and no audit row.Current state
Not a live gap.
AUDIT_AUTO_INTERCEPTOR_ENABLEDis"false"incharts/digital-trust-common-service/values.yaml:254and.env.example:147, with no environment overriding it. All 21 mutating routes in the repo today use:idor take no path param, so the interceptor resolves correctly for every route that currently exists.The exposure is in the API shape already specified in
docs/openapi.yaml, which uses named params rather than{id}:/tenants/{tenantId}/users/{userId}userId/tenants/{tenantId}/connectors/{connectorId}connectorId/tenants/{tenantId}/credential-definitions/{credDefId}credDefId/tenants/{tenantId}/credentials/{exchangeId}/acceptexchangeId/tenants/{tenantId}/credentials/{credentialId}/revokecredentialId/tenants/{tenantId}/connections/{connectionId}connectionId/tenants/{tenantId}/operations/{operationId}operationId/tenants/{tenantId}/webhooks/{webhookId}webhookId/tenants/{tenantId}/clients/{clientId}/rotate-secretclientId/tenants/{tenantId}/roles/{role}/scopesroleNone of these would produce an audit row with the interceptor enabled.
The skip is logged at
debug(line 106), so enabling the flag yields audit rows for:idroutes while credential revocation, client secret rotation, and role/scope changes produce nothing, with no signal that coverage is incomplete.Work
tenantIdroute param, or read an explicit param name from route metadata (e.g.@AuditResource('credentialId')).debugtowarnso missing coverage is visible when the flag is on.:id, named-param, and no-param routes.Notes
actorIdis hardcoded to'system'/AuditActorType.SYSTEM(line 114-115). Once JWT guards roll out (#165) the authenticated principal is available onreq.auth, so attribution could be real rather than synthetic. Separate concern, same function; splitting it out if it needs its own discussion.Filed as Post-MVP: the interceptor ships disabled and no route in the repo today is affected, so this only needs to land before the flag is turned on or before the first named-param controller is written, whichever comes first.
AU-07 (#40) does not depend on this. It writes its audit entry explicitly and marks the controller
@SkipAutoAudit().Related: #142 (interceptor implementation), #141 (domain-level
audit.writeproducers), #27 (PE-04 audit log schema).