brackt/app/lib/error-reporting.ts
Claude 41a0237a87
Stop reporting bot-scanner 404s to Sentry
Automated scanners probing for WordPress paths (/blog/wp/v2/posts/*,
POST /) generate a React Router 404 or 405 on every hit, and handleError
forwarded all of them to Sentry, exhausting the quota. The existing
defence was a list of ignoreErrors regexes that needed a new entry for
each scanner pattern.

Filter on what the error is instead: shouldReportServerError drops 4xx
responses React Router generated itself (internal: true) for requests
that matched nothing. Responses the app throws deliberately still report,
as do all 5xx and real exceptions.

A request carrying a same-origin Referer is still reported, so a broken
internal link remains visible in Sentry -- only cold scanner hits are
dropped. No HTTP response changes; every URL returns the status and page
it did before.

Also adds /blog to the Express bot-probe filter so the highest-volume
path 404s without an SSR render, and removes the now-redundant
route-404 ignoreErrors entries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ESrxBMZMx2BD9rcKTCgLe4
2026-09-14 20:11:46 +00:00

49 lines
1.9 KiB
TypeScript

/**
* 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);
}