Skip to content

[PE-04-followup] Auto-audit interceptor only resolves :id, silently skipping all named-param routes #192

Description

@Gavinok

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

  • Resolve the resource id for named-param routes: either take the last non-tenantId route param, or read an explicit param name from route metadata (e.g. @AuditResource('credentialId')).
  • Raise the unresolved-resource log from debug to warn so missing coverage is visible when the flag is on.
  • Decide whether coverage should be opt-in per route rather than inferred from param naming.
  • Unit tests for :id, named-param, and no-param routes.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions