Skip to content

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.

PatternWhereReplacement
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 URLevent.url, event.referrer, props.* URL-shaped valuesParameter dropped entirely
token=…, auth=…, api_key=…, password=… query parametersSame surfacesParameter dropped entirely

The filter runs across every string-shaped value in the wire payload, recursively — there is no opt-out per field.

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.
  • 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.

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 / password are dropped before the stack lands in props.stack.
  • Inline emails and card-shaped tokens in props.message and props.stack are replaced with [email] / [card].

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.url

Pipe 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.

  1. 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.
  2. Pre-redact at the source. When you know a string contains a user email, drop it before you call track().
  3. Use the tracking plan additionalProperties: false. That alone catches “I accidentally added an email prop” at the validator before the filter has to.