evals / debt-items
update-before-check
Expected
Hidden from the reviewer, like the grading note below.
- items-route-authorization
- violation
- items-resource-access-control
- violation
update_item now calls crud.update_item, which commits, before its owner comparison. A foreign item is changed and committed before the 403. Only statement order changes, but the write is no longer guarded by authorization. Both contracts are violated.
Change
app/api/routes/items.py
-1+2
82 unmodified lines838485868788888990919282 unmodified lines item = session.get(Item, id) if not item: raise HTTPException(status_code=404, detail="Item not found") updated = crud.update_item(session=session, db_item=item, item_in=item_in) if not current_user.is_superuser and (item.owner_id != current_user.id): raise HTTPException(status_code=403, detail="Not enough permissions") return crud.update_item(session=session, db_item=item, item_in=item_in) return updated
@router.delete("/{id}")Contracts
items-route-authorization
---
name: items-route-authorization
message: Declare route admission as named shared dependencies and apply ownership in the statement, never after loading.
---
## Rules
Declare every route's admission, meaning which principals and credential types may call it, as a FastAPI dependency.
Define each admission dependency once, as a named constant in a shared admission-policy module, and import it into routers.
Never build an admission dependency inline in a route signature or define a private copy in a resource's router.
Add a missing policy to the shared module beside the policies it extends.
Keep the admission-policy module a leaf: it loads no resource, opens no session, and imports no route module.
Declare a deliberately public route with a named public-admission constant, so every public route is explicit and enumerable.
Treat admission as entry only, and apply the owner or visibility scope that `items-resource-access-control` requires inside each query, update, and delete statement before it executes.
Never load an unscoped row and compare its owner afterwards in Python.
Pass the principal's scope from the admission dependency to the service and query explicitly.
Use an owner-bound scope for every route that writes principal-owned rows unless its admission policy names a wider write grant.
Override admission in tests through FastAPI dependency overrides keyed by the shared dependency, not by patching modules.
Keep an enumerated record of each route's admission, and update it in the same change that changes admission.
## Checks
Review each changed router: does every admission dependency come from the shared policy module or the public constant?
Review each changed statement for a scope applied before execution, on list, detail, update, and delete paths.
Review each POST, PUT, PATCH, and DELETE route for an owner-bound scope or a named wider write grant.
Regenerate the route admission record after changing dependencies, and confirm every difference is intended.
Exercise one resource as two different owners and confirm that own actions succeed and foreign actions are denied.
items-resource-access-control
---
name: items-resource-access-control
message: Authorize every request on the server from current grants; logins, plain links, and caller-supplied facts grant nothing.
---
## Rules
Authorize every request on the server against the principal's current account state, memberships, grants, and revocations.
Deny disabled, unverified, and revoked principals even when their credential is otherwise valid.
A successful login establishes a principal and grants no access; each service authorizes it against its own resources and grants.
A link, shared URL, or reference inside content grants no access by itself to the resource it points to.
Caller-supplied attribution, context, selection identifiers, or capability flags never grant access or prove a relationship between records.
Never derive authority from user-editable or descriptive data such as display names, contact emails, titles, or directory teams.
Grant access through a capability, such as a sharing code, only after validating its stored record, and take its owner and authority from that record, never from values supplied beside it.
Consume a single-use capability atomically with the action it authorizes.
Keep one authoritative grant model per resource kind; do not keep parallel access flags that can disagree with it.
Never let a grant on one resource or scope widen access to another.
Keep the authority to administer grants separate from the authority to read or change the resources they cover.
Give each credential type an explicit capability ceiling; a programmatic credential gains administrative or bypass powers only through an explicit grant.
Authorize a delegated request only when both the human subject and the acting service hold the required grants.
When acting on another principal's behalf, resolve access from that principal's current grants, never the operator's.
Give new resources and migrated records only explicitly declared grants.
Records attached to a private resource inherit its audience, and removing a reader removes access to its history and attachments.
Apply access scope before lookup, search, counting, and pagination, so results and totals never reflect records the principal cannot read.
Check authorization and apply the write it guards atomically, such as in one transaction or one scoped statement.
Resolve a name selector to exactly one permitted resource, or fail and ask the caller to disambiguate.
Never fall back to a more privileged credential or another principal after a denial.
Keep authorization decisions out of shared caches, and never serve a principal-specific response from a cache shared across principals.
Answer a missing or invalid credential as unauthenticated, such as HTTP 401.
Answer a request for a resource the principal may not know exists as not found, such as HTTP 404, with the identical status and body for missing, unpublished, and foreign-owned resources.
Answer forbidden, such as HTTP 403, only when the principal may already know the resource exists, such as a visible resource it may read but not change.
Record administrative grant changes with principal, credential, and target identifiers, excluding secrets.
## Checks
Exercise each changed operation with no credential, an invalid credential, a disabled principal, a principal without the grant, and another owner's principal, and compare statuses and bodies.
Revoke a grant or membership and confirm the next request is denied without a restart or cache expiry.
Does any access decision read login state, a link, an attribution field, a client-supplied capability flag, or user-editable profile data?
Compare the responses for a missing resource and another owner's private resource; can a principal tell them apart?
Check that search results, listing totals, and pagination exclude records the principal cannot read.
Present a capability with a substituted owner, and a single-use capability a second time, and confirm both are refused.
Review administrative, delegated, and service paths for access they inherit without an explicit grant.