No fallback admin credentials in authentication code
Why it matters
Fallback admin credentials are a silent authentication backdoor. When a request arrives with an empty or malformed body, the OR-expression resolves to the hardcoded value — and the code proceeds to look up and authenticate a real admin account. An attacker who sends an empty POST to the login endpoint gets in as admin. This maps to OWASP A07:2021 (Identification and Authentication Failures) and CWE-1188 (Insecure Default Initialization): the default is not 'reject unauthenticated request' but 'log in as administrator.' No rate limit or firewall rule compensates for this; the bypass is baked into application logic.
Severity rationale
Critical because a single unauthenticated HTTP request with an empty body can elevate to admin access, bypassing all authentication controls entirely.
Remediation
Fail loudly on missing credentials at the route boundary — never fall through to a default email or password. In your login handler (src/app/api/login/route.ts or equivalent), replace every OR-fallback with explicit Zod validation:
import { z } from 'zod'
const LoginSchema = z.object({
email: z.string().email(),
password: z.string().min(1),
})
export async function POST(req: Request) {
const body = await req.json()
const parsed = LoginSchema.safeParse(body)
if (!parsed.success) {
return Response.json({ error: 'Invalid credentials' }, { status: 400 })
}
const { email, password } = parsed.data
// No fallback, no default — explicit rejection only
}
Audit every file that reads req.body.email, req.body.password, or equivalent session fields and remove all || and ?? fallbacks to credential strings.
Detection
-
ID:
fallback-admin-credentials -
Severity:
critical -
What to look for: Walk all source files for OR-fallback patterns containing admin/test credentials. Before evaluating, extract and quote each OR-fallback expression. Count all occurrences of these patterns:
X || 'admin@admin.com',X || 'admin@localhost',X || 'test@test.com',X || 'admin@example.com',X || 'admin'(where X is a variable),X ?? 'admin@...',X || "password",X || "admin123",X || "123456",X || "password123",X || "letmein". Also count default parameters:function(email = 'admin@admin.com'),function(password = 'password123'). Also count fallback assignments in session/user-creation code:const email = req.body.email || 'admin@...',const user = { email: body.email || 'admin@...', role: 'admin' }. -
Pass criteria: 0 fallback admin-credential patterns in source code. Report: "Scanned X source files, 0 fallback admin credentials found."
-
Fail criteria: At least 1 OR-fallback or default parameter pattern uses admin credentials, test passwords, or "bypass" values.
-
Do NOT pass when: The fallback is conditionally gated on
NODE_ENV === 'development'but the condition wraps only the value, not the surrounding auth check — the admin email still ends up in the session lookup path. -
Skip (N/A) when: Project has 0 non-test source files.
-
Cross-reference: For deeper authentication security, the SaaS Authentication audit (
saas-authentication) covers session and credential flows in depth. -
Detail on fail:
"2 fallback admin credentials: 'const email = req.body.email || \"admin@admin.com\"' in src/app/api/login/route.ts (line 12), 'const user = { email: body.email ?? \"admin@example.com\" }' in src/lib/session.ts (line 34)" -
Remediation: Fallback admin credentials are a security backdoor — if the request body is empty or malformed, the code silently logs in as admin. Fix by failing loudly on missing credentials:
// Bad: silent admin bypass const email = req.body.email || 'admin@admin.com' const user = await findUserByEmail(email) // Good: explicit validation, no fallback const { email } = req.body if (!email) { return Response.json({ error: 'Email required' }, { status: 400 }) } const user = await findUserByEmail(email)Use a validation schema (Zod, Yup) to enforce required fields at the route boundary.
External references
- cwe · CWE-1188 — Insecure Default Initialization of Resource
- cwe · CWE-287 — Improper Authentication
- owasp:2021 · A07
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ai-slop-half-finished·automated