[RUM-10415] add privacy allowlist support (with treewalker)#3751
[RUM-10415] add privacy allowlist support (with treewalker)#3751cy-moi wants to merge 13 commits into
Conversation
|
✅ Tests 🎉 All green!❄️ No new flaky tests detected 🎯 Code Coverage 🔗 Commit SHA: acb4d33 | Docs | Was this helpful? Give us feedback! |
Bundles Sizes Evolution
🚀 CPU Performance
🧠 Memory Performance
|
8832509 to
d5204d3
Compare
d5204d3 to
77c36ca
Compare
eaaebde to
763bf92
Compare
763bf92 to
e43093f
Compare
280fa9a to
e9d99bc
Compare
| const { | ||
| enablePrivacyForActionName, | ||
| actionNameAttribute: userProgrammaticAttribute, | ||
| defaultPrivacyLevel, | ||
| } = rumConfiguration | ||
| const enableAllowlistMask = isAllowlistMaskEnabled(defaultPrivacyLevel, getNodeSelfPrivacyLevel(element)) |
There was a problem hiding this comment.
suggestion: this could be done in the getTextualContentWithTreeWalker function
| if (window.$DD_ALLOW && window.$DD_ALLOW.has(text.toLocaleLowerCase())) { | ||
| return text | ||
| } | ||
| return fixedMask || text.replace(/\S/g, TEXT_MASKING_CHAR) |
| // If the node privacy level is MASK_UNLESS_ALLOWLISTED, we use the default privacy level | ||
| // If the enablePrivacyForActionName is true, we use the node privacy level | ||
| // Otherwise, we use the allow level | ||
| // TODO: we should make enablePrivacyForActionName true by default for the next major version | ||
|
|
||
| // this won't work because there would always be a case when we set the nodeSelfPrivacy to ALLOW | ||
| // if we use configuration.defaultPrivacyLevel === NodePrivacyLevel.MASK_UNLESS_ALLOWLISTED as a check | ||
| // then when we set defaultPrivacyLevel to ALLOW, and override the node privacy level to MASK_UNLESS_ALLOWLISTED |
There was a problem hiding this comment.
question: I don't get this comment
If the node privacy level is MASK_UNLESS_ALLOWLISTED, we use the default privacy level
If the enablePrivacyForActionName is true, we use the node privacy level
Otherwise, we use the allow level
That's exactly what's the code is doing. Could you explain why we do this instead?
TODO: we should make enablePrivacyForActionName true by default for the next major version
Same here, why?
this won't work because there would always be a case when we set the nodeSelfPrivacy to ALLOW
if we use configuration.defaultPrivacyLevel === NodePrivacyLevel.MASK_UNLESS_ALLOWLISTED as a check
then when we set defaultPrivacyLevel to ALLOW, and override the node privacy level to MASK_UNLESS_ALLOWLISTED
I guess something is missing here?
There was a problem hiding this comment.
TODO: we should make enablePrivacyForActionName true by default for the next major version
If we set this by default when the configuration.defaultPrivacyLevel: MASK, it would simplify things greatly and align with Session Replay.
The last paragraph was badly formed, I was explaining why we have to check for isAllowlistMaskEnabled(configuration.defaultPrivacyLevel, nodeSelfPrivacy). When we set defaultPrivacyLevel to ALLOW, and then set the node class name to MASK_UNLESS_ALLOWLISTED, without checking the nodeSelfPrivacy level we will not be able to apply the allowlist, because we are ignoring the nodePrivacyLevel and setting it to ALLOW.
We need all these checks because we override the node privacy level to allow here. By making MASK level actually masks action names, we can get rid of the override and the checks for it.
I updated the comments to reflect this. I hope it explains better this time.
| const nodePrivacyLevel = isAllowlistMaskEnabled(configuration.defaultPrivacyLevel, nodeSelfPrivacy) | ||
| ? nodeSelfPrivacy | ||
| : configuration.enablePrivacyForActionName | ||
| ? nodeSelfPrivacy | ||
| : NodePrivacyLevel.ALLOW |
There was a problem hiding this comment.
suggestion: What about something like this:
const nodePrivacyLevel =
nodeSelfPrivacy === PrivacyLevel.MASK_UNLESS_ALLOW_LISTED || configuration.enablePrivacyForActionName
? nodeSelfPrivacy
: NodePrivacyLevel.ALLOW| actionNameAttribute: userProgrammaticAttribute, | ||
| defaultPrivacyLevel, | ||
| } = rumConfiguration | ||
| const enableAllowlistMask = isAllowlistMaskEnabled(defaultPrivacyLevel, getNodeSelfPrivacyLevel(element)) |
There was a problem hiding this comment.
suggestion: use getNodePrivacyLevel instead, so that privacy level follows the privacy inheritance rules.
| // Without allowlist, should behave like mask mode | ||
| window.$DD_ALLOW = undefined | ||
| registerCleanupTask(() => { | ||
| window.$DD_ALLOW = undefined | ||
| }) |
There was a problem hiding this comment.
suggestion: This should be unnecessary. window.$DD_ALLOW should be undefined at the beginning of the test (if it's not, then a cleanup is missing in another test), and since the test does not change it, it should stay undefined.
| const clickActionBase: ClickActionBase = { | ||
| type: ActionType.CLICK, | ||
| name: '', | ||
| nameSource: ActionNameSource.TEXT_CONTENT, | ||
| target: { | ||
| selector: 'button', | ||
| width: 100, | ||
| height: 100, | ||
| }, | ||
| position: { x: 0, y: 0 }, | ||
| } |
There was a problem hiding this comment.
suggestion: it looks like you only use the clickActionBase.name. Maybe keep ClickActionBase private and just use a string variable instead of defining the whole structure?
96a7dd0 to
b6e655d
Compare
b6e655d to
0408c56
Compare
bb5332c to
70059f3
Compare
| declare global { | ||
| interface Window { | ||
| $DD_ALLOW?: Set<string> | ||
| } | ||
| } |
There was a problem hiding this comment.
suggestion: don't use declare global as it will declare a global variable everywhere, including in our customers source code (i.e. $DD_ALLOW will be visible in their code editor autocompletion).
Instead, use something like this:
interface BrowserWindow {
$DD_ALLOW?: Set<string>
}
// then
(window as BrowserWindow).$DD_ALLOW(Not pretty, but we have some thoughts to improve this in the future
| export function getPrivacySelector(privacyLevel: string) { | ||
| return `[${PRIVACY_ATTR_NAME}="${privacyLevel}"], .${PRIVACY_CLASS_PREFIX}${privacyLevel}` | ||
| } | ||
|
|
||
| /** | ||
| * Text censoring non-destructively maintains whitespace characters in order to preserve text shape | ||
| * during replay. | ||
| */ | ||
| export const censorText = (text: string) => text.replace(/\S/g, TEXT_MASKING_CHAR) |
There was a problem hiding this comment.
nitpick: maybe move those functions back to privacy.ts as they are not constants
1f1e3b1 to
acb4d33
Compare
Motivation
Mask action name with allowlists generated from rum privacy build plugin(WIP). This approach is purely client side and allowlist-based. We aim to mask all action names (custom & auto) OOTB with build time configuration using a build plugin.
This PR is a diverge from #3648 and rebase on tree walker text content capture in order to mask granular action names and fix potential leaking risks (link).
Changes
Test instructions
Checklist