From 4c89b129724c0285ca1439d3099d3a25830b4b1f Mon Sep 17 00:00:00 2001 From: Rogier Trimpe Date: Thu, 27 Aug 2026 09:39:51 +0200 Subject: [PATCH 1/4] =?UTF-8?q?feat(privacy):=20Privacy=20at=20Capture=20?= =?UTF-8?q?=E2=80=94=20recorded-DOM=20URL=20sanitization=20(experimental)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../privacy-at-capture-url-sanitization.md | 22 ++++++ guide.md | 37 ++++++---- packages/rrweb-snapshot/src/privacy.ts | 72 ++++++++++++++++++- packages/rrweb-snapshot/src/types.ts | 1 + .../test/privacy-integration.test.ts | 51 ++++++++++--- packages/rrweb-snapshot/test/privacy.test.ts | 68 +++++++++++++++--- packages/rrweb/src/index.ts | 1 + packages/rrweb/src/record/index.ts | 5 +- packages/types/src/index.ts | 15 ++++ 9 files changed, 240 insertions(+), 32 deletions(-) create mode 100644 .changeset/privacy-at-capture-url-sanitization.md diff --git a/.changeset/privacy-at-capture-url-sanitization.md b/.changeset/privacy-at-capture-url-sanitization.md new file mode 100644 index 0000000000..d5f830ed25 --- /dev/null +++ b/.changeset/privacy-at-capture-url-sanitization.md @@ -0,0 +1,22 @@ +--- +"rrweb": minor +"rrweb-snapshot": minor +"@rrweb/types": minor +--- + +Privacy at Capture: recorded-DOM URL sanitization (**experimental** -- no +vendor precedent, review this hardest). + +- Under `balanced`/`strict`, every URL-bearing attribute the serializer emits + and the Meta event's `href` go through `sanitizeUrl`: userinfo is + stripped, sensitive query parameter values are replaced with `*` + (`url.blockedQueryParameters` plus a default list), and the hash is + removed unless `url.removeHash: false`. `strict` blocks every parameter + value unless `url.allowedQueryParameters` names it. +- An unparseable URL fails closed: the attribute is dropped (`null`) rather + than emptied, since an empty `src`/`href` re-resolves to the document URL + at replay. +- The unmask escape cannot reopen a sanitized URL. +- Rebased onto the renamed rule actions (`mask`/`block`/`unmask`) and the + opt-in `vendorCompat` flag; URL sanitization itself keys off the managed + presets and is unaffected by either. diff --git a/guide.md b/guide.md index 1e4880569c..cdd515d0fe 100644 --- a/guide.md +++ b/guide.md @@ -215,7 +215,7 @@ you get by default (the `minimal` preset, below): - `input[type="password"]` will be masked by default. - Mask options to mask the content in input elements. -For a consistent policy across text, inputs, and attributes, pass a +For a consistent policy across text, inputs, attributes, and URLs, pass a versioned `privacyPolicy`: ```js @@ -240,17 +240,28 @@ record({ action: 'block', }, ], + url: { + blockedQueryParameters: ['token', 'session'], + }, }, }); ``` `preset` compiles to the following, on top of the `rules` above: -| preset | behavior | -| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `minimal` (default) | Inert: only the existing masking options above apply. `rules` still work (below), but the `data-privacy` attribute and cross-vendor class recognition described below are off. | -| `balanced` | Masks every input value (like `maskAllInputs: true`); masks the `title`, `placeholder`, and `aria-label` attributes on every element. Page text is untouched. | -| `strict` | Everything `balanced` does, plus: all page text is masked; media element sources (``, `