Skip to content

feat: Privacy at Capture — opt-in heuristic PII detectors (experimental) - #8

Open
roggernaut wants to merge 3 commits into
privacy-at-capture-corefrom
privacy-at-capture-detectors
Open

roggernaut wants to merge 3 commits into
privacy-at-capture-corefrom
privacy-at-capture-detectors

Conversation

@roggernaut

Copy link
Copy Markdown
Owner

Adds an opt-in detector set for email addresses, phone numbers, Luhn-valid payment card numbers, SSN-like values, and IPv4 addresses in page text. A match masks the entire text node during the initial snapshot and on live characterData mutations.

Form input values are never scanned. When at least one detector is active, the compiled policy sets maskAllInputs: true, including on a minimal base policy, so every input value is occluded from the first captured event. This avoids leaking the prefixes typed before a pattern can match and avoids treating a clean scan as proof that a value is safe.

Detectors are never implied by balanced or strict. Consumers opt in through @rrweb/rrweb-plugin-privacy-detectors or applyPrivacyDetectors. Existing policy masking runs first, attribute values are outside the detector path, and custom detector patterns are not supported.

This PR also restores RecordPlugin.applyPrivacyPolicy, allowing a plugin to transform the portable policy before record() compiles it. The detector plugin is currently its only consumer.

Experimental status

The patterns are informed by automatic redaction approaches in commercial capture tools, but this exact fixed detector set and its live text-mutation integration have not yet run at production scale. Treat it as a backstop for unexpected PII in page text. Known sensitive fields should still be protected with data-privacy, a selector rule, or an appropriately restrictive preset.

Known limitations

  • Heuristics can produce false positives and false negatives. The fixed set cannot model every kind of PII.
  • A match masks the whole text node rather than only the matching range. This is safer and can remove useful surrounding text.
  • Text values longer than 10,000 characters fail closed and are masked without scanning.
  • If a plugin-produced policy fails to compile, record() logs the error and falls back to the user's untransformed policy. Recording continues, so applications should surface recorder errors in their own monitoring.

Verification

The snapshot and live-mutation detector paths, input-wide occlusion, detector overrides, disabled-detector behavior, Luhn checks, and plugin policy composition have dedicated tests. The detector plugin suite passes independently of the core policy suite.

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown

Bundle Size Changes

Size change: +446.98 kB (+4.05%) | Total size: 11477.03 kB

all - 2101.05 kB -> 2111.74 kB (+10.69 kB (+0.51%))
File Base PR Diff
all.cjs 603.81 kB 606.86 kB +3.06 kB (+0.51%)
all.js 603.48 kB 606.53 kB +3.06 kB (+0.51%)
all.umd.cjs 607.35 kB 610.41 kB +3.06 kB (+0.50%)
all.umd.min.cjs 286.41 kB 287.93 kB +1.52 kB (+0.53%)
browser-client - 794.48 kB -> 805.14 kB (+10.66 kB (+1.34%))
File Base PR Diff
browser-client.cjs 227.78 kB 230.82 kB +3.05 kB (+1.34%)
browser-client.js 227.55 kB 230.60 kB +3.05 kB (+1.34%)
browser-client.umd.cjs 230.94 kB 233.98 kB +3.05 kB (+1.32%)
browser-client.umd.min.cjs 108.22 kB 109.74 kB +1.52 kB (+1.40%)
plugins 🆕 - 0 B -> 393.75 kB (+393.75 kB)
File Base PR Diff
packages/plugins/rrweb-plugin-privacy-detectors/dist/rrweb-plugin-privacy-detectors.cjs - 112.94 kB +112.94 kB 🆕
packages/plugins/rrweb-plugin-privacy-detectors/dist/rrweb-plugin-privacy-detectors.js - 112.75 kB +112.75 kB 🆕
packages/plugins/rrweb-plugin-privacy-detectors/dist/rrweb-plugin-privacy-detectors.umd.cjs - 115.41 kB +115.41 kB 🆕
packages/plugins/rrweb-plugin-privacy-detectors/dist/rrweb-plugin-privacy-detectors.umd.min.cjs - 52.65 kB +52.65 kB 🆕
record - 676.87 kB -> 687.53 kB (+10.66 kB (+1.57%))
File Base PR Diff
record.cjs 194.29 kB 197.34 kB +3.05 kB (+1.57%)
record.js 194.20 kB 197.25 kB +3.05 kB (+1.57%)
record.umd.cjs 195.31 kB 198.35 kB +3.05 kB (+1.56%)
record.umd.min.cjs 93.07 kB 94.59 kB +1.52 kB (+1.63%)
rrweb - 2039.66 kB -> 2050.35 kB (+10.69 kB (+0.52%))
File Base PR Diff
rrweb.cjs 587.18 kB 590.23 kB +3.06 kB (+0.52%)
rrweb.js 586.88 kB 589.93 kB +3.06 kB (+0.52%)
rrweb.umd.cjs 588.52 kB 591.57 kB +3.06 kB (+0.52%)
rrweb.umd.min.cjs 277.09 kB 278.61 kB +1.52 kB (+0.55%)
rrweb-snapshot - 702.08 kB -> 712.63 kB (+10.55 kB (+1.50%))
File Base PR Diff
rrweb-snapshot.cjs 201.81 kB 204.83 kB +3.02 kB (+1.50%)
rrweb-snapshot.js 200.40 kB 203.29 kB +2.89 kB (+1.44%)
rrweb-snapshot.umd.cjs 204.43 kB 207.49 kB +3.06 kB (+1.50%)
rrweb-snapshot.umd.min.cjs 95.45 kB 97.03 kB +1.58 kB (+1.65%)

@roggernaut
roggernaut force-pushed the privacy-at-capture-detectors branch from 1ce27b6 to 394af66 Compare September 2, 2026 14:33
@roggernaut
roggernaut force-pushed the privacy-at-capture-core branch from ee793c7 to da75a0f Compare September 2, 2026 19:51
@roggernaut
roggernaut force-pushed the privacy-at-capture-detectors branch from 394af66 to 7114ecd Compare September 2, 2026 21:06
Rogier Trimpe and others added 3 commits September 3, 2026 12:09
…xperimental)

Adds a fixed, whole-value detector set (email, phone, Luhn-valid payment card,
SSN-like, IPv4) that masks a page text node or form input value when a
detector matches, at snapshot time and on live updates (`characterData`
mutations and input events). Detectors are never implied by a preset: opt in
through `@rrweb/rrweb-plugin-privacy-detectors` or the `applyPrivacyDetectors`
helper.

Also adds the `RecordPlugin.applyPrivacyPolicy` hook (and its fail-safe
fallback in `record()`), which lets a plugin transform the portable policy
before it is compiled. The detectors plugin is its only consumer.

EXPERIMENTAL: whole-value detection on input values and live mutations has no
production mileage in any shipped session-replay recorder. Known limitations
are documented in the plugin README and the changeset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lies

One-time console.info ("privacy-detectors active: input values record as
length-only stars") the first time applyPrivacyPolicy runs, so an embedder
gets a visible confirmation that input occlusion is in force. Guarded by a
module-level once-flag; a test isolates it with vi.resetModules() since
every other test in the file already exercises applyPrivacyPolicy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rted internals

`passesLuhn` joins `splitSelectorList`, `stars`, and `validateSelector` in
the migration guide's list of `@internal`, unstable symbols that
`rrweb-snapshot`'s barrel now re-exports.

refactor(privacy): rename the `manual` preset to `minimal` throughout the
detectors layer -- `applyPrivacyDetectors`'s default base, the plugin's
own default-policy construction, every `preset: 'manual'` fixture and test
name, and the plugin README's preset mentions. Clean rename, nothing
released; prose meaning "manually configured" (e.g. "a manual option
already masks") is left alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@roggernaut
roggernaut force-pushed the privacy-at-capture-detectors branch from 7114ecd to a391af8 Compare September 3, 2026 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant