Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
835f86e
docs: add Privacy at Capture v2 simplification design spec
Aug 25, 2026
62f611a
docs: add privacy v2 implementation plan
Aug 26, 2026
0880e2b
feat(privacy): compile policy onto selector lists and rrweb options
Aug 26, 2026
54798a2
fix(privacy): restore interim maskAllElementAttributes/maskAttributeF…
Aug 26, 2026
01fbc2e
feat(privacy): fixed whole-value detectors, delete range and custom-p…
Aug 26, 2026
2b253f2
fix(privacy): tighten phone pattern quantifier for whole-value semant…
Aug 26, 2026
ec48742
feat(privacy): sanitizeUrl strips userinfo, uses precompiled sets, fa…
Aug 26, 2026
90036fe
feat(privacy): unmask selector + detector hook in core text masking, …
Aug 26, 2026
72e4763
fix(privacy): keep inherited masking across shadow/iframe boundaries,…
Aug 26, 2026
22c9d9d
feat(privacy): single maskInput entry point, Sentry-style fn composition
Aug 26, 2026
145ac97
feat(privacy): single attribute finalization pass, added-node coverag…
Aug 26, 2026
43afe1e
fix(privacy): apply policy to renamed rr_src, make maskAttributeFn a …
Aug 26, 2026
5d32701
fix(privacy): let an emptied attribute reach the policy block
Aug 26, 2026
bdb7597
feat(privacy): CSS is never masked; delete stylesheet masking call sites
Aug 26, 2026
1b65915
fix(canvas): masking forces FPS capture path; content-box region scal…
Aug 26, 2026
0bf6128
fix(privacy): plugin compile fallback, shared untainted tagName, plug…
Aug 26, 2026
1525d45
fix(privacy): route remaining mutation.ts tagName reads through untai…
Aug 26, 2026
f00cea8
docs(privacy): v2 types sync, changeset, guide
Aug 26, 2026
f34e6c7
fix(privacy): scan input values and live text mutations with detectors
Aug 26, 2026
9da8a3a
docs(privacy): fix detector-coverage claim, changeset hygiene, legacy…
Aug 26, 2026
f7c4f1c
test(privacy): perf smoke + full sweep
Aug 26, 2026
5201b4c
fix(privacy): final review fixes — phone recall, disclosures, preset …
Aug 26, 2026
0d60d4d
Apply formatting changes
roggernaut Aug 26, 2026
101010b
chore(privacy): neutral vendor attribution, remove internal process docs
Aug 26, 2026
609837f
fix(privacy): align tie-break, selector validation, and exemption gat…
Aug 26, 2026
fbc9955
fix(privacy): honor the record()-level unmaskTextSelector in attribut…
Aug 26, 2026
15ddebc
fix(privacy): treat backslash escapes as universal when splitting sel…
Aug 26, 2026
53b2f10
Apply formatting changes
roggernaut Aug 26, 2026
f4615de
feat(privacy): recognize Datadog and New Relic mask/block conventions
Aug 27, 2026
12c76a1
feat(privacy): dimension-preserving media placeholders and operationa…
Aug 27, 2026
df42c23
perf(privacy): per-flush mask-decision memoisation
Aug 27, 2026
74deb50
docs(privacy): lead with data-privacy convention; trim operational ex…
Aug 27, 2026
c6cf1dd
fix(detectors): occlude all input values while active, scan text cont…
Aug 27, 2026
7785189
docs(detectors): disclose the all-flags-disabled edge in the occlusio…
Aug 27, 2026
46ef610
refactor(privacy): one privacy prologue, one selector merge, one unma…
Aug 27, 2026
83fb32f
refactor(privacy): one text-mask resolver for both serialization paths
Aug 27, 2026
b31dd20
refactor(privacy): split the mask-all selector once per pass, not per…
Aug 27, 2026
8bf4171
perf(privacy): memoise the snapshot walk's mask decisions per call
Aug 27, 2026
504eb61
refactor(privacy): one type family, one spelling of the preset checks
Aug 27, 2026
6610354
refactor(privacy): fold the masking helpers into one of each
Aug 27, 2026
ace2f66
perf(privacy): cheapen the per-attribute and per-input hot paths
Aug 27, 2026
d122b2a
refactor(canvas): fold canvas-sampling into canvas-mask
Aug 27, 2026
2f1f706
fix(privacy): <option selected> follows the select's masking decision
Aug 27, 2026
e09812b
fix(privacy): review round 1 — fail-closed coercion, one prologue, de…
Aug 27, 2026
6bd6f9b
Apply formatting changes
roggernaut Aug 27, 2026
3f6e7e0
docs(privacy): correct the internal compiled-policy option's preceden…
Aug 27, 2026
610dfd1
fix(privacy): reject foreign unmask tokens
Aug 27, 2026
9145757
docs(privacy): match house comment density; consolidate changesets
Aug 28, 2026
16e7cc2
docs(privacy): final aggressive comment-reduction pass
Aug 28, 2026
afac20c
refactor(privacy): rename the legacy preset to manual
Aug 28, 2026
0815f7b
refactor(privacy)!: name the actions mask | block | unmask, and fail …
Aug 30, 2026
df326c4
feat(privacy)!: foreign vendor-class recognition becomes opt-in via v…
Aug 30, 2026
623d2ba
fix(privacy)!: final hardening wave — never-honor-unmask, three warns…
Aug 30, 2026
e55d5ca
fix(privacy)!: closing review wave — legacy-signature coercion, manua…
Aug 30, 2026
1614a40
Scope the credential-floor claim to native form controls
Aug 30, 2026
900062b
refactor(privacy): rename the manual preset to minimal
Aug 31, 2026
47b9bf8
feat(privacy): extend vendorCompat to every verified session-replay c…
Sep 2, 2026
a3e05e2
feat(privacy): add the remaining verified vendors to vendorCompat
Sep 2, 2026
e21c469
feat(privacy): data-privacy="ignore" — mask plus input-event silence
Sep 2, 2026
8f9dde3
feat(privacy): granular vendorCompat — pick the vendors to honor
Sep 2, 2026
46623bf
fix(privacy): close the provenance-audit defects and gaps in the core…
Sep 2, 2026
cd68ae7
fix(privacy): close the remaining audit items — canvas scale, samplin…
Sep 2, 2026
60d0ccc
feat(privacy): vendorCompat fidelity — events-only ignoreEvents slot,…
Sep 3, 2026
0a67697
fix(privacy): extend the vendorCompat fidelity pass to the full registry
Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .changeset/calm-ravens-protect.md

This file was deleted.

2 changes: 0 additions & 2 deletions .changeset/khaki-hoops-smile.md

This file was deleted.

11 changes: 0 additions & 11 deletions .changeset/kind-pumas-detect.md

This file was deleted.

9 changes: 0 additions & 9 deletions .changeset/loud-lions-protect.md

This file was deleted.

65 changes: 65 additions & 0 deletions .changeset/privacy-at-capture-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
"rrweb-snapshot": minor
"rrweb": minor
"@rrweb/types": major
"@rrweb/rrweb-plugin-privacy-detectors": minor
"@rrweb/utils": minor
---

Privacy at Capture: policies now compile onto rrweb's existing masking
primitives.

- BREAKING: `@rrweb/rrweb-plugin-privacy-detectors`'s API and the
`RecordPlugin.applyPrivacyPolicy` hook are new, and several masking
decisions that used to have inconsistent defaults are now always enforced
(e.g. protected inputs, mask/unmask ties) -- the default preset introduced
by this change is named `minimal`, and recognizing another session-replay
tool's privacy classes is opt-in via `vendorCompat` rather than a
managed-preset default -- see the guide's migration section for the full
list. It covers the verified mask/block conventions of twenty-five
session-replay tools (each token sourced in the guide's "Vendor class
recognition" table), taking `true` for every vendor or an array of vendor
ids for just the named ones (an unknown id is dropped with a `console.warn`
naming it), and can only ever increase masking or blocking, never reveal:
no foreign tool's unmask or input-ignore convention is ever honored, under
any form of the setting. `needMaskingText`'s fail-closed catch and an explicit
`recordCanvas: true` under `strict` each log a one-time `console.warn`
instead of failing silently.
- Add an opt-in, versioned `privacyPolicy` with `strict`/`balanced`/`minimal`
presets, the vendor-neutral `data-privacy` HTML binding (the severity
ladder `unmask` < `mask` < `ignore` < `block`, nearest annotated ancestor
deciding, with any unrecognized value failing closed to `mask`; `ignore`
masks like `mask` and additionally emits no input events for the subtree
at all, unlike the legacy events-only, per-element `.rr-ignore`),
fail-closed `canvasMasking`, and
attribute-masking escape hatches; existing masking options remain the
`minimal` default. Every masking decision fails closed: a callback that
throws or returns a non-string yields stars, an input whose
`type`/`autocomplete` cannot be read is treated as protected, a block
selector that throws while matching blocks with a one-time `console.warn`,
canvas mask regions scale against the layout content box (immune to CSS
transforms), and configuring `canvasMasking` forces the FPS capture path
on its own. `data-privacy` and the `rr-*` classes are managed-preset
features: under `minimal` a rule compiles to its bare selector and switches
nothing else on. The guide's new "For event consumers" section documents
what changes on the wire for anyone consuming the event stream directly
(nullable attributes, SVG media placeholders, starred text, the
`data-privacy` collision with pre-existing attributes, canvas keyframes
under a `canvasMasking` adapter -- `strict` emits no canvas events at all
-- and canvas-in-shadow-DOM discovery now requiring a native shadow root,
so polyfilled shadow canvases are no longer captured).
- **Experimental:** opt-in heuristic PII detectors (email/phone/card/SSN/IP)
via `@rrweb/rrweb-plugin-privacy-detectors`, which logs a one-time
`console.info` the first time it applies its policy.
- **Experimental, no vendor precedent:** recorded-DOM URL sanitization under
`balanced`/`strict`. **Open design question for upstream:** the Meta
event's own `href` is scoped like `balanced` (blocked-list-only param
masking) even under `strict`, since it is the recording's own address
rather than page-author markup; every DOM URL attribute keeps `strict`'s
normal mask-everything-unless-allowlisted treatment. A relative URL in an
attribute rrweb does not already absolutify (`<form action>`,
`<video poster>`, ...) is rewritten root-relative.
- **Experimental optimization:** an unmask-selector presence probe avoids
the per-node ancestor walk `unmaskTextSelector` would otherwise force. The
probe costs one full-document element sweep per flush precisely when no
unmask target exists, scaling with DOM size.
27 changes: 27 additions & 0 deletions .changeset/privacy-at-capture-vendor-compat-fidelity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
"rrweb-snapshot": minor
"rrweb": minor
"@rrweb/types": minor
---

`vendorCompat` fidelity pass: entries now match the vendors' verified implementations, and gain an events-only ignore slot.

- Vendor registry entries gain an `ignoreEvents` slot compiled into the new
`ignoreEventsSelector`: input events from a matching element are suppressed
and nothing else changes — no masking implied, unlike `data-privacy="ignore"`.
- Sentry (`.sentry-ignore`, `[data-sentry-ignore]`), PostHog
(`.ph-ignore-input`), New Relic (`.nr-ignore`), and Highlight
(`.highlight-ignore`) ignore tokens are now honored, each verified from the
vendor's source.
- The mapping doctrine is now pinned in code and tests: each vendor token maps
to the closest treatment our verbs express, never a less protective one, and
no entry may carry an allow/unmask-like selector in any slot.
- Datadog `mask-user-input` stays mapped to text mask: form values are already
masked globally wherever compat applies, and dropping the token would record
the form-element text (e.g. `<option>` labels) it protects there.
- Docs-tier remaps from verified vendor documentation: Heap's redact
attributes and Session Rewind's `[data-sr-redact]` move to block,
`[heap-ignore]` and `.userback-ignore` move to events-only ignore,
Mouseflow's `.mf-masked`/`[data-mf-replace]` move to block (they remove the
subtree there), and Smartlook's legacy `[data-recording-sensitive]` becomes
mask + events-ignore.
Loading
Loading