/** * Decides which server-side errors are worth sending to Sentry. * * Automated scanners probe for CMS paths that have never existed here * (`/blog/wp/v2/posts/999999`, `/wp-login.php`, a bare `POST /`). React Router * throws for each one — a 404 when no route matches, a 405 when a route has no * `action` — and every throw reaches `handleError` in `app/entry.server.tsx`. * Reporting those burns the Sentry quota without ever describing a real bug. */ import { isRouteErrorResponse } from "react-router"; /** React Router stamps `internal: true` on the errors it generates itself. */ function isInternalRouterError(error: unknown): boolean { return (error as { internal?: unknown }).internal === true; } /** True when the request was linked from a page on this same origin. */ function hasSameOriginReferer(request: Request): boolean { const referer = request.headers.get("referer"); if (!referer) return false; try { return new URL(referer).origin === new URL(request.url).origin; } catch { // Scanners send garbage in this header; a referer we can't parse isn't ours. return false; } } /** * Whether `error` should be reported to Sentry. * * Drops only the 4xx responses React Router generated for a request that * matched nothing — that is, unrecognised URLs and methods. Everything else is * reported, including responses the app threw deliberately (`internal: false`), * so a 403 from an ownership check still shows up. * * The exception is a request carrying a same-origin `Referer`: a 404 reached * from one of our own pages is a broken internal link, not a scanner, and stays * visible in Sentry. */ export function shouldReportServerError( error: unknown, request: Request, ): boolean { if (!isRouteErrorResponse(error)) return true; if (!isInternalRouterError(error)) return true; if (error.status < 400 || error.status > 499) return true; return hasSameOriginReferer(request); }