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:
- Read context/architecture-context.md in full
- Read context/code-standards.md in full
- Read context/progress-tracker.md to understand what has already been built and why, so you do not duplicate or contradict recent work
- Read
src/modules/auth/auth.service.ts (generateTokens, lines 160–184) to understand how the role claim enters the JWT
- 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
- 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.
- Apply it to the entire
/admin controller tree.
- Deny by default: users whose role is not explicitly
admin (and status !== 'blocked') receive ForbiddenException with a structured code such as ADMIN_FORBIDDEN.
- Log denied attempts through the existing audit interceptor so brute-force probing is visible.
- 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
Mandatory Checks Before Opening PR
PRs that fail any check above will be closed without review. This is a Grantfox-funded, quality-first project.
Problem
AuditController(src/modules/admin/audit.controller.ts) is protected by@UseGuards(JwtAuthGuard)only (line 12). There is noRolesGuard, no role decorator, and no admin allowlist anywhere on the/admincontroller. TheJwtAuthGuard(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-logsand 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:
src/modules/auth/auth.service.ts(generateTokens, lines 160–184) to understand how theroleclaim enters the JWTsrc/auth/guards/roles.guard.tsandsrc/common/guards/jwt-auth.guard.tsYour 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
AdminGuardthat authorizes against the server-side user record (fetch role/status fresh from Supabase byreq.user.wallet) rather than trusting the JWTroleclaim alone — tokens minted before a role grant/revoke stay valid until refresh./admincontroller tree.admin(and status !== 'blocked') receiveForbiddenExceptionwith a structured code such asADMIN_FORBIDDEN.Files To Touch
src/modules/admin/admin.module.tssrc/modules/admin/admin.guard.tssrc/modules/admin/audit.controller.tscontext/progress-tracker.mdequivalent documentation if present in this repo's docsAcceptance Criteria
/admin/*route is reachable without explicit server-side admin authorizationMandatory Checks Before Opening PR
npm run build) and lint passesPRs that fail any check above will be closed without review. This is a Grantfox-funded, quality-first project.