Skip to content

critical: admin audit-log endpoints have no role gate — any authenticated wallet can read full audit trail #115

Description

@EmeditWeb

Problem

AuditController (src/modules/admin/audit.controller.ts) is protected by @UseGuards(JwtAuthGuard) only (line 12). There is no RolesGuard, no role decorator, and no admin allowlist anywhere on the /admin controller. The JwtAuthGuard (src/common/guards/jwt-auth.guard.ts) merely validates the JWT signature and returns { wallet } — it performs zero authorization.

Consequence: any user who completes wallet sign-in once can call GET /admin/audit-logs and read the entire immutable audit trail: every actor wallet, every action, every resource touched across the platform. Audit logs exist precisely to record sensitive administrative activity; exposing them to all authenticated users is both an information-disclosure incident and a reconnaissance goldmine for attackers mapping internal actions before escalating.

The same gap applies to anything else mounted under src/modules/admin — the guard is on the class, not the capability.

Ground Rules

This issue must be solved according to StepFi's established engineering standards. Before writing any code:

  1. Read context/architecture-context.md in full
  2. Read context/code-standards.md in full
  3. Read context/progress-tracker.md to understand what has already been built and why, so you do not duplicate or contradict recent work
  4. Read src/modules/auth/auth.service.ts (generateTokens, lines 160–184) to understand how the role claim enters the JWT
  5. Read src/auth/guards/roles.guard.ts and src/common/guards/jwt-auth.guard.ts

Your PR will be rejected regardless of whether CI passes if it conflicts with anything in these files or introduces patterns inconsistent with what already exists.

What To Build

  1. Create an AdminGuard that authorizes against the server-side user record (fetch role/status fresh from Supabase by req.user.wallet) rather than trusting the JWT role claim alone — tokens minted before a role grant/revoke stay valid until refresh.
  2. Apply it to the entire /admin controller tree.
  3. Deny by default: users whose role is not explicitly admin (and status !== 'blocked') receive ForbiddenException with a structured code such as ADMIN_FORBIDDEN.
  4. Log denied attempts through the existing audit interceptor so brute-force probing is visible.
  5. Tests: non-admin wallet gets 403; admin wallet gets 200; blocked admin gets 401/403; unauthenticated gets 401; stale-token-with-role-claim-but-revoked-user gets 403.

Files To Touch

  • src/modules/admin/admin.module.ts
  • new src/modules/admin/admin.guard.ts
  • src/modules/admin/audit.controller.ts
  • test files under the repo's established test layout
  • context/progress-tracker.md equivalent documentation if present in this repo's docs

Acceptance Criteria

  • No /admin/* route is reachable without explicit server-side admin authorization
  • Authorization does not rely solely on JWT claims
  • Denied attempts are auditable
  • All existing tests still pass; new tests cover every rejection branch

Mandatory Checks Before Opening PR

  • Code follows existing NestJS conventions in this repo exactly
  • Build passes (npm run build) and lint passes
  • New tests written; suite green
  • PR template filled out completely
  • PR references this issue number exactly

PRs that fail any check above will be closed without review. This is a Grantfox-funded, quality-first project.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions