All 22 checks with why-it-matters prose, severity, and cross-references to related audits.
Ad-hoc role checks scattered across route files — `user.role === 'admin'` repeated in a dozen places — are impossible to audit and trivially out of sync. When a new role is added or a permission is tightened, every scattered comparison must be found and updated manually. One missed file is a silent privilege escalation. CWE-284 (Improper Access Control) and OWASP A01 (Broken Access Control) both trace back to exactly this pattern: authorization decisions made without a single, auditable source of truth. NIST 800-53 AC-3 requires access enforcement to be consistently applied — string comparisons in individual handlers fail that standard by design.
Why this severity: High because a missing or stale inline role check grants unauthorized capabilities to any authenticated user who reaches that route.
saas-authorization.access-control.auth-model-definedSee full patternWhen roles are checked with exact string equality (`role === 'member'`), a higher-privilege user fails checks designed for lower-privilege users — they must be explicitly listed in every condition they should satisfy. This creates access gaps that worsen as the role model grows. An Admin denied access to a Member-gated feature is a support burden at best; an Admin who can bypass a Member-gated resource because the hierarchy isn't enforced is a CWE-284 / OWASP A01 violation at worst. NIST 800-53 AC-3 expects access enforcement to reflect the intended privilege model, not accidental exclusion through literal comparisons.
Why this severity: High because exact-equality role checks produce both access gaps (Admins locked out) and privilege inversions (lower roles unexpectedly included) depending on how conditions are written.
saas-authorization.access-control.role-hierarchy-enforcedSee full patternCopy-pasted `getSession()` + inline `if (!session) return 401` blocks in every route handler are a maintenance hazard. When the session-check logic needs to change — adding a new auth provider, switching libraries, or tightening the token validation — every handler must be found and updated. One missed handler leaves an unguarded route. OWASP A01 (Broken Access Control) and CWE-284 explicitly cover the failure mode where access enforcement is inconsistent across handlers. Centralized middleware makes the enforcement surface one file, one audit.
Why this severity: Low because the immediate security impact is maintenance risk and inconsistency rather than a direct bypass, but the long-term risk of drift is meaningful.
saas-authorization.access-control.authorization-centralizedSee full patternHiding a UI button behind a client-side feature flag is not access control — it is presentation logic. The API endpoint behind that button remains callable by anyone who can craft a request. CWE-602 (Client-Side Enforcement of Server-Side Security) names this pattern precisely: security controls that exist only in client code are not controls at all. OWASP A01 covers the broader failure of missing server-side authorization. An attacker who skips the UI and calls the API directly will exercise the gated feature regardless of any client-side `useFeatureFlag` check.
Why this severity: Low severity because feature-only gating typically exposes paid features to non-paying users rather than enabling data exfiltration, but the business and billing impact is real.
saas-authorization.access-control.feature-flags-server-verifiedSee full patternA user update endpoint that accepts a `role` or `isAdmin` field in the request body without admin verification is a mass assignment vulnerability (CWE-915) enabling privilege escalation (CWE-285). Any authenticated user who POSTs `{ "role": "admin" }` to their own profile endpoint will elevate their privileges if the backend spreads the request body directly into a database update. OWASP A01 (Broken Access Control) flags this pattern explicitly. NIST 800-53 AC-6 (Least Privilege) requires that users cannot grant themselves permissions beyond what they were assigned.
Why this severity: Low because the attacker must be authenticated and must know which fields to tamper with, but self-promotion to admin via a single request is a complete authorization bypass.
saas-authorization.access-control.role-assignment-restrictedSee full patternA single unprotected API route or Server Action is a complete authorization bypass for any operation it performs. API routes in Next.js are public endpoints by default — authentication and authorization must be added explicitly. CWE-285 (Improper Authorization) and CWE-284 (Improper Access Control) are both in play when a route performs database writes without verifying who is asking. OWASP A01 (Broken Access Control) is the top web application risk category for this exact reason. NIST 800-53 AC-3 and AC-6 require access enforcement before any privileged operation executes. Server Actions receive the same treatment — they are callable from the browser and must not assume caller identity.
Why this severity: Critical because a single unprotected route exposes all of its data operations to unauthenticated callers, with no authentication barrier between the public internet and the database.
saas-authorization.resource-auth.every-api-checks-permsSee full patternFetching 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.
Why this severity: Critical because any authenticated user can access any record in the affected table by supplying a valid ID, regardless of who owns it.
saas-authorization.resource-auth.resource-ownership-verifiedSee full patternIDOR (Insecure Direct Object Reference) — CWE-639, CAPEC-143 — is one of the most reliably exploitable authorization failures: an attacker supplies someone else's resource ID and receives that resource. OWASP A01 (Broken Access Control) names IDOR as a primary example. The threat model is simple: authenticated user A calls `GET /api/invoices/invoice-uuid-owned-by-user-B` and receives user B's invoice. UUIDs reduce guessability but are not authorization controls — they merely slow brute-force enumeration. NIST 800-53 AC-3 requires access enforcement based on identity, not identifier obscurity. A single IDOR-vulnerable endpoint exposes every record in the affected table to any authenticated caller.
Why this severity: Critical because the attack requires only authentication and knowledge of one valid ID — no privilege escalation, no chaining, just a direct read or write of another user's data.
saas-authorization.resource-auth.no-idorSee full patternFile uploads stored in a flat global namespace allow users to access, overwrite, or enumerate other users' files by manipulating the path or key. If the storage path is constructed from user-supplied input rather than the server-side session, path traversal (CWE-22) becomes possible. At minimum, a flat namespace lets user A's upload silently overwrite user B's file if both upload the same filename. OWASP A01 (Broken Access Control) covers this: storage access must be scoped to the authenticated user. NIST 800-53 AC-3 requires access enforcement before any resource operation, including writes to object storage.
Why this severity: High because flat-namespace uploads expose all users' files to access or overwriting by other users, with severity scaling with the sensitivity of the uploaded content.
saas-authorization.resource-auth.file-upload-scopedSee full patternShared and team resources introduce a second authorization dimension: not just "is the user authenticated?" but "is the user a member of the group that owns this resource?" Skipping the membership check (CWE-639, CWE-284) means any authenticated user who knows a team project ID can read or modify it. OWASP A01 (Broken Access Control) explicitly covers this: authorization must verify the relationship between the principal and the resource, not just that a session exists. NIST 800-53 AC-3 requires access enforcement to reflect actual entitlements — membership in the owning group is the entitlement for shared resources.
Why this severity: High because the attack surface is every authenticated user in the system, not just attackers who have compromised specific accounts.
saas-authorization.resource-auth.shared-resources-access-controlsSee full patternBulk operations that accept an array of IDs and apply `WHERE id IN (ids)` without scoping to the authenticated user allow cross-user interference: user A includes IDs belonging to user B in their batch and the operation succeeds. CWE-639 (Authorization Bypass Through User-Controlled Key) applies directly — the user controls the ID set. CWE-284 covers the broader access control failure. OWASP A01 flags this as a broken access control pattern. A single bulk-delete endpoint without ownership scoping can allow an authenticated attacker to delete data belonging to any other user in the system.
Why this severity: Medium because the attacker must be authenticated and supply valid IDs, but the ability to affect other users' data at bulk scale is a meaningful authorization failure.
saas-authorization.resource-auth.bulk-operations-check-eachSee full patternAPI keys without scope restrictions grant the same access as the owner's full session — a compromised key is equivalent to a compromised account. CWE-285 (Improper Authorization) and CWE-284 cover the failure to limit what an API key can do. OWASP A01 (Broken Access Control) flags credential over-privilege as a primary risk. NIST 800-53 AC-6 (Least Privilege) and IA-3 (Device Identification) require that credentials be limited to their intended purpose. A read-only integration that holds a full-permission key can delete all user data, initiate payment changes, or export sensitive records — damage that scoped keys would prevent.
Why this severity: High because a single compromised full-permission API key exposes the entire account, whereas a scoped key limits the blast radius to the permitted operations.
saas-authorization.api-auth.api-keys-scopedSee full patternWebhook endpoints that process payloads without verifying the sender's signature accept forged requests from anyone who knows the endpoint URL. An attacker can POST a fabricated `payment_succeeded` event to trigger order fulfillment without paying, or a `user.deleted` event to wipe accounts. CWE-345 (Insufficient Verification of Data Authenticity) and CWE-347 (Improper Verification of Cryptographic Signature) define this failure. OWASP A02 (Cryptographic Failures) covers the broader category. NIST 800-53 SC-8 requires data integrity protection in transit — webhook signature verification is the enforcement mechanism for inbound event streams.
Why this severity: Medium because exploitation requires knowing the webhook URL, but the business impact of forged events (fake payments, account manipulation) can be severe and irreversible.
saas-authorization.api-auth.webhook-validates-senderSee full patternReturning HTTP 200 with an error body on authorization failures breaks API client error handling — clients that check `response.ok` will treat the failure as a success. Returning HTTP 500 from an unhandled exception when unauthorized access is attempted leaks implementation details (CWE-209). Returning HTTP 302 redirects from API routes sends HTML to JSON clients. These response code errors make authorization failures invisible to automated monitoring and difficult to debug. OWASP A01 (Broken Access Control) and CWE-284 both require that unauthorized access be denied with correct HTTP semantics, not masked behind incorrect status codes.
Why this severity: Medium because incorrect status codes obscure authorization failures from clients and monitoring, increasing mean time to detection when access controls are bypassed.
saas-authorization.api-auth.authorization-fails-403See full patternA test suite that only verifies authorized access never tests the actual security boundary — it confirms the happy path works but leaves the failure path untested. CWE-284 (Improper Access Control) vulnerabilities frequently survive code review and ship to production because no test ever checked that unauthorized access was rejected. OWASP A01 (Broken Access Control) is the top web application risk; the absence of negative authorization tests means any refactor can silently remove a permission check with no failing test to signal the regression. NIST 800-53 CA-8 (Penetration Testing) requires verifying that security controls reject unauthorized access — automated negative tests are the continuous equivalent.
Why this severity: Low because the pattern describes test absence rather than a direct vulnerability, but authorization regressions that only tests would catch are consistently discovered in production instead.
saas-authorization.api-auth.authorization-tested-negativeSee full patternA single global rate limit applied identically to free users, paid users, and admins means the limit must be conservative enough for the most restricted tier — which throttles legitimate premium users — or generous enough for heavy users — which provides no abuse protection for the free tier. CWE-284 (Improper Access Control) covers the failure to differentiate resource access by privilege level. OWASP A01 (Broken Access Control) and NIST 800-53 AC-6 (Least Privilege) require that access controls reflect the actual entitlement model, which for API rate limits means tier-aware limits that match the product's promise to each user segment.
Why this severity: Low because absent role-differentiated limits degrades product experience and cost controls rather than enabling direct data exfiltration.
saas-authorization.api-auth.api-rate-limit-per-roleSee full patternAn admin route that checks `if (!session)` but not `if (session.user.role !== 'admin')` is accessible to every authenticated user in the application. This exposes user management, system configuration, and privileged operations to any account holder. CWE-285 (Improper Authorization) and CWE-284 (Improper Access Control) define this failure precisely. OWASP A01 (Broken Access Control) lists this as a primary failure mode. NIST 800-53 AC-3 and AC-6 require that admin operations enforce both authentication and the specific privilege level required — the authentication check alone is necessary but not sufficient.
Why this severity: Critical because any authenticated user gains access to administrative functionality — user management, billing, system configuration — without needing any elevated credentials.
saas-authorization.admin-privilege.admin-routes-protectedSee full patternSpreading `req.body` directly into a `db.user.update({ data: body })` call is mass assignment (CWE-915): any field the attacker includes in the request body will be written to the database. For user records, this means `{ "role": "admin" }` is a single-request privilege escalation. CWE-285 (Improper Authorization) and OWASP A01 (Broken Access Control) cover this exact pattern. NIST 800-53 AC-6 (Least Privilege) requires that users cannot update fields beyond their entitlement — without an explicit allowlist, every field in the database schema is implicitly writable by authenticated users.
Why this severity: High because an authenticated user can escalate to admin privileges with a single crafted PATCH request, requiring no special knowledge beyond knowing which fields exist in the user model.
saas-authorization.admin-privilege.no-privilege-escalation-paramsSee full patternSensitive account operations — password change, email change, account deletion, MFA modification — that execute with only a valid session cookie are fully exploitable by session hijacking or CSRF. An attacker who steals a session token can change the account email, locking out the legitimate owner permanently. CWE-306 (Missing Authentication for Critical Function) and CWE-308 (Use of Single-Factor Authentication for a Critical Action) define this failure. OWASP A07 (Identification and Authentication Failures) covers it. NIST 800-53 IA-11 (Re-Authentication) explicitly requires fresh authentication for high-impact operations. Re-authentication limits the stolen-session blast radius to read-only access.
Why this severity: High because a stolen session cookie enables permanent account takeover through email address change without any additional credential, with no recovery path for the victim.
saas-authorization.admin-privilege.sensitive-ops-reauthSee full patternMulti-tenant applications that derive the active tenant ID from a client-supplied header, query parameter, or request body — rather than from the authenticated session — allow any authenticated user to access any organization's data by supplying that org's ID. CWE-284 (Improper Access Control) and CWE-639 (Authorization Bypass Through User-Controlled Key) both apply. OWASP A01 (Broken Access Control) lists tenant isolation failures as a primary risk. NIST 800-53 AC-3 and SC-4 (Information in Shared Resources) require that tenant boundaries be enforced by the system, not honored on trust from the client.
Why this severity: High because any authenticated user can access any tenant's data by supplying a valid org ID — no brute force needed if org IDs are discoverable or guessable.
saas-authorization.admin-privilege.multi-tenant-isolationSee full patternA database query in a multi-tenant route that omits the tenant ID filter returns records from all tenants — every authenticated user can read every organization's data if they can reach the endpoint. CWE-284 (Improper Access Control) and CWE-639 (Authorization Bypass Through User-Controlled Key) apply when the query is not scoped to the active tenant. OWASP A01 (Broken Access Control) covers the failure. NIST 800-53 AC-3 and SC-4 (Information in Shared Resources) require that queries not cross tenant boundaries. List endpoints (`findMany`, `SELECT *`) without a tenant filter are the highest-risk variant — a single call returns the entire dataset.
Why this severity: High because any authenticated user in the system can enumerate another tenant's complete dataset through an unscoped list query with a single authenticated request.
saas-authorization.admin-privilege.db-queries-tenant-scopedSee full patternList endpoints without mandatory tenant scope filters are the highest-impact cross-tenant leakage vector: a single `GET /api/projects` call returns every project in the database if the tenant filter is missing or overridable. CWE-284 (Improper Access Control) and CWE-639 (Authorization Bypass Through User-Controlled Key) apply when the tenant filter can be bypassed by request parameters. OWASP A01 (Broken Access Control) lists this as a primary multi-tenant failure. NIST 800-53 AC-3 and SC-4 (Information in Shared Resources) require that tenant boundaries be enforced at the data layer. Search and autocomplete endpoints are a particularly common blind spot — they are often added quickly and the tenant filter is forgotten.
Why this severity: High because a missing tenant filter on a single list endpoint exposes every record in the affected table to any authenticated user, across all organizations in the system.
saas-authorization.admin-privilege.no-cross-tenant-leakageSee full patternRun this audit in your AI coding tool (Claude Code, Cursor, Bolt, etc.) and submit results here for scoring and benchmarks.
Open Authorization Audit