⚗️ Collect click-target attributes as a facetable key-value map - #5002
Draft
LFBarreto wants to merge 11 commits into
Draft
⚗️ Collect click-target attributes as a facetable key-value map#5002LFBarreto wants to merge 11 commits into
LFBarreto wants to merge 11 commits into
Conversation
Add sanitized href and aria-label to the click action composed_path_selector. This helps identify the clicked element without exposing PII. The href value keeps only the origin and the path. The path removes segments that look like generated identifiers. The href value drops the query string and the hash. A non-http(s) href keeps only the scheme. The aria-label value follows the same privacy rules as action names. This feature is behind the composed_path_selector_attributes experimental flag. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Bundles Sizes Evolution
|
Run Prettier to fix a formatting issue. Fix the e2e assertion to match CSS.escape output for spaces and slashes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a preceding sibling in the test body so the link's nth-child position stays fixed. Without it, the async and npm CI test setups append an init script after </body>. Browsers reparent that script into <body> as a sibling, which changed the number of children and added an unexpected nth-child to the selector. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Change the href sanitization to keep the (deduplicated) query parameter names while still dropping their values. Parameter names are typically static field names, not user data, so keeping them makes the selector more useful without exposing what was in the query string. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Do not apply the privacy-level masking to href. Its own sanitization (dropping query values, the hash, and any non-http(s) payload) is already the intended protection for this attribute, regardless of the privacy level in effect. aria-label keeps the existing masking behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix bugs found by code review: - Exclude href and aria-label from the safe-attributes allowlist, so setting actionNameAttribute to either no longer leaks the raw, unsanitized value alongside the sanitized one. - Detect the href's non-http(s) scheme from the parsed URL instead of a regex on the raw string, so a leading whitespace or control character can no longer bypass the scheme-only shortcut and leak the full payload (an email address, a script...). - Mask aria-label at the hidden and ignore privacy levels too, not only mask and mask-unless-allowlisted, matching how session replay already handles them. - Compare the resolved origin against the current page's origin to decide whether to show it, instead of pattern-matching the raw href, so a backslash-led href cannot hide a real cross-origin navigation. - Decode a path segment before checking if it looks generated, so a percent-encoded non-ASCII segment (ex: an accented word) is not redacted by mistake. - Resolve an empty href to the current document instead of treating it as absent. Also extract the aria-label masking logic into a shared, exported maskAttributeIfNeeded in privacy.ts, and fix a test cleanup that used an inline delete instead of registerCleanupTask. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The backslash-led href test relied on old Chrome/Edge (BrowserStack legacy devices) resolving "\\evil.example/..." as a file: URL, unlike current browsers, which resolve it as http:. Replace it with a <base> element pointing to a different origin, which resolves consistently across browsers and still exercises the same origin-comparison code path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add `_dd.action.target.attributes`, a key-value map of href, aria-label, name, title, alt, id, role, and data-* attributes collected from a click's composedPath, behind a new `composed_path_selector_attributes_map` experimental flag. This lets customers filter and group RUM click actions by these values, which the existing `composed_path_selector` CSS-like string does not support. href and aria-label are no longer collected in `composed_path_selector`, to avoid collecting the same PII-sensitive data in two fields. Every collected value is dropped if it contains an email address or a digit, regardless of privacy level, so the map never carries a numeric identifier or an email address. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`maskAttributeIfNeeded` was gated behind `enablePrivacyForActionName`, so disabling that action-name-only option would also disable masking for the new attributes facet map. `data-dd-privacy` was leaking into the map as a regular data-* attribute, and `placeholder` was missing from the masked-text set despite the code claiming parity with `shouldMaskAttribute`. The new e2e test also enabled the wrong experimental-flag string, so it silently ran against unflagged behavior. Also dedupes `FILTERED_TAGNAMES` into a shared export, fixes a stale comment, and adds dedicated `urlSanitizer.spec.ts` coverage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3 tasks
Not asked for in the original request; removing to keep the collected attribute set limited to what customers actually need.
id, role, and data-* commonly carry digits customers want (product ids, SKUs, test ids). The blanket digit-drop now applies only to aria-label/name/title/alt, which are the free-form text attributes most likely to leak a raw identifier when unmasked. The email check still applies to every attribute.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
_dd.action.target.composed_path_selectoridentifies the clicked element for auto-collected click actions. It is a single CSS-like string. Customers cannot filter or group RUM click actions by one attribute value, for examplehreforaria-label.This change adds a key-value map of click-target attributes. Customers can use it as a facet to filter and group click actions.
Design rationale, prior-art comparison (Heap, PostHog, FullStory, Amplitude, GA4), and customer motivation are written up in RFC: Collecting Click-Target Attributes as a Facetable Key-Value Map.
JIRA: PANA-8907
This field is kept under
_dd.*for now. Promoting it to the publicaction.targetschema needs a companion PR inrum-events-format: DataDog/rum-events-format#439 (draft).Changes
_dd.action.target.attributes, a key-value map collected from a click'scomposedPath(). The map can containhref,aria-label,name,title,alt,id,role, anddata-*attributes.hrefis collected only from<a>and<area>elements, and only after the same structural sanitization already used incomposed_path_selector(origin plus path, generated segments replaced with?, query values dropped, query names kept, hash dropped). This sanitization now lives in a sharedurlSanitizer.ts, with its own unit tests.aria-label,name,title, andaltgo through the existing privacy masking pipeline (defaultPrivacyLevel,data-dd-privacy,$DD_ALLOW), independently of theenablePrivacyForActionNameoption, which only controls masking of the action name.data-*attributes go through the same masking pipeline, except for a small allowlist of test-id-style attributes (data-testid,data-cy, ...), which stay unmasked. The SDK's owndata-dd-privacyattribute is excluded from this wildcard collection.idandroleare collected as-is. They are not part of the masked set.aria-label,name,title, andaltvalues are also dropped if they contain a digit, since that free-form text can carry a raw identifier (e.g. a phone number) when unmasked.href,id,role, anddata-*are exempt from the digit check: digits are the common, wanted case there (product ids, SKUs, test ids).composed_path_selector_attributes_mapexperimental flag.hrefandaria-labelare removed fromcomposed_path_selector. They stay in the new attributes map only, so the SDK does not collect the same PII-sensitive data in two fields.See
getComposedPathAttributes.tsfor the implementation.Test instructions
yarn test:e2e -g "composed path"covers the attribute map and the selector string, with the correct experimental flag enabled.yarn dev, addenableExperimentalFeatures: ['composed_path_selector_attributes_map']to the sandboxDD_RUM.init()call insandbox/index.html, click a link with a query string and an icon button with anaria-label, and confirm_dd.action.target.attributeson the intake payload contains the sanitizedhrefand the maskedaria-label, with no digits or email addresses in any value.Checklist