-
Notifications
You must be signed in to change notification settings - Fork 162
🐛 Add mask-unless-allowlisted privacy level support for standard attr #3907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
ebfde81 to
1ad9e51
Compare
Bundles Sizes Evolution
🚀 CPU Performance
🧠 Memory Performance
|
|
✅ Tests 🎉 All green!❄️ No new flaky tests detected 🎯 Code Coverage 🔗 Commit SHA: 16c8de1 | Docs | Datadog PR Page | Was this helpful? Give us feedback! |
0913df3 to
168b5d1
Compare
168b5d1 to
71674a9
Compare
|
/to-staging |
|
View all feedbacks in Devflow UI.
Commit 71674a9ec7 will soon be integrated into staging-42.
Commit 71674a9ec7 has been merged into staging-42 in merge commit 58ffb86f27. Check out the triggered pipeline on Gitlab 🦊 If you need to revert this integration, you can use the following command: |
… into staging-42 Integrated commit sha: 71674a9 Co-authored-by: cy-moi <[email protected]>
…utes action names
9932c44 to
06574d5
Compare
| rumConfiguration: RumConfiguration | ||
| ): ActionName { | ||
| const { enablePrivacyForActionName, defaultPrivacyLevel } = rumConfiguration | ||
| const nodeSelfPrivacyLevel = getNodePrivacyLevel(element, defaultPrivacyLevel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I would avoid calling this nodeSelfPrivacyLevel, because that phrase has a particular meaning in the code (it's the privacy level of a node ignoring its ancestors), and this variable has a different meaning (it contains the privacy level of element considering its ancestors).
| const nodeSelfPrivacyLevel = getNodePrivacyLevel(element, defaultPrivacyLevel) | |
| const nodePrivacyLevel = getNodePrivacyLevel(element, defaultPrivacyLevel) |
| return { | ||
| name: element.getAttribute(attribute) || '', | ||
| name: | ||
| enablePrivacyForActionName && nodeSelfPrivacyLevel && shouldMaskNode(element, nodeSelfPrivacyLevel, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it makes sense to check if nodeSelfPrivacyLevel is truthy, because getNodePrivacyLevel always returns a valid privacy level:
| enablePrivacyForActionName && nodeSelfPrivacyLevel && shouldMaskNode(element, nodeSelfPrivacyLevel, false) | |
| enablePrivacyForActionName && shouldMaskNode(element, nodeSelfPrivacyLevel, false) |
Looking at the bigger picture, it's unfortunate that we do all of the work of calling getNodePrivacyLevel() (which can be a bit expensive) and then ignore the result if enablePrivacyForActionName is false. It would be better to restructure this function so that you check enablePrivacyForActionName first, and then only call getNodePrivacyLevel() if enablePrivacyForActionName is true.
| * node if it is ignored or hidden? it doesn't matter since it won't be serialized). | ||
| */ | ||
| export function shouldMaskNode(node: Node, privacyLevel: NodePrivacyLevel) { | ||
| export function shouldMaskNode(node: Node, privacyLevel: NodePrivacyLevel, isTextContent: boolean = true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding this extra argument to shouldMaskNode(), I would suggest creating a new shouldMaskAttribute() function in this file and migrating as much of the logic as you can from serializeAttribute() in serializeAttribute.ts into shouldMaskAttribute(). This would give us a central place to control privacy for attributes, similar to what we already have for nodes, instead of spreading the logic between multiple places in the code.
I think that you should be able to restructure serializeAttribute() so that it more or less looks like this:
export function serializeAttribute() {
if (/* HIDDEN */) {
// Handle HIDDEN case.
return ...
}
if (shouldMaskAttribute(...)) {
if (/* is one of the image cases */) {
// Handle image cases.
return ...
}
// We're in a non-image case.
return CENSORED_STRING_MARK
}
// Handle non-HIDDEN, non-masked cases...
return
}With this setup, we'll ensure that action names and session replay handle attribute masking in a consistent way.
| rumConfiguration: RumConfiguration | ||
| ): ActionName { | ||
| const { enablePrivacyForActionName, defaultPrivacyLevel } = rumConfiguration | ||
| const nodeSelfPrivacyLevel = getNodePrivacyLevel(element, defaultPrivacyLevel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔨 warning: It's very inefficient to call getNodePrivacyLevel over and over again for each attribute / element ancestors. You could use the cache argument to make it faster.
| name: | ||
| enablePrivacyForActionName && nodeSelfPrivacyLevel && shouldMaskNode(element, nodeSelfPrivacyLevel, false) | ||
| ? maskDisallowedTextContent(attributeValue || '', ACTION_NAME_PLACEHOLDER) | ||
| : attributeValue || '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 suggestion: maybe don't do the expensive operations if the attribute is missing
let attributeValue = element.getAttribute(attribute)
if (attributeValue) {
const nodePrivacyLevel = ...
etc.
} else {
attributeValue = ''
}
return { name: attributeValue, nameSource: ActionNameSource.STANDARD_ATTRIBUTE }… code according to comments
40849d8 to
16c8de1
Compare
| let nodeSelfPrivacyLevel | ||
| if (attributeValue) { | ||
| nodeSelfPrivacyLevel = getNodePrivacyLevel(element, defaultPrivacyLevel, nodePrivacyLevelCache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion:
- It should be
nodePrivacyLevel, notnodeSelfPrivacyLevel - You can use
constsince you only define it once - You can shortcut the whole block with
&& enablePrivacyForActionName
if (attributeValue && enablePrivacyForActionName) {
const nodePrivacyLevel = getNodePrivacyLevel(element, defaultPrivacyLevel, nodePrivacyLevelCache)| switch (attributeName) { | ||
| case 'title': | ||
| case 'alt': | ||
| case 'name': | ||
| case 'placeholder': | ||
| case 'aria-label': | ||
| return true | ||
| } | ||
| if (tagName === 'A' && attributeName === 'href') { | ||
| return true | ||
| } | ||
| if (tagName === 'IFRAME' && attributeName === 'srcdoc') { | ||
| return true | ||
| } | ||
| if (attributeValue && attributeName.startsWith('data-')) { | ||
| return true | ||
| } | ||
| if (tagName === 'IMG' || tagName === 'SOURCE') { | ||
| return true | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔨 warning: you changed the logic while moving the code: it makes it hard to catch the exact changes here. Don't hesitate to use multiple commits.
The logic changed and now:
nameandaria-labelattributes are masked. Could you add it to the "change" section of the PR? And explain the motivation.- all attributes of
imgandsourceelements are masked. This sounds quite problematic, because attributes likeclassoridwill be masked and it will break the replay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. @cy-moi , it might make sense to make the name and aria-label changes in a separate PR, since this is a policy change and not just a refactor. I do think it's a good idea! But making it in a separate PR could have several advantages:
- Easier to review.
- Easier to back out the change if we discover there's a reason to do so.
- It'd be clearly announced in the changelog.
Re: the img and source changes, I think that's probably just a bug, right? The original code in serializeAttribute.ts masks the src and srcset on img and source elements, but it doesn't mask all attributes on those elements.
Motivation
Following up on the new
mask-unless-allowlistedbehavior, in which case we mask with allowlists whenshouldMaskNodeis true andenablePrivacyForActionNameturned on. However, it seems like we did not cover standard attributes.Changes
Make masking for standard attributes the same as the textual content.
Test instructions
Should pass CI
Note: This PR has a dependency on https://github.com/DataDog/web-ui/pull/237816 when testing on staging.
Checklist