Advanced

Privacy & Scrubbing

What Reliable does to keep PII out of your event payloads, and what you should do on top of those defaults.

Built-in scrubbing#

Several layers of automatic PII protection are wired in by default. None of them are perfect, they're heuristics, but they remove the most common leaks before payloads ever leave the browser.

String scrubbing#

Any string that the SDK serializes (URLs, breadcrumb messages, error messages, browser_state values) is run through a regex scrubber. The scrubber masks:

  • Email addresses → [email]
  • Credit-card-shaped numbers (13-19 consecutive digits) → [card]
  • JWT tokens (3 dot-separated base64 segments) → [token]
  • API keys in URLs (?api_key=..., ?token=...) → [redacted]

Cookies & localStorage#

When an error fires, Reliable snapshots document.cookie and localStorage as part of the error's browser_state. Before sending:

  • Keys matching /token|secret|password|auth(?!or)/i are fully redacted (value replaced with [REDACTED]).
  • Other values are truncated to 200 characters and run through the string scrubber above.

Session replay#

rrweb is the most invasive piece of the SDK: it records the page as the user sees it. By default:

  • Every <input> value is masked, password fields included (typed characters show as *).
  • Page text is recorded as it is. Mask it with data-rl-mask, or leave an element out entirely with data-rl-block.
  • Cross-origin iframes are not recorded; same-origin ones are.

See Session Replay for how the masking attributes work. From SDK 1.5.1, data-rr-mask and data-rr-block (the names these docs used before) work too.

Adding your own scrubbing#

For app-specific PII patterns (internal IDs, custom token formats, anything the defaults miss), use beforeSend:

typescript
init({
  publicKey: 'pk_live_rl_...',
  beforeSend(event) {
    // Strip our internal customer IDs from URLs
    if (typeof event.url === 'string') {
      event.url = event.url.replace(/cust_[a-zA-Z0-9]+/g, 'cust_[id]');
    }
    return event;
  },
});

What every event payload contains#

Even with all scrubbing in place, every event includes:

  • The current path (URL pathname + query, with sensitive query params redacted and email addresses removed)
  • User agent string
  • Viewport dimensions
  • The session UUID (random per session, no PII)
  • For clicks: a scrubbed CSS selector and coordinates. The clicked element's text is never sent.
  • If identify() was called: the externalId, email, name, and traits you provided

The SDK also keeps a few entries in the page's own storage, all first-party and none containing personal data: in localStorage, reliable:session (the current visit, shared by your tabs, ended after 30 minutes without activity) and reliable:visitor (a random anonymous visitor id used to count unique and returning visitors). With replay on, also reliable:tab in sessionStorage (a random id that keeps each tab's replay separate) and an IndexedDB database, reliable_replay_v2, holding the last 70 seconds of recording.

This is the data you control. Reliable doesn't fingerprint, doesn't read storage you didn't authorize, doesn't make third-party requests.

GDPR / consent flows#

For GDPR-compliant apps, gate the entire SDK behind consent. The simplest pattern:

typescript
async function bootstrap() {
  const consent = await waitForConsent();
  if (consent.analytics) {
    init({ publicKey: 'pk_live_rl_...' });
  }
}

Once init() has been called, you cannot un-init it. Reload the page to fully tear down the SDK. (For SPAs that need conditional behavior at runtime, prefer dropping events in beforeSend based on a global consent flag.)

No automatic system is bulletproof

Scrubbing is heuristic. If your domain has unique patterns (e.g. patient IDs in HIPAA, account numbers in finance), audit your event payloads in the dashboard and add custom beforeSend rules before going to production.