DEV Community

Cover image for Do you scrub request bodies before they reach your error tracker?
Amorizz
Amorizz

Posted on Edited on

Do you scrub request bodies before they reach your error tracker?

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"}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
ahmedznouda profile image
Ahmed ZNOUDA •

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.