Object-level authorization checks the requested resource, not only the identity making the request. A user may be allowed to view invoices while still being restricted to invoices for their own account. The check connects the actor, action, and exact object before data is returned or changed.
Why route protection is not enough
Authentication can prove that a request comes from a known user. A broad permission can prove that the user may perform an operation in general. Neither proves that the specific identifier in the request belongs to a resource the user may access.
The gap often appears in direct lookups. A handler reads an object by ID, confirms that it exists, and returns it. If the query or subsequent policy never scopes the object to the current account, another valid identifier may cross the intended boundary.
| Request path | Object to check | Relationship to verify |
|---|---|---|
GET /orders/:id | One order | Actor can read its account |
PATCH /files/:id | One file | Actor can change its owner or folder |
| Bulk export | Selected records | Every record is within the allowed scope |
| Background job | Stored job target | Job retains the initiating account context |
Where the check can disappear
An audit of vibe-coded software traces every path that accepts or reconstructs a resource identifier. Generated handlers may repeat a convenient global lookup instead of using a shared scoped query. New batch operations, administrative tools, and asynchronous jobs can bypass a check that exists in the original screen.
The audit looks for several patterns:
- Global lookup: The object is fetched by identifier without account or ownership scope.
- Client-supplied owner: The request states which user or tenant owns the object.
- Partial bulk check: The first item is allowed, but later items are not checked.
- Detached child object: A nested record is loaded without verifying its parent.
- Stale job context: Queued work runs after the initiating user's access changes.
How object checks relate to policy
Attribute-based access control can express the relationship as a policy using actor and resource attributes. A simpler application may enforce the same rule by scoping database queries to the current account. The implementation differs, but the decision must happen before the operation and must use trusted context.
Tenant boundaries need the same precision. A general role may allow invoice access, while object-level authorization limits that role to one tenant's invoices. This makes the check a core part of tenant isolation rather than an optional filter in the interface.
Evidence that every object is protected
The strongest tests create at least two actors and two resources. They prove that an allowed actor can use the correct object, cannot use the other object, and causes no read, write, event, or queued work after denial. Bulk and nested routes need equivalent cross-boundary cases.
The output of the audit is a map of identifiers, loading paths, ownership rules, and enforcement points. It supports the discipline required when AI-generated software moves into production, where a working screen does not show what a changed request can reach.