Rendering error.message or error.stack to the DOM or returning it in an API JSON response exposes your internal architecture to anyone who triggers a 500 — framework versions, file system paths, database error codes, and third-party service identifiers are all visible in unfiltered stack traces. OWASP A05 (Security Misconfiguration) and CWE-209 (Generation of Error Message Containing Sensitive Information) classify this as an active information-disclosure vulnerability, not merely a cosmetic issue. Attackers routinely trigger intentional errors to enumerate your stack before targeting known CVEs. CWE-215 (Insertion of Sensitive Information Into Debugging Code) further applies when dev-mode conditionals are left in production builds.
Critical because stack traces and raw error messages in production responses give attackers a direct map of your framework, file paths, and database schema.
Replace all {error.message} or {error.stack} renders with a static user-safe message. Log internally, never to the DOM.
// app/error.tsx — safe version
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
useEffect(() => { reportError(error) }, [error])
return (
<div>
<h2>Something went wrong</h2>
<p>We've been notified. Please try again.</p>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
For API routes:
} catch (error) {
console.error('[API Error]', error) // server-side only
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
Verify with NODE_ENV=production by triggering a deliberate error and inspecting both the response body and rendered DOM for any technical details.
ID: saas-error-handling.error-boundaries.five-hundred-page-no-internals
Severity: critical
What to look for: Examine the server error page(s): app/error.tsx, app/global-error.tsx, pages/_error.tsx, pages/500.tsx, or equivalent custom error pages for non-Next.js frameworks. Check whether the error object's properties (error.message, error.stack, error.cause) are rendered to the DOM. Check for conditional logic that might expose stack traces in production (e.g., process.env.NODE_ENV === 'development' checks that render the full error). Also check API route error handlers — do they return the raw error.message or stack in JSON responses?
Pass criteria: Count all error pages and API error response handlers. Pass if error pages and API error responses do not render or return the error.stack, raw error.message, database error details, or file system paths to end users. No more than 0 locations may expose internal error details. Extract and quote the first error response pattern you find to verify it does not leak internals. A generic "Something went wrong" message with a request ID or support code is acceptable and good practice. Report even on pass: "X error response locations examined, 0 expose internal details."
Fail criteria: Fail if {error.message} or {error.stack} is rendered in JSX. Fail if API routes return { error: error.message } or { stack: error.stack } directly. Do NOT pass when error.message is displayed in a toast or alert — the message may contain internal details that vary by error type.
Skip (N/A) when: Never — every web application can encounter a 500 condition and must handle it safely.
Detail on fail: Identify the exact location where internals are exposed (e.g., "app/error.tsx renders {error.message} directly; api/users/route.ts returns raw error.message in catch block"). Max 500 chars.
Remediation: Exposing stack traces and internal error messages in production gives attackers a roadmap to your application's internals — framework versions, file paths, database schemas, and third-party service names are all visible in unfiltered error output.
Replace internal error details with user-safe messages:
// app/error.tsx — safe version
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
// Log internally, never render error.message or error.stack to the DOM
useEffect(() => { reportError(error) }, [error])
return (
<div>
<h2>Something went wrong</h2>
<p>We've been notified and are looking into it. Please try again.</p>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
For API routes:
} catch (error) {
console.error('[API Error]', error) // server-side only
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
Verification: trigger a deliberate error in production mode (NODE_ENV=production) and inspect the response body and rendered DOM for any technical details.