# Generic error messages are shown to users; no raw exceptions or stack traces exposed

- **Pattern:** `ab-001169` (`ecommerce-payment-security.fraud-prevention.generic-error-messages`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001169
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001169)

## Why it matters

Returning raw exception messages, Stripe error objects, or database errors from payment API routes leaks internal system architecture to every browser that inspects network responses. A response like `{ error: "No such payment_intent: 'pi_3Ox...'" }` tells an attacker your payment intent ID format, your Stripe account configuration, and the exact server-side path that failed. CWE-209 (Generation of Error Message Containing Sensitive Information) directly names this pattern. OWASP A05 (Security Misconfiguration) flags information leakage from unhandled exceptions as a reconnaissance vector. Beyond security, raw Stripe error messages contain decline codes and internal identifiers that help card-testing bots distinguish working cards from flagged ones.

## Severity rationale

Medium because raw exception messages in API responses expose internal system details and Stripe error internals that serve as reconnaissance data for card-testing attacks and targeted exploits.

## Remediation

Catch and classify errors in payment routes; return only generic user-facing messages while logging the full details server-side.

```ts
// app/api/payment/route.ts
try {
  const paymentIntent = await stripe.paymentIntents.confirm(id)
  return NextResponse.json({ status: paymentIntent.status })
} catch (err) {
  if (err instanceof Stripe.errors.StripeCardError) {
    console.error('Card error', { code: err.code, decline_code: err.decline_code, id })
    return NextResponse.json(
      { error: 'Your card was declined. Please try a different payment method.' },
      { status: 402 }
    )
  }
  console.error('Payment error', err)
  return NextResponse.json(
    { error: 'Payment could not be processed. Please try again.' },
    { status: 500 }
  )
}
```

Never return `err.message`, `err.stack`, or a serialized error object in the response body. The safe surface to the client is a human-readable string you control.

## Detection

- **ID:** `generic-error-messages`
- **Severity:** `medium`
- **What to look for:** Count every error response path in payment API routes. For each catch block or error handler, classify the response as safe (generic message like "Payment failed") or unsafe (raw `err.message`, `err.stack`, or full error object serialization). Check for patterns like `return NextResponse.json({ error: err.message })` or `res.status(500).json(err)` that would forward raw error messages. Report the ratio: "X of Y error paths return safe generic messages."
- **Pass criteria:** 100% of payment-related API error responses return generic, user-friendly messages that do not reveal internal system details, stack traces, database error messages, or payment provider error internals. No more than 0 error paths should expose raw exception details. Detailed error information is logged server-side only.
- **Fail criteria:** Any payment API error response returns a raw exception message, a database error string, a stack trace, or an unfiltered Stripe error object to the client.
- **Do NOT pass when:** Error responses include `err.message` from Stripe errors — Stripe error messages contain internal details like `"No such payment_intent: 'pi_xxx'"` that should not reach the client. Forwarding `err.message` is NOT a pass.
- **Skip (N/A) when:** Never — this applies to all payment-related API routes.
- **Detail on fail:** Describe the exposed information. Example: `"POST /api/payment returns the full Stripe StripeCardError object including decline_code and charge ID in the JSON response — internal payment details are exposed to the browser"`
- **Cross-reference:** The Error & Resilience audit covers error exposure and information leakage across all API routes, not limited to payment endpoints.
- **Remediation:** Sanitize errors before sending to the client. Log detailed errors server-side:

  ```ts
  // app/api/payment/route.ts
  export async function POST(req: Request) {
    try {
      const paymentIntent = await stripe.paymentIntents.confirm(paymentIntentId)
      return NextResponse.json({ status: paymentIntent.status })
    } catch (err) {
      if (err instanceof Stripe.errors.StripeCardError) {
        // Log the full error server-side for diagnostics
        console.error('Card error', { code: err.code, decline_code: err.decline_code })

        // Return only a safe, generic message to the client
        return NextResponse.json(
          { error: 'Your card was declined. Please try a different payment method.' },
          { status: 402 }
        )
      }

      // Generic server error — do not expose internals
      console.error('Payment processing error', err)
      return NextResponse.json(
        { error: 'Payment could not be processed. Please try again.' },
        { status: 500 }
      )
    }
  }
  ```

## External references

- cwe CWE-209
- owasp A05

Taxons: injection-and-input-trust, observability

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