Problem
src/modules/admin/audit.controller.ts is decorated @Controller('admin') and guarded with @UseGuards(JwtAuthGuard) only. There is no role check and no admin allowlist. Any wallet that completes the normal nonce-and-signature flow and holds a valid JWT can read the full admin audit log through GET /admin/..., including the record of who performed which privileged action and when.
The root cause is that the API has no concept of an administrator. USER_ROLES in src/modules/users/dto/set-role.dto.ts is ['sponsor', 'vendor', 'mentor']. There is no admin value, no ADMIN_WALLET or equivalent environment variable, and no admin guard anywhere in src/. RolesGuard exists and works, but there is no admin role for it to check.
This blocks other work as well as being a live exposure. Vendor approval, protocol pause, and parameter governance all need an authenticated administrator, and none of them can be built correctly until this exists.
Note the design constraint: roles are wallet-bound and permanent. POST /users/me/role returns 409 once set, per the 2026-07-16 tracker entry. Admin must therefore not be a self-assignable role. If it were, anyone could claim it.
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 in full, especially the 2026-07-16 entry on wallet-bound roles and
RolesGuard, so this extends that design rather than contradicting it
- Read
src/auth/guards/roles.guard.ts to understand the existing role-checking mechanism before adding to it
- Read
src/modules/users/dto/set-role.dto.ts and src/modules/users/users.service.ts to understand how roles are assigned and why they are permanent
- Read
src/config/env.ts for the existing configuration and validation pattern
Your PR will be rejected regardless of whether CI passes if it conflicts with anything in these files, introduces patterns inconsistent with what already exists, or duplicates functionality that was solved differently elsewhere in the codebase.
What To Build
- Add an
ADMIN_WALLETS environment variable holding a comma-separated allowlist of Stellar addresses. Validate at startup in src/config/env.ts that every entry is a well-formed Stellar public key, and fail fast with a clear message if not. Do not hardcode any address in a service file.
- Do not add
admin to USER_ROLES. Roles are self-selected and permanent, so a self-assignable admin role is a privilege escalation. Admin status must derive from the allowlist only.
- Create
src/auth/guards/admin.guard.ts exporting an AdminGuard that resolves the authenticated wallet from the request and checks membership in the allowlist. Follow the structure of RolesGuard.
- Include the admin's JWT wallet address in the guard's rejection log, but return a generic 403 body to the caller. Do not leak the allowlist or its size in the response.
- Apply
@UseGuards(JwtAuthGuard, AdminGuard) to audit.controller.ts.
- Audit every other controller for endpoints that assume an administrator but do not enforce one. Fix any found in this PR, and list them in the PR description.
- Add tests: a non-allowlisted authenticated wallet gets 403, an allowlisted wallet gets 200, an unauthenticated request gets 401, and an empty or unset
ADMIN_WALLETS denies everyone rather than allowing everyone. That last case is the one that turns a config mistake into a breach, so it must be explicit.
- Document
ADMIN_WALLETS in .env.example with a comment explaining that an empty value denies all admin access by design.
Files To Touch
src/auth/guards/admin.guard.ts (new)
src/auth/guards/admin.guard.spec.ts (new)
src/config/env.ts
src/modules/admin/audit.controller.ts
.env.example
Acceptance Criteria
Mandatory Checks Before Opening PR
PRs that fail any check above will be closed without review, regardless of how much work went into them. No exceptions. This is a Grantfox-funded, quality-first project. Code that does not meet these standards will not be merged and will not be paid.
Problem
src/modules/admin/audit.controller.tsis decorated@Controller('admin')and guarded with@UseGuards(JwtAuthGuard)only. There is no role check and no admin allowlist. Any wallet that completes the normal nonce-and-signature flow and holds a valid JWT can read the full admin audit log throughGET /admin/..., including the record of who performed which privileged action and when.The root cause is that the API has no concept of an administrator.
USER_ROLESinsrc/modules/users/dto/set-role.dto.tsis['sponsor', 'vendor', 'mentor']. There is noadminvalue, noADMIN_WALLETor equivalent environment variable, and no admin guard anywhere insrc/.RolesGuardexists and works, but there is no admin role for it to check.This blocks other work as well as being a live exposure. Vendor approval, protocol pause, and parameter governance all need an authenticated administrator, and none of them can be built correctly until this exists.
Note the design constraint: roles are wallet-bound and permanent.
POST /users/me/rolereturns 409 once set, per the 2026-07-16 tracker entry. Admin must therefore not be a self-assignable role. If it were, anyone could claim it.Ground Rules
This issue must be solved according to StepFi's established engineering standards. Before writing any code:
RolesGuard, so this extends that design rather than contradicting itsrc/auth/guards/roles.guard.tsto understand the existing role-checking mechanism before adding to itsrc/modules/users/dto/set-role.dto.tsandsrc/modules/users/users.service.tsto understand how roles are assigned and why they are permanentsrc/config/env.tsfor the existing configuration and validation patternYour PR will be rejected regardless of whether CI passes if it conflicts with anything in these files, introduces patterns inconsistent with what already exists, or duplicates functionality that was solved differently elsewhere in the codebase.
What To Build
ADMIN_WALLETSenvironment variable holding a comma-separated allowlist of Stellar addresses. Validate at startup insrc/config/env.tsthat every entry is a well-formed Stellar public key, and fail fast with a clear message if not. Do not hardcode any address in a service file.admintoUSER_ROLES. Roles are self-selected and permanent, so a self-assignable admin role is a privilege escalation. Admin status must derive from the allowlist only.src/auth/guards/admin.guard.tsexporting anAdminGuardthat resolves the authenticated wallet from the request and checks membership in the allowlist. Follow the structure ofRolesGuard.@UseGuards(JwtAuthGuard, AdminGuard)toaudit.controller.ts.ADMIN_WALLETSdenies everyone rather than allowing everyone. That last case is the one that turns a config mistake into a breach, so it must be explicit.ADMIN_WALLETSin.env.examplewith a comment explaining that an empty value denies all admin access by design.Files To Touch
src/auth/guards/admin.guard.ts(new)src/auth/guards/admin.guard.spec.ts(new)src/config/env.tssrc/modules/admin/audit.controller.ts.env.exampleAcceptance Criteria
GET /admin/*returns 403 for an authenticated wallet not on the allowlistGET /admin/*returns 401 for an unauthenticated requestADMIN_WALLETSdenies all admin access rather than granting itadminis not added toUSER_ROLESand cannot be self-assigned viaPOST /users/me/roleMandatory Checks Before Opening PR
npm run build)anytypesPRs that fail any check above will be closed without review, regardless of how much work went into them. No exceptions. This is a Grantfox-funded, quality-first project. Code that does not meet these standards will not be merged and will not be paid.