Resource Ownership Verified
Why it matters
Fetching a resource by a client-supplied ID without verifying the requesting user owns it is Insecure Direct Object Reference — CWE-639 (Authorization Bypass Through User-Controlled Key). Any user who knows (or guesses) a resource ID can read or modify records they do not own. OWASP A01 (Broken Access Control) lists IDOR as a primary failure mode. UUIDs do not fix this: they are harder to guess but are not authorization controls. NIST 800-53 AC-3 requires that access enforcement be based on identity and authorization, not obscurity of identifiers. A missed ownership check on a single endpoint exposes the data of every user who has a record in that table.
Severity rationale
Critical because any authenticated user can access any record in the affected table by supplying a valid ID, regardless of who owns it.
Remediation
Include ownership in the database query itself — making the check atomic with the fetch and impossible to accidentally omit. Return 404 (not 403) to avoid leaking whether the resource exists.
// Safe — ownership enforced at query level
const post = await db.post.findFirst({
where: {
id: params.id,
authorId: session.user.id, // Ownership is part of the lookup
},
});
if (!post) return NextResponse.json({ error: 'Not found' }, { status: 404 });
// 404 (not 403) avoids confirming the resource exists to unauthorized callers
For shared resources accessed through organizational membership, verify the relationship through a membership table rather than relying on direct ownership.
Detection
-
ID:
resource-ownership-verified -
Severity:
critical -
What to look for: Count all relevant instances and enumerate each. Find database queries that fetch a single resource by a user-supplied ID —
findUnique({ where: { id: params.id } }),findFirst({ where: { id: body.id } }), raw SQLWHERE id = $1. Check whether these queries also include a filter on the authenticated user's ID or a membership verification. The patternfindUnique({ where: { id: params.id } })followed byif (resource.userId !== session.user.id)is acceptable. The patternfindUnique({ where: { id: params.id } })with no subsequent ownership check is a fail. -
Pass criteria: All queries that retrieve a single resource by a client-supplied ID either (a) include the user's ID in the WHERE clause as part of the query, or (b) check the returned resource's ownership field immediately after fetching and return 403/404 if ownership does not match. At least 1 implementation must be verified.
-
Fail criteria: Any query fetches a resource by client-supplied ID without verifying the requesting user's relationship to that resource — either inline in the query or immediately after.
-
Skip (N/A) when: The project has no routes that accept resource IDs as parameters. Skip for genuinely public resources (blog posts, public profiles) where all authenticated users are intended to have read access.
-
Detail on fail:
"Found N query/queries that fetch resources by ID without ownership verification. Any user who knows a resource ID can read/modify it."(List the specific files and queries.) -
Remediation: Include ownership in the database query itself — this is the safest approach because the check is atomic with the fetch.
// Unsafe — fetches by ID alone // const post = await db.post.findUnique({ where: { id: params.id } }); // Safe — fetches only if the requesting user owns it const post = await db.post.findFirst({ where: { id: params.id, authorId: session.user.id, // Ownership enforced at query level }, }); if (!post) return NextResponse.json({ error: 'Not found' }, { status: 404 }); // Return 404 (not 403) to avoid revealing whether the resource existsReturning 404 instead of 403 for unauthorized resource access is intentional — it avoids leaking information about resource existence to unauthorized callers.
External references
- cwe · CWE-639 — Authorization Bypass Through User-Controlled Key
- cwe · CWE-284 — Improper Access Control
- owasp:2021 · A01
- nist:rev5 · AC-3
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-authorization·automated