# Validation failures return useful error responses

- **Pattern:** `ab-000278` (`ai-slop-security-theater.unenforced-validation.error-responses-handle-validation-failures`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000278
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000278)

## Why it matters

When `Body.parse(body)` throws a `ZodError` inside an uncaught handler, most frameworks return a generic 500 Internal Server Error. The client has no idea what field was invalid, and your server logs expose the full Zod error tree — which reveals your schema structure to anyone reading logs or observing error responses. CWE-755 (Improper Handling of Exceptional Conditions) applies: a validation library that crashes the handler instead of returning a 400 teaches clients nothing useful and may leak internal schema details.

## Severity rationale

Low because the primary failure is poor error communication and log leakage rather than direct exploitation, though repeated 500s from malformed inputs can mask real errors.

## Remediation

Use `.safeParse()` to get a typed success/error result without throwing, then return an explicit 400 with the validation issues.

```ts
export async function POST(req: Request) {
  const body = await req.json()
  const result = Body.safeParse(body)
  if (!result.success) {
    return Response.json(
      { error: 'Invalid input', issues: result.error.issues },
      { status: 400 }
    )
  }
  const user = await prisma.user.create({ data: result.data })
  return Response.json(user, { status: 201 })
}
```

Alternatively, wrap `.parse()` in a try/catch and return a 422 with a sanitized error message. Avoid forwarding the raw `ZodError.issues` array to clients in production — it reveals your schema.

## Detection

- **ID:** `error-responses-handle-validation-failures`
- **Severity:** `low`
- **What to look for:** For mutating handlers that invoke validation, count all validating handlers and enumerate how many wrap the validation in a try/catch OR use `.safeParse(` (which doesn't throw) AND return a structured 4xx response on validation failure. Handlers that let validation throw an unhandled exception (defaulting to a generic 500) fail this check.
- **Pass criteria:** 100% of validating mutating handlers either use `.safeParse(` and return a 4xx for failures OR wrap `.parse(` in try/catch and return a 4xx. Report: "X validating handlers, Y with explicit error handling, 0 throwing unhandled."
- **Fail criteria:** At least 1 validating handler has no try/catch around `.parse(` and no explicit 4xx response.
- **Skip (N/A) when:** Project has 0 mutating handlers OR no mutating handler invokes validation.
- **Detail on fail:** `"1 handler returns 500 on validation failure: app/api/users/route.ts POST calls Body.parse(body) without try/catch — invalid input causes ZodError to leak as a 500"`
- **Remediation:** A validation failure should be a 400 Bad Request with a clear error, not an unhandled 500:

  ```ts
  export async function POST(req: Request) {
    const body = await req.json()
    const result = Body.safeParse(body)
    if (!result.success) {
      return Response.json(
        { error: 'Invalid input', issues: result.error.issues },
        { status: 400 }
      )
    }
    const user = await prisma.user.create({ data: result.data })
    return Response.json(user, { status: 201 })
  }
  ```

---

## External references

- cwe CWE-390
- cwe CWE-755

Taxons: error-resilience

HTML version: https://auditbuffet.com/patterns/ab-000278
