> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # Protecting user privacy – Session replay pocket guide [](/pocket-guides.md)Aa [](/pocket-guides/session-replay/configure-replays.md)[](/pocket-guides/session-replay/reverse-proxy.md) # Protecting user privacy – Session replay pocket guide Session Replay gives you insights into user behavior, but it can be invasive if misconfigured. Here are the important privacy controls you should know for each platform, but don't sweat the implementation details, there's a skill you can copy at the end of the guide. ## Mask first, allow explicitly If a page could contain sensitive data, mask **all** text, then unmask safe content explicitly. You should never allow captures by default. Trying to mask fields individually becomes a dangerous game of whack-a-mole. You'll only notice when you watch a replay with private user information, which is too late. ## Where each platform starts Each SDK has different default masking behavior and supports different masking rules. | Platform | Masked out of the box | Can you unmask individual elements? | | --- | --- | --- | | Web | Inputs masked, all other text recorded as it appears | Yes, but through a callback rather than a selector | | Android | Text and images masked, recorded as a wireframe | Yes, and revealing beats masking | | iOS | Text and images masked, recorded as a wireframe | Yes, and revealing beats masking | | React Native | Text, inputs, and images masked, always a screenshot | No – only an app-wide default | | Flutter | Text and images masked, always a screenshot | No – only an app-wide default | ## Blocking is not masking [Masking](/docs/session-replay/privacy.md) asterisks the text and keeps the layout. Blocking replaces the element with a blank box, and also stops [autocapture](/docs/product-analytics/autocapture.md) events firing from inside it – so you lose the clicks as well as the pixels. Mask unless you need the thing gone completely. ## Blindspots Mask every sensitive screen and you've covered what's visible. There are four things that still go out regardless of masking: - **The URL.** The player shows it and the timeline lists it. Query strings carry auth tokens, emails, and user IDs, and no amount of text masking touches them. - **Network payloads.** Request and response bodies and headers, if you've turned network capture on. One callback covers both these and the URL. - **Canvas.** If you record canvas, you're recording pixels. No selector reaches inside one. - **Element attributes.** A masked element can still ship a `title`, an `alt`, or a `value` carrying the thing you masked. Masking attributes costs replay fidelity, so it's off by default. ## Take this skill with you Pass it to your agent and implement masking before capturing session replays in production Fig. 1. Mask everything by default, then, based on what you need to see, reveal fields and components individually. session-replay-masking/SKILL.md ```markdown # Session Replay masking Your job is to get this project to a state where a session recording cannot leak data the team hasn't consciously agreed to record. **The rule that overrides everything else: you never reveal anything on your own judgment.** You mask first, you propose, the human decides. If you are unsure whether something is safe, it stays masked and you say so. ## Before you write anything Read the current docs rather than working from memory. Every PostHog docs URL returns Markdown if you append `.md`: - `https://posthog.com/docs/session-replay/privacy.md` – masking for every SDK, and the exact config option names - `https://posthog.com/docs/session-replay/network-recording.md` – headers, bodies, and the callback that redacts them and the page URL - `https://posthog.com/docs/session-replay/mobile.md` – wireframe versus screenshot mode - `https://posthog.com/llms.txt` – the index, if you need anything else Then find out what you're working with: 1. Which PostHog SDK, and where it's initialized. 2. Whether masking has already been configured, and by whom – do not silently overwrite an existing config. 3. On mobile, whether screenshot mode is on. It raises the stakes of every unmarked view. ## Step one: mask everything Set the project to mask all text and all inputs, using whatever the SDK calls it. On web that means text masking applied broadly rather than per-element. On Android, iOS, React Native, and Flutter the SDK already masks text and images by default — confirm it hasn't been turned off rather than assuming. Stop here and confirm recordings still capture something useful before going further. A fully masked replay is correct, and it is also the baseline you reveal back from. ## Step two: propose what to reveal Now read the app and build a list of candidates. Look for: - Navigation, headers, footers, and marketing copy – no user data, high value in a replay - Buttons, labels, and empty states - Error and validation messages, which are usually the point of watching a replay - Product-owned content like plan names, feature names, and static help text For each candidate, tell the human three things: what it is, why you think it's safe, and what would make it unsafe. Then ask. Never propose revealing anything that could carry user input, names, email addresses, IDs, tokens, financial data, health data, or anything rendered from a database row. If a component sometimes holds user content and sometimes doesn't, it stays masked. ## Step three: apply only what was approved Implement exactly the reveals that were agreed, one mark at a time, in the markup rather than in a central selector list – a mark next to the component moves with it and gets reviewed with it. Use whatever the SDK provides: - Web has no unmask selector. Mark safe elements with a data attribute and check for it in the masking callback, matching on the nearest marked ancestor so a wrapper covers what's inside it. - Android and iOS have real reveal markers, and reveal wins over mask. - React Native and Flutter have no reveal marker at all. If something must be visible there, say so plainly – the only lever is an app-wide default, and changing it is a separate decision the human has to make knowingly. ## Step four: the things masking doesn't cover Raise each of these explicitly, because a team that has masked its screens usually believes it's done: - **URLs.** The player shows the page URL and the timeline lists it. Query strings carry tokens, emails, and IDs. Redaction is a callback, and the same one covers network requests. - **Network payloads.** Only relevant if network capture is on. Headers and bodies both. - **Canvas.** Only relevant if canvas recording is on. It's pixels; no selector reaches inside. - **Element attributes.** A masked element can still ship a `title`, `alt`, or `value` holding what you masked. ## Finally Tell the human how to verify: open a recording and look at it. Reading the config proves nothing. List what you masked, what you revealed and on whose instruction, and what you left alone because you weren't sure. ``` Show full example Fig. 1 – The skill itself. Copy it, pass it to your agent, and have masking in place before real traffic arrives. See also: [Privacy controls](/docs/session-replay/privacy.md) · [Mobile replay](/docs/session-replay/mobile.md) · [Network recording](/docs/session-replay/network-recording.md) · [Control which sessions to record](/pocket-guides/session-replay/configure-replays.md) [‹ Which sessions to record](/pocket-guides/session-replay/configure-replays.md)[All guides](/pocket-guides.md)p. 3 of 7[Reverse proxy ›](/pocket-guides/session-replay/reverse-proxy.md)