@@ -2,6 +2,7 @@ import { createLogger, runWithRequestContext } from '@sim/logger'
22import { getErrorMessage } from '@sim/utils/errors'
33import type { NextRequest } from 'next/server'
44import { NextResponse } from 'next/server'
5+ import { HttpError } from '@/lib/core/utils/http-error'
56import { generateRequestId } from '@/lib/core/utils/request'
67
78const logger = createLogger ( 'RouteHandler' )
@@ -12,20 +13,24 @@ type RouteHandler<T = unknown> = (
1213) => Promise < NextResponse | Response > | NextResponse | Response
1314
1415/**
15- * Reads a numeric `statusCode` (4xx or 5xx) off an Error so typed domain errors
16- * (e.g. `WorkspaceAccessDeniedError`, `InvalidFieldError`) map to the correct
17- * HTTP status when they bubble up unhandled instead of defaulting to 500.
16+ * Reads a numeric `statusCode` (4xx or 5xx) off an `HttpError` so typed domain
17+ * errors (e.g. `WorkspaceAccessDeniedError`, `InvalidFieldError`) map to the
18+ * correct HTTP status when they bubble up unhandled instead of defaulting to
19+ * 500.
20+ *
21+ * Uses an `instanceof HttpError` check (not duck-typing on `statusCode`) so
22+ * third-party errors that happen to carry a `statusCode`-shaped field cannot
23+ * trigger this path and leak their internal `message` to the client.
1824 *
1925 * When a typed status is returned, the error's `message` is sent to the client
2026 * verbatim — matching the NestJS `HttpException` / Spring `ResponseStatusException`
21- * convention. The safety contract is convention-based: only attach `statusCode`
22- * to errors whose `message` is safe to expose to clients (no stack traces,
23- * secrets, file paths, ORM internals). Untyped errors fall back to a generic
24- * 500 response with no message exposure.
27+ * convention. Subclasses of `HttpError` are responsible for keeping `message`
28+ * safe to expose to clients (no stack traces, secrets, file paths, ORM
29+ * internals).
2530 */
2631function readTypedErrorStatus ( error : unknown ) : number | undefined {
27- if ( ! ( error instanceof Error ) ) return undefined
28- const status = ( error as { statusCode ?: unknown } ) . statusCode
32+ if ( ! ( error instanceof HttpError ) ) return undefined
33+ const status = error . statusCode
2934 if ( typeof status !== 'number' ) return undefined
3035 if ( status < 400 || status >= 600 ) return undefined
3136 return status
0 commit comments