Skip to content

Commit

Permalink
Make sure text mutations respect strict privacy mode (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
John Pham authored Feb 4, 2022
1 parent 8c1b928 commit 9a3bb3f
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 15 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@highlight-run/rrweb",
"version": "1.1.7",
"version": "1.1.8",
"description": "record and replay the web",
"scripts": {
"test": "npm run bundle:browser && cross-env TS_NODE_CACHE=false TS_NODE_FILES=true mocha -r ts-node/register -r ignore-styles -r jsdom-global/register test/**.test.ts",
Expand Down
15 changes: 11 additions & 4 deletions src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
isIgnored,
isIframeINode,
hasShadowRoot,
obfuscateText,
} from '../utils';
import { IframeManager } from './iframe-manager';
import { CanvasManager } from './observers/canvas/canvas-manager';
Expand Down Expand Up @@ -406,10 +407,16 @@ export default class MutationBuffer {

const payload = {
texts: this.texts
.map((text) => ({
id: this.mirror.getId(text.node as INode),
value: text.value,
}))
.map((text) => {
let value = text.value;
if (this.enableStrictPrivacy && value) {
value = obfuscateText(value);
}
return {
id: this.mirror.getId(text.node as INode),
value,
};
})
// text mutation's id was not in the mirror map means the target node has been removed
.filter((text) => this.mirror.has(text.id)),
attributes: this.attributes
Expand Down
12 changes: 2 additions & 10 deletions src/snapshot/snapshot.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { obfuscateText } from '../utils';
import {
serializedNode,
serializedNodeWithId,
Expand Down Expand Up @@ -625,16 +626,7 @@ function serializeNode(
'NOSCRIPT',
]);
if (!IGNORE_TAG_NAMES.has(parentTagName) && textContent) {
// We remove non-printing characters.
// For example: '‌' is a character that isn't shown visibly or takes up layout space on the screen. However if you take the length of the string, it's counted as 1.
// For example: "‌1"'s length is 2 but visually it's only taking up 1 character width.
// If we don't filter does out, our string obfuscation could have more characters than what was originally presented.
textContent = textContent.replace(/[^ -~]+/g, '');
textContent =
textContent
?.split(' ')
.map((word) => Math.random().toString(20).substr(2, word.length))
.join(' ') || '';
textContent = obfuscateText(textContent);
}
}
return {
Expand Down
17 changes: 17 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,3 +629,20 @@ export function hasShadowRoot<T extends Node>(
): n is T & { shadowRoot: ShadowRoot } {
return Boolean(((n as unknown) as Element)?.shadowRoot);
}

/**
* Returns a string of the same length that has been obfuscated.
*/
export function obfuscateText(text: string): string {
// We remove non-printing characters.
// For example: '&zwnj;' is a character that isn't shown visibly or takes up layout space on the screen. However if you take the length of the string, it's counted as 1.
// For example: "&zwnj;1"'s length is 2 but visually it's only taking up 1 character width.
// If we don't filter does out, our string obfuscation could have more characters than what was originally presented.
text = text.replace(/[^ -~]+/g, '');
text =
text
?.split(' ')
.map((word) => Math.random().toString(20).substr(2, word.length))
.join(' ') || '';
return text;
}

0 comments on commit 9a3bb3f

Please sign in to comment.