Skip to content

[RUM-10415] add privacy allowlist support (with treewalker)#3751

Closed
cy-moi wants to merge 13 commits into
mainfrom
congyao/RUM-10415-add-privacy-allowlist-support-treewalker
Closed

[RUM-10415] add privacy allowlist support (with treewalker)#3751
cy-moi wants to merge 13 commits into
mainfrom
congyao/RUM-10415-add-privacy-allowlist-support-treewalker

Conversation

@cy-moi

@cy-moi cy-moi commented Aug 1, 2025

Copy link
Copy Markdown
Contributor

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

  • Add allowlist processing helpers
  • Mask all action names with the allowlist when privacy build plugin is opt-in

Test instructions

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.

@cy-moi cy-moi changed the title Congyao/rum 10415 add privacy allowlist support treewalker [RUM-10415] add privacy allowlist support treewalker Aug 1, 2025
@cy-moi
cy-moi marked this pull request as ready for review August 1, 2025 10:15
@cy-moi
cy-moi requested a review from a team as a code owner August 1, 2025 10:15
@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Aug 1, 2025

Copy link
Copy Markdown

✅ Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

🎯 Code Coverage
Patch Coverage: 100.00%
Total Coverage: 92.51% (-0.02%)

View detailed report

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: acb4d33 | Docs | Was this helpful? Give us feedback!

@cit-pr-commenter

cit-pr-commenter Bot commented Aug 1, 2025

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 152.75 KiB 153.43 KiB 698 B 0.45%
Rum Recorder 19.06 KiB 19.06 KiB 1 B 0.01%
Rum Profiler 5.18 KiB 5.18 KiB 1 B 0.02%
Logs 54.80 KiB 54.80 KiB 0 B 0.00%
Flagging N/A 931 B 931 B N/A%
Rum Slim 111.69 KiB 112.34 KiB 669 B 0.58%
Worker 23.60 KiB 23.60 KiB 0 B 0.00%
🚀 CPU Performance
Action Name Base Average Cpu Time (ms) Local Average Cpu Time (ms) 𝚫
addglobalcontext 0.012 0.008 -0.004
addaction 0.034 0.023 -0.011
addtiming 0.007 0.005 -0.001
adderror 0.032 0.025 -0.007
startstopsessionreplayrecording 0.001 0.002 0.000
startview 0.007 0.009 0.002
logmessage 0.036 0.027 -0.009
🧠 Memory Performance
Action Name Base Consumption Memory (bytes) Local Consumption Memory (bytes) 𝚫 (bytes)
addglobalcontext 25.38 KiB 26.44 KiB 1.07 KiB
addaction 45.63 KiB 101.36 KiB 55.73 KiB
addtiming 25.16 KiB 27.17 KiB 2.01 KiB
adderror 49.80 KiB 90.91 KiB 41.11 KiB
startstopsessionreplayrecording 24.08 KiB 26.18 KiB 2.10 KiB
startview 421.05 KiB 438.63 KiB 17.58 KiB
logmessage 42.33 KiB 43.41 KiB 1.07 KiB

🔗 RealWorld

@cy-moi cy-moi changed the title [RUM-10415] add privacy allowlist support treewalker [RUM-10415] add privacy allowlist support (with treewalker) Aug 1, 2025
@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from 8832509 to d5204d3 Compare August 7, 2025 14:05
@cy-moi
cy-moi changed the base branch from main to congyao/RUM-11139-use-treewalker-in-action-name August 7, 2025 14:05
@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from d5204d3 to 77c36ca Compare August 7, 2025 15:36
Base automatically changed from congyao/RUM-11139-use-treewalker-in-action-name to main August 18, 2025 10:33
@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from eaaebde to 763bf92 Compare August 18, 2025 15:00
@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from 763bf92 to e43093f Compare August 19, 2025 09:15
@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from 280fa9a to e9d99bc Compare August 19, 2025 10:12
Comment on lines +195 to +200
const {
enablePrivacyForActionName,
actionNameAttribute: userProgrammaticAttribute,
defaultPrivacyLevel,
} = rumConfiguration
const enableAllowlistMask = isAllowlistMaskEnabled(defaultPrivacyLevel, getNodeSelfPrivacyLevel(element))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: use the censorText function

Comment on lines +148 to +155
// 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

@BenoitZugmeyer BenoitZugmeyer Aug 20, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@cy-moi cy-moi Aug 21, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +157 to +161
const nodePrivacyLevel = isAllowlistMaskEnabled(configuration.defaultPrivacyLevel, nodeSelfPrivacy)
? nodeSelfPrivacy
: configuration.enablePrivacyForActionName
? nodeSelfPrivacy
: NodePrivacyLevel.ALLOW

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: use getNodePrivacyLevel instead, so that privacy level follows the privacy inheritance rules.

Comment on lines +1025 to +1029
// Without allowlist, should behave like mask mode
window.$DD_ALLOW = undefined
registerCleanupTask(() => {
window.$DD_ALLOW = undefined
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +12 to +22
const clickActionBase: ClickActionBase = {
type: ActionType.CLICK,
name: '',
nameSource: ActionNameSource.TEXT_CONTENT,
target: {
selector: 'button',
width: 100,
height: 100,
},
position: { x: 0, y: 0 },
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from 96a7dd0 to b6e655d Compare August 21, 2025 10:43
@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from b6e655d to 0408c56 Compare August 21, 2025 10:46
@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from bb5332c to 70059f3 Compare August 22, 2025 13:29
Comment thread packages/rum-core/src/domain/privacy.ts Outdated
Comment on lines +281 to +285
declare global {
interface Window {
$DD_ALLOW?: Set<string>
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +41 to +49
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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: maybe move those functions back to privacy.ts as they are not constants

@cy-moi
cy-moi force-pushed the congyao/RUM-10415-add-privacy-allowlist-support-treewalker branch from 1f1e3b1 to acb4d33 Compare August 25, 2025 08:10
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.

2 participants