Session Replay Privacy: Masking PII Without Losing the Insight

The fastest way to understand session replay privacy is to picture the failure. A customer, mid-checkout, fat-fingers their sixteen-digit card number into the promo-code box instead of the card field. Your replay tool wasn't told the promo box was sensitive, so it recorded every keystroke. Now the CVV is sitting in a third-party vendor's database, in plain text, next to the customer's IP and email. Nobody decided that would happen. It happened because masking was scoped to the fields you expected, and users don't stay inside the fields you expect.

So the short answer: masking PII in session replay without losing the insight means recording layout, clicks, scrolls and error states while redacting the content of anything a person types or reads that could identify them. You keep the shape of the behavior and throw away the private payload. The rest of this is about how to draw that line, and how to prove you drew it correctly before you ship.

Start from the failure mode, not the feature

I've spent enough years in privacy engineering to distrust any masking config that was written optimistically. The optimistic version says "we mask the password field and the card field, we're fine." The pessimistic (correct) version asks: what happens when the data lands somewhere it shouldn't?

This isn't paranoia, it's the actual litigation surface. Session replay has driven a wave of wiretap class actions in the US, and the complaints read exactly like the promo-box scenario. In the Nike/FullStory suit, the plaintiff argued replay was "dangerous" precisely because it recorded "sensitive user information such as passwords and credit card numbers." Loeb & Loeb's July 2025 write-up on session-replay legal risk lists the same trigger: recording sensitive input without clear disclosure or a lawful basis. Defendants have been winning a lot of these lately (the Ninth Circuit affirmed in Thomas v. Papa John's on June 18, 2025 that FullStory's use fell within the wiretap "party exception," per Covington's 2025 wiretapping roundup), but "we eventually won the lawsuit" is not a privacy program.

There's a concept I keep coming back to: the creepy line. It's the point where a user, if they watched their own replay over your shoulder, would feel surveilled rather than helped. Masking is how you stay on the safe side of that line. Everything below is downstream of one question — would this make the person on the recording uncomfortable if they saw it?

What "PII" actually means inside a replay

Regulators are less vague than people assume. GDPR Article 5(1)(c) requires that personal data be "adequate, relevant and limited to what is necessary in relation to the purposes for which they are processed." In plain speech: don't collect what you don't need. Article 25 goes further with "data protection by design and by default." The default configuration has to be the private one, not the one you have to remember to turn on.

Translate that to a replay and three buckets fall out:

Content a user enters, meaning form inputs, search queries, chat messages. Highest risk, because it's where card numbers, national IDs, health details and free-text confessions live. Content a user reads that's specific to them: an account balance, an order history, a diagnosis on a patient portal. And metadata around the session, like IP address, precise geolocation, anything that re-identifies an otherwise anonymous recording.

The behavioral layer (where they clicked, how long they hovered, where the rage-clicks clustered, which step they abandoned) is almost never PII on its own. That's the insight you're trying to keep. Good masking is surgical. It removes the first three buckets and leaves the fourth intact.

Block, mask, or allowlist: the decision that actually matters

Most tools give you three verbs, and teams conflate them constantly. They're not the same, and the difference is exactly what leaks.

Using rrweb's vocabulary (the open-source engine under a lot of web replay, including PostHog and Kixo), per the rrweb masking guide:

Verb rrweb mechanism What the replay shows Use it for
Block .rr-block / blockClasses A blank placeholder box, same dimensions Whole regions you never need: a payment iframe, an embedded doc viewer
Mask .rr-mask / maskTextClass Layout preserved, text replaced with asterisks Text and inputs where you want the shape but not the words
Ignore .rr-ignore / ignoreClasses Element records, but input events are dropped Fields where even keystroke timing is too much

The strategic fork sits upstream of those verbs, though. It's whether your baseline is mask-by-default (mask everything, then allowlist the safe stuff back in) or capture-by-default (record everything, then blocklist the sensitive stuff out). Here's the honest comparison.

Mask-by-default (allowlist) Capture-by-default (blocklist)
Failure mode You over-mask, lose some insight, notice it and un-mask You under-mask, leak PII, find out via a subject-access request or a lawsuit
New field ships without config Safe (masked) Exposed (recorded)
Effort to get useful replays Higher up front — you allowlist deliberately Lower up front, higher forever after
Regulator's reading of Article 25 Matches "by default" Fights it

I'll take a side, because hedging here is dishonest. Mask-by-default is the only defensible baseline for anything touching regulated data. The two failure modes are not symmetric. Over-masking costs you a slightly duller replay that you can fix on Tuesday. Under-masking costs you a plaintext card number you can't un-leak. A blocklist assumes you can enumerate every sensitive field in a product that ships weekly. You can't, and the promo-box story is what that assumption looks like in production.

The one caveat: mask-by-default genuinely does blunt some insight, and for a low-risk marketing site with no logins and no forms, capture-by-default with a tight blocklist can be reasonable. Match the baseline to the data, not to a dogma.

What the major tools mask out of the box

Defaults matter more than capabilities, because the default is what runs on the day someone forgets to configure the thing. Here's where the common tools land, drawn from vendor docs and Lokker's 2026 GDPR session-replay comparison. This describes default behavior, not the ceiling. Most of these can be configured to mask far more.

Tool Text inputs masked by default? Notes
PostHog Yes Masks input elements by default as "highly likely to contain sensitive text such as email or password," per PostHog's privacy docs. All-text masking is opt-in.
Mouseflow Yes Lokker groups it with the mask-all-inputs-by-default tools.
Glassbox Yes Same group; aggressive default masking.
Kixo Yes Input masking on by default for web (rrweb) and native iOS/Android capture; allowlist-based approach to un-mask deliberately.
FullStory Passwords only by default Lokker notes it needs extra configuration beyond defaults for comprehensive masking.
LogRocket Passwords only by default Same — configurable, but the default isn't mask-all-text.
Microsoft Clarity Configurable (Balanced default) Masks text/numbers by default in its "Balanced" mode; can be dialed up or down.

Kixo shows up here as one option among several. It's a chat-first product analytics platform that does web replay via rrweb plus native iOS/Android capture with input masking on by default. Whether that's the right call for you depends on the same trade-offs as everyone else's tool: default posture, native support, and how much you're willing to configure. There is no tool where turning it on and walking away is the responsible move.

Two things this table can't tell you. First, "masked by default" only covers the fields the tool recognizes as inputs. The promo-box problem is a gap in what counts as sensitive, not in whether masking is on. Second, native mobile masking (iOS/Android) works differently from DOM masking. There's no CSS class to hang a .rr-mask on a UIKit view, so mobile masking relies on view-level flags and framework hooks. If you're recording apps, verify the mobile masking separately. I've watched teams assume their web config carried over. It doesn't.

The masking checklist that actually holds up

A config that looks right in a spec is not the same as a config that redacts a real card number. The only test that counts is: type sensitive data into your live app, watch the replay, and confirm you can't read it back. Do this before every launch that touches a new form.

Here's the sequence I run:

  1. Set the baseline to mask. Confirm all text inputs and text nodes mask by default, then allowlist the genuinely safe, non-personal fields back in (a product-category dropdown, a quantity stepper).
  2. Block the untouchable regions. Payment iframes, ID-upload widgets, any embedded third-party form — block the whole region, don't rely on field-level masking inside something you don't control.
  3. Adversarially test the wrong fields. Type a fake card number into the promo box, the search bar, the coupon field, the "notes to seller" textarea. Anywhere a panicking user might. Watch the replay. If any of those digits survive, your baseline is a blocklist wearing an allowlist's clothes.
  4. Test the read-side, not just inputs. Load an account page with a balance, an order history, a masked-but-present SSN. Confirm the values render as asterisks in the replay, not just in the input boxes.
  5. Check the network payload, not the player. The replay UI might display asterisks while the raw event stream shipped the real value. Open the network tab, inspect the actual recording payload, grep it for the test data you typed. This is the step people skip, and it's the one that catches the real leaks.
  6. Verify mobile and web separately. If you record both, run steps 3–5 on each platform. Different masking mechanisms, different gaps.
  7. Confirm retention and deletion. Even perfectly masked sessions shouldn't live forever. JustAnalytics' 2025 field guide puts the common GDPR-aligned window at 30–90 days; set it, then verify the auto-deletion actually fires rather than trusting the setting exists.

Step 5 is the one I'd tattoo on people if I could. A masked player with an unmasked payload is the worst outcome, because it feels safe. You looked, you saw asterisks, you shipped. Meanwhile the plaintext is in transit and at rest. Always check the wire.

Masking reduces the sensitivity of what you record. It does not establish that you were allowed to record at all. Those are two different obligations, and conflating them is a common way to be technically careful and legally exposed at the same time.

Under GDPR you still need a lawful basis (usually legitimate interest or consent, depending on the data), you still need to honor a Global Privacy Control signal, and you still need to stop recording when someone opts out. A recording that's beautifully masked but was captured after the user rejected analytics is still an unlawful recording. If you're wiring replay into a broader stack, the consent plumbing deserves its own design. Connecting a consent management platform to your capture pipeline is where that logic should live, not buried in a masking config.

And if you want the shared vocabulary for the behavioral signals you're keeping after all this redaction (rage clicks, dead clicks, session depth), the behavior analytics glossary covers the terms without the PII.

The honest trade-off

You will lose some insight to masking. That's real, and I won't pretend the mask-everything replay is as rich as the record-everything one. When every input reads as asterisks, you lose the ability to see what someone searched, only that they searched and bailed. Sometimes that content was the insight.

The resolution isn't to mask less. It's to get the non-content signal (the where, the how-often, the abandonment point) from the replay, and get the content signal from a separate, consented, aggregated place designed to hold it. Replay tells you the checkout broke at step three for 14% of mobile sessions. It should not tell you Maria Alvarez's card number. Keep those two facts in two different systems, and you can be genuinely useful and still stay on the right side of the creepy line.

Draw the line at the failure mode. Test it by trying to break it. Check the wire, not the player. Do those three things and masking stops being a compliance checkbox and starts being what it should've been all along: the reason you can watch real users without watching over them.