- 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 checks a specific mistake: accidentally making a private profile endpoint public. It applies to the NestJS JWT example. The Python example below checks ownership of individual records.
These examples show rules derived from the projects’ source. They do not report a Perch scan of either project.
Python: FastAPI ownership checks
In the FastAPI template’s item routes, ordinary users can access their own items and superusers have broader access. The create handler takes the owner from the authenticated user. The list query restricts ordinary users’ results and count to that owner.
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 at its root:
- 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.
Run a rule-only scan, then check one handler after an edit:
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 as well as the endpoint. A parameter named current_user does not by itself prove authentication works.
TypeScript: NestJS guard registration
The NestJS JWT sample registers AuthGuard with APP_GUARD. Its login handler is marked public; its profile handler uses the authenticated request user. The guard verifies a bearer token for routes without the public marker.
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 in the clone’s root perch.yaml. Registration and the public-route exception are separate checks because they live in different files:
- 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 preserve the sample’s authentication wiring. Resource ownership needs an additional policy, such as the FastAPI rule above. The NestJS authentication documentation explains global guards and explicit public routes.
Authorization tests
Exercise endpoints with an unauthenticated request, the resource owner, another user, and each allowed administrative role. For list routes, check both returned records and counts. Denied writes must leave the resource unchanged.
Framework dependency injection, decorators, and middleware can be outside a method’s static call graph. Keep the wiring checks separate, inspect the supplied context, and run HTTP tests to verify the actual access boundary. A model finding needs review; a passing rule is not a security guarantee.