// server/middleware/sentry-payload.ts // Attaches the (secret-scrubbed) request payload + query to the per-request Sentry scope so // captured server errors show what was actually sent. Zero-touch on the ~1177 routes: // errorHandlingHelper's captureException automatically merges this isolation scope into the // event. The context is only transmitted when an error is captured (>= 500) — see // server/utils/errorHandlingHelper.ts. Read-only w.r.t. the request: it never mutates the // request/response/event.context, and self-disables when SENTRY_DSN is empty. import * as Sentry from '@sentry/nuxt' import { scrubPayload } from '../../sentry.scrub' const WRITE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']) export default defineEventHandler(async (event) => { // Only when Sentry is enabled; never disturb the request otherwise. if (!process.env.SENTRY_DSN) return try { const ctx: any = { method: event.method, path: event.path } // Query params (all methods) — failing GET list/filter endpoints show their inputs. const query = getQuery(event) if (query && Object.keys(query).length) ctx.query = scrubPayload(query) // JSON write bodies only — skips multipart uploads & SSE streams (non-JSON content-type), // so file-upload / streaming routes are never consumed early. if (WRITE_METHODS.has(event.method)) { const contentType = getHeader(event, 'content-type') || '' if (contentType.includes('application/json')) { // h3 caches the parsed body on the event, so the route's own readBody() still works. const body = await readBody(event) if (body !== undefined) ctx.body = scrubPayload(body) } } // Isolation scope (not current scope): shared across the whole request regardless of // span forking, so it's present when errorHandlingHelper captures later. Sentry.getIsolationScope().setContext('request_payload', ctx) } catch { /* telemetry must never break a request */ } })