A Zod schema exported but never called at runtime is not input validation — it is type decoration. The taxons placeholder-hygiene and injection-and-input-trust both apply: the AI wrote a schema to satisfy the appearance of security (OWASP A03: Injection), but the validation never executes, so arbitrary input reaches database writes, file operations, and downstream systems unchanged. CWE-20 (Improper Input Validation) is the direct mapping. This is the canonical AI security-theater pattern: the codebase looks hardened because schemas exist, but CreateUserSchema.parse() is never called on any entry-point body.
Critical because a defined-but-never-invoked schema provides zero protection — every API route that relies on it accepts arbitrary input, enabling injection and data corruption.
Call .parse() or .safeParse() on every schema at every entry point that receives external input. Type inference alone (z.infer<typeof Schema>) does nothing at runtime.
// src/app/api/users/route.ts
import { CreateUserSchema } from '@/lib/schemas'
export async function POST(req: Request) {
const body = await req.json()
// Bad: schema exists but is never invoked — arbitrary input passes through
// const user = await db.user.create({ data: body })
// Good: parse throws ZodError on invalid input before it reaches the DB
const data = CreateUserSchema.parse(body)
const user = await db.user.create({ data })
return Response.json(user, { status: 201 })
}
For React forms: useForm({ resolver: zodResolver(CreateUserSchema) }). For tRPC procedures: .input(CreateUserSchema). For server actions: parseWithZod(formData, { schema: CreateUserSchema }). Every schema must have at least one call site that runs at runtime.
ID: ai-slop-hallucinations.data-references.zod-schemas-have-runtime-use
Severity: critical
What to look for: Walk source files for every exported schema declaration matching: export const X = z.object(...), export const X = z.array(...), export const X = z.union(...), export const X = z.discriminatedUnion(...), Joi.object(...), yup.object(...), valibot.object(...), Schema(...) (Mongoose), pgTable(...) (Drizzle, but those are tables, exclude them). For each exported schema name X, search the entire src/, app/, lib/, server/, pages/, api/ tree (excluding test directories) for at least 1 runtime call site referencing the schema: X.parse(, X.safeParse(, X.parseAsync(, X.safeParseAsync(, X.validate(, X.validateSync(, X.assert(, parse(X, ...), validateRequest(X, ...), useForm({ resolver: zodResolver(X) }), useForm({ resolver: yupResolver(X) }), tRPCRouter.input(X), safeAction(X), useFormState(action, X), parseWithZod({ schema: X }). EXCLUDE schemas that are only used for type inference (type T = z.infer<typeof X>) — search for any use of the schema NAME beyond z.infer<>/Joi.AnyType<>/yup.InferType<>. Count all exported schemas, total with at least one runtime use, total dangling (defined but never invoked at runtime).
Pass criteria: 100% of exported validation schemas have at least 1 runtime call site. Report: "X exported schemas inspected, Y with runtime use, 0 dangling."
Fail criteria: At least 1 exported schema is declared but never invoked at runtime — the schema exists for type inference only, providing no input validation.
Do NOT pass when: A schema is only used inside type X = z.infer<typeof Y> declarations and has zero .parse()/.safeParse()/resolver calls anywhere in the codebase. This is the canonical AI security theater pattern: the model writes "we have validation" via a schema export, but never actually validates anything.
Skip (N/A) when: No validation library is in dependencies (none of zod, yup, joi, valibot, superstruct, class-validator).
Report even on pass: Report the count and library detected. Example: "Validation library: zod. 14 exported schemas inspected, 14 with runtime use (100%)."
Cross-reference: For broader security control validation, the Security Hardening audit (security-hardening) covers input validation patterns and security middleware wiring.
Detail on fail: "3 dangling schemas: 'CreateUserSchema' in src/lib/schemas.ts (used only in type inference), 'UpdateProfileSchema' in src/lib/schemas.ts (zero call sites). The schemas exist but no input is actually validated — this is security theater."
Remediation: A schema defined but never invoked is not validation — it's just types. The runtime accepts any input. Fix it by actually parsing input through the schema at every entry point:
// src/app/api/users/route.ts
import { CreateUserSchema } from '@/lib/schemas'
export async function POST(req: Request) {
const body = await req.json()
// Bad: no validation, body is `any`
// const user = await db.user.create({ data: body })
// Good: parse input through the schema
const data = CreateUserSchema.parse(body) // throws on invalid input
const user = await db.user.create({ data })
return Response.json(user)
}
For form actions, use a resolver: useForm({ resolver: zodResolver(CreateUserSchema) }). For tRPC, pass the schema via .input(CreateUserSchema).