Problem Statement. roles.guard.ts short-circuits when no @Roles() decorator is
present and returns true. A misconfigured hero route that silently accepts an
Authorization: Bearer header but only validates against x-api-key could let an attacker
bypass the role check by presenting a token nobody considered.
Why it matters. Defence-in-depth. A "no decorator => open" rule plus a header
introspection catch-all is a common authorisation-bypass primitive.
Technical Context. app/backend/src/auth/roles.guard.ts:
if (!requiredRoles) {
return true; // No role required — open to any authenticated caller
}
Expected Outcome. A complementary UnexpectedAuthHeaderGuard (or a property on
RolesGuard) that fails (HTTP 401) when an undecorated handler receives any
authorisation credential. Operators opt-in by adding @NoAuthStrict() on handlers
explicitly designed for public anonymous access.
Acceptance Criteria.
- New e2e test asserts: a route with no decorator and a
Bearer header returns 401, not
200.
- Routes intentionally public (
/health, /api/docs, /ai/metrics) opt out by an env
list PUBLIC_AUTH_BYPASS=/health,/api/docs,/ai/metrics.
- CI lint catches new undecorated routes: a
nestjs-route-doctor runs in CI and reports.
Implementation Notes. The safest approach is to make the absence of credentials the
default state and require an explicit allowlist rather than the inverse.
Files or modules likely to be affected. src/auth/roles.guard.ts,
new src/common/guards/unexpected-auth-header.guard.ts,
app/backend/test/security.e2e-spec.ts.
Dependencies. None.
Difficulty. Medium
Estimated effort. S
Backlog item #6 from `docs/maintainer-issue-backlog.md.
Problem Statement.
roles.guard.tsshort-circuits when no@Roles()decorator ispresent and returns
true. A misconfigured hero route that silently accepts anAuthorization: Bearerheader but only validates againstx-api-keycould let an attackerbypass the role check by presenting a token nobody considered.
Why it matters. Defence-in-depth. A "no decorator => open" rule plus a header
introspection catch-all is a common authorisation-bypass primitive.
Technical Context.
app/backend/src/auth/roles.guard.ts:Expected Outcome. A complementary
UnexpectedAuthHeaderGuard(or a property onRolesGuard) that fails (HTTP 401) when an undecorated handler receives anyauthorisation credential. Operators opt-in by adding
@NoAuthStrict()on handlersexplicitly designed for public anonymous access.
Acceptance Criteria.
Bearerheader returns 401, not200.
/health,/api/docs,/ai/metrics) opt out by an envlist
PUBLIC_AUTH_BYPASS=/health,/api/docs,/ai/metrics.nestjs-route-doctorruns in CI and reports.Implementation Notes. The safest approach is to make the absence of credentials the
default state and require an explicit allowlist rather than the inverse.
Files or modules likely to be affected.
src/auth/roles.guard.ts,new
src/common/guards/unexpected-auth-header.guard.ts,app/backend/test/security.e2e-spec.ts.Dependencies. None.
Difficulty. Medium
Estimated effort. S
Backlog item #6 from `docs/maintainer-issue-backlog.md.