PII filter
The PII filter runs server-side at the collector before any event is persisted. It is on by default, has no config knob to turn off, and is defense-in-depth on top of any client-side care your developers take.
What gets stripped
Section titled “What gets stripped”| Pattern | Where | Replacement |
|---|---|---|
Emails (name@host.tld) | event.url query string, props.* string values, traits.* string values, error stack frames | [email] |
| Credit-card-shaped tokens (Luhn-valid 13–19 digit sequences) | Same surfaces as emails | [card] |
email=… query parameters in any URL | event.url, event.referrer, props.* URL-shaped values | Parameter dropped entirely |
token=…, auth=…, api_key=…, password=… query parameters | Same surfaces | Parameter dropped entirely |
The filter runs across every string-shaped value in the wire payload, recursively — there is no opt-out per field.
Why server-side
Section titled “Why server-side”A client-side filter is still useful (it stops a careless track('shared', { url: 'https://example.com/?token=abc' }) from leaving the device). But
client-side enforcement is bypassable — a malicious or buggy client can
construct a request directly. Server-side is the policy layer:
- The collector strips before any storage write.
- Even custom collectors / webhook adapters that bypass the SDK pass through this layer.
- The filter is benchmarked as part of the p99 < 5 ms ingest budget — it is not an afterthought.
What this is NOT
Section titled “What this is NOT”- Not a phone-number scrubber. Phone formats vary too widely across locales for a generic regex; if you need this, sanitize at the source.
- Not an address-of-record scrubber. Postal addresses, names, identifiers — the filter has no idea what those look like in your domain. If you persist them, you persist them.
- Not a PCI-compliance black box. The card-shaped-token filter is a best-effort net for accidental leaks; do not rely on it as your sole defense for handling actual card data. Tokenize at the payment processor.
How the filter behaves in errors
Section titled “How the filter behaves in errors”The errors module emits stack traces. Stack frames often contain the full script URL with a session token in the query string. The filter strips both:
- Query-string params named
token/auth/api_key/passwordare dropped before the stack lands inprops.stack. - Inline emails and card-shaped tokens in
props.messageandprops.stackare replaced with[email]/[card].
How to audit
Section titled “How to audit”Every event that had a PII strip applied is logged at debug level:
level=debug reason=pii_stripped event_id=… site_id=… count=2 fields=props.message,event.urlPipe these to your log aggregator if you want to track filter activity. The structured-log fields are operator-facing and may evolve in any release; do not parse them programmatically.
Defense-in-depth recommendations
Section titled “Defense-in-depth recommendations”- Never put PII in URL query strings. It ends up in browser history, referrer headers, server access logs, and CDN logs in addition to Leatsmap. The filter is a last line.
- Pre-redact at the source. When you know a string contains a user
email, drop it before you call
track(). - Use the tracking plan
additionalProperties: false. That alone catches “I accidentally added anemailprop” at the validator before the filter has to.