- 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.