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.