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
This function takes an order ID and nothing else. It has no way to know who the caller is, so it cannot check that the caller owns the order. The code is deliberately broken; it comes from Perch’s 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
The rule states the access policy directly. In a real application, authorization often happens in the caller before this function runs, so check the caller too.
Reject a different owner
One way to fix it, shown here as an illustrative example, is to take an actor_id from the trusted authentication layer and check 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 request field the user controls. Test the fix at the HTTP boundary: cancel the order as its owner, as a different user, and without signing in. Only the owner’s request should succeed, and the other two must leave the order unchanged.
Rerun the rule after the edit. A repository scan reads committed code, so commit the change before scanning.
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 says nothing about who the caller is. That scan did not report the missing owner check in cancel_order. Ownership is specific to this application, which is why it needs the custom rule above.
For access policies in a web framework, see the FastAPI and NestJS examples.