A file inside app/(authenticated)/ or app/admin/ that never calls getServerSession(), auth(), or an equivalent session getter is unprotected — the directory name is a routing convention, not a security boundary. CWE-862 (Missing Authorization) and OWASP A01 (Broken Access Control) describe this exactly. An unauthenticated user who discovers the route URL gets full access. This pattern is extremely common in AI-generated code because the model groups files into protected directories without always wiring the session check inside each file.
Critical because a missing session check in a protected route grants unauthenticated access to admin data, user records, or privileged operations — a direct OWASP A01 broken access control.
Call the session getter at the top of every protected route handler and redirect or return 401 immediately if the result is null.
// app/admin/users/page.tsx
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default async function Page() {
const session = await auth()
if (!session?.user || session.user.role !== 'admin') {
redirect('/login')
}
const users = await prisma.user.findMany()
return <UserList users={users} />
}
For API routes, return a 401 instead of redirecting. Do not rely on directory grouping in the App Router as a security mechanism — it affects routing only, not authentication.
ID: ai-slop-security-theater.unbound-auth.protected-routes-call-session-getter
Severity: critical
What to look for: Count all protected route files. Walk all protected route files (API handlers under app/api/(authenticated)/**, app/api/(protected)/**, app/api/admin/**, pages/api/admin/**, OR pages under app/(authenticated)/**, app/(protected)/**, app/(dashboard)/**, app/admin/**). For each file, count whether the file imports AND calls at least 1 session-getter function: getServerSession, auth(), getUser, currentUser, getSession, cookies() followed by JWT verification, clerkAuth(), getKindeServerSession, getSupabaseServerClient. Report files where the route is implied to be protected by directory naming but no session getter is called.
Pass criteria: 100% of protected route files import AND call a session-getter function. Report: "X protected route files inspected, Y call a session getter, 0 unprotected."
Fail criteria: At least 1 file in a protected route directory has no session-getter call.
Do NOT pass when: A file imports a session getter but never calls it — the import alone doesn't protect anything.
Skip (N/A) when: Project has no protected route directories detected (no (authenticated), (protected), admin/, etc.).
Cross-reference: For deeper authorization analysis, the SaaS Authorization audit (saas-authorization) covers RBAC and permission patterns in depth.
Detail on fail: "2 protected routes don't check the session: app/admin/users/page.tsx (no getServerSession/auth() call), app/api/admin/delete/route.ts DELETE handler (no session lookup)"
Remediation: A protected route that doesn't actually check the session is unprotected. Always call the session getter at the top of the handler:
// Bad: directory implies protection, but no auth check
// app/admin/users/page.tsx
export default async function Page() {
const users = await prisma.user.findMany()
return <UserList users={users} />
}
// Good: explicit session check
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default async function Page() {
const session = await auth()
if (!session?.user || session.user.role !== 'admin') {
redirect('/login')
}
const users = await prisma.user.findMany()
return <UserList users={users} />
}