def cancel_order(order_id, orders):
"""Cancel an order on behalf of the person who placed it."""
order = orders[order_id]
order.status = "cancelled"
return order
An order ID is all this function takes. It receives no user identity and makes no ownership check. The code is a deliberately broken example order service.
Require ownership before cancellation
Save this rule in perch.yaml:
- name: cancellation-requires-owner
where: auth.py
each: method
ensure: >-
If this method cancels an order, it checks that the
authenticated caller owns that order before changing it.
Possession of an order ID alone is insufficient. A method
that does not cancel orders satisfies this rule.
After setting up the example, check the function:
perch check auth.py::cancel_order --rules cancellation-requires-owner
This rule names the access policy directly. In a real application, also inspect the caller: authorization may happen before the function is called.
Reject a different owner
An illustrative fix takes actor_id from the trusted authentication layer and checks it before changing the order:
def cancel_order(order_id, orders, actor_id):
order = orders[order_id]
if order.owner_id != actor_id:
raise PermissionError("Order belongs to another user")
order.status = "cancelled"
return order
Never take actor_id from a user-controlled request field. Test the owner, a different user, and an unauthenticated request at the HTTP boundary. Denied requests must leave the order unchanged.
Rerun the rule after the edit. Commit before running a repository scan.
Scan for other security issues
perch scan --filter type=security
The built-in checks ask about authorization, injection, weak crypto, and other security classes. In our recorded combined scan, Perch flagged this method at 78% for weak crypto:
def token_for(user_id):
"""A stable token identifying a user to the order service."""
return hashlib.md5(str(user_id).encode()).hexdigest()
Hashing a predictable user ID does not establish the caller’s identity. That scan did not report the missing owner check in cancel_order; the custom rule above makes that requirement explicit.
For framework-specific policies, use the FastAPI and NestJS examples.