Last week I put a fake DSN host behind a small Express app to log what the Sentry SDK actually sends. I went in for the byte counts. The thing I keep thinking about a week later is one line in a stored error event:
request.data = {"items":[1,2],"card_last4":"4242"}
That's the JSON body of a checkout request, saved inside the error. I never set sendDefaultPii. This was @sentry/node 11.9.0 with Express 4.22.3 and close to default config. The SDK did replace the session cookie with [Filtered] on its own. It left the body alone, and it attached the client IP as user.ip_address.
My test body was fake. A real one could be a password reset form, a signup with an email and phone number, or a webhook payload with a customer's address in it.
Why I can't shrug this off
On a SaaS plan, the vendor usually has server-side scrubbing and a retention policy, and you clicked through their DPA at some point. Self-hosted, it's my disk. The body sits in my Postgres, in last night's pg_dump, in the backup bucket, for as long as the retention I set (or forgot to set).
And the body is the part I almost never read when I debug. In that run the whole error event was 7,234 bytes. The exception (type, message, stack frames) was 4,026 of it and the modules list was 1,588. When I deleted the body, cookies and IP in beforeSend, the event went to 7,099 bytes. I dropped 135 bytes and nothing I'd look at for a 500.
beforeSend(event) {
if (event.request) {
delete event.request.data;
delete event.request.cookies;
delete event.request.headers?.cookie;
}
if (event.user) delete event.user.ip_address;
return event;
}
Where I'm not sure
Deleting the whole body is the blunt option. Some bugs only make sense with the payload: a validation error on a field you didn't expect, or a client sending null where you assumed a string. A denylist (password, card*, token, email) keeps that context, but it only covers the fields you thought of. The day someone adds a tax_id field to a form, it lands in your error store until somebody notices.
Where I've landed for now: delete by default, and when I'm chasing one specific bug, log the part of the body I need at the call site, on purpose, then remove it.
I'm building Epure, a lightweight self-hosted error tracker, and this is the default I keep going back and forth on. Whatever a tool does out of the box is what most teams end up running in production, so I'd rather hear from people with real traffic before I pick.
What does your beforeSend do with event.request.data right now: nothing, a key denylist, or delete the whole body?
Top comments (1)
On mobile I ended up on the other side of this. Crashlytics doesn't attach request bodies on its own, so a body only reaches an error report if our own code puts it in the log message. That moved the problem from config to habit: everything goes through one logger, and before anything is sent it replaces API keys, bearer tokens, JWTs, emails and phone-number-shaped strings with ***, and cuts long quoted strings, since those are usually what the user said.
It's a denylist, so it has exactly the gap you describe: it only knows the shapes we thought of. What I trust more is the rule at the call site: log the status code, the length, the name of the field that failed, not the value. "Validation failed on tax_id (string, 14 chars)" is usually enough to start.
For a default in your tool I'd lean to your answer: delete the body, and make keeping it a visible opt-in per route, not a global switch. A global "send bodies" flag gets turned on for one bug and never turned off.