- name: profile-is-not-public
where: sample/19-auth-jwt/src/auth/auth.controller.ts
ensure: >-
In AuthController, signIn may be public and getProfile
must require the global authentication guard. Marking
getProfile or its containing class Public breaks this
rule.
This rule catches one specific mistake: making a private profile endpoint public by accident. It is written for the NestJS JWT example. The Python example further down checks who owns each record.
Both rules are written from each project’s source. Neither is a report from a Perch scan of that project.
Python: FastAPI ownership checks
In the FastAPI template’s item routes, ordinary users can reach only their own items, and superusers can reach more. The create handler sets the owner to the authenticated user. For ordinary users, the list query limits both the results and the count to items they own.
Clone that revision:
git clone https://github.com/fastapi/full-stack-fastapi-template.git fastapi-example
cd fastapi-example
git checkout cb740b656d7a0a6c5e12c7bf8e50343ec94ee9c7
Save this as perch.yaml in the root of the clone:
- name: item-access-requires-owner
where: backend/app/api/routes/items.py
each: method
ensure: >-
An item endpoint derives the acting user from CurrentUser.
Non-superusers may read, change, or delete only their own
items. Listing applies the owner restriction to both the
records and the count. Creating an item assigns the
authenticated user's ID as owner. Trusting a caller-supplied
owner ID or accepting an item ID without the ownership
check breaks this rule.
Scan with only the custom rules, then check a single handler after you edit it:
perch scan --paths backend/app/api/routes/items.py --filter type=lint
perch check backend/app/api/routes/items.py::update_item \
--rules item-access-requires-owner
CurrentUser is defined through a dependency in deps.py. Review that dependency along with the endpoint: a parameter named current_user does not by itself show that authentication works.
TypeScript: NestJS guard registration
The NestJS JWT sample registers AuthGuard with APP_GUARD, so the guard runs on every route. The login handler is marked public. The profile handler reads the user from the request, which the guard has already authenticated by verifying a bearer token.
git clone --filter=blob:none --sparse https://github.com/nestjs/nest.git nest-example
cd nest-example
git checkout 14b74ae705904626aa2bbe8ee30ad94f48d16381
git sparse-checkout set sample/19-auth-jwt
Save these file rules as perch.yaml in the root of the clone. Registering the guard and exempting public routes happen in different files, so each gets its own check:
- name: auth-guard-is-global
where: sample/19-auth-jwt/src/auth/auth.module.ts
ensure: >-
The providers declaration registers AuthGuard using
APP_GUARD. Importing the guard without registering it,
or registering it only as an ordinary injectable, breaks
this rule.
- name: profile-is-not-public
where: sample/19-auth-jwt/src/auth/auth.controller.ts
ensure: >-
In AuthController, signIn may be public and getProfile
must require the global authentication guard. Marking
getProfile or its containing class Public breaks this
rule.
- name: guard-verifies-token
where: sample/19-auth-jwt/src/auth/auth.guard.ts
ensure: >-
In canActivate, a route without the explicit public
marker requires a bearer token verified by JwtService.
Missing or invalid tokens are rejected. Merely decoding
a token or allowing the request after verification
throws breaks this rule.
perch scan --paths sample/19-auth-jwt/src/auth --filter type=lint
perch check sample/19-auth-jwt/src/auth/auth.controller.ts \
--rules profile-is-not-public
These checks keep the sample’s authentication wiring in place. Checking who owns a resource needs its own policy, like the FastAPI rule above. The NestJS authentication documentation explains global guards and explicit public routes.
Authorization tests
Call each endpoint without signing in, as the resource owner, as another user, and as each administrative role that should have access. For list routes, check both the records returned and the count. A denied write must leave the resource unchanged.
Frameworks often apply authentication through dependency injection, decorators, or middleware, which can sit outside a method’s static call graph. Keep the wiring checks separate, look at the context Perch supplied, and run HTTP tests to confirm the real access boundary. Review every model finding; a passing rule is not a security guarantee.