Skip to content

✨ Add href and aria-label to composed_path_selector - #5002

Draft
LFBarreto wants to merge 7 commits into
mainfrom
luiz.barreto/composed-path-selector-attributes
Draft

✨ Add href and aria-label to composed_path_selector#5002
LFBarreto wants to merge 7 commits into
mainfrom
luiz.barreto/composed-path-selector-attributes

Conversation

@LFBarreto

@LFBarreto LFBarreto commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Motivation

_dd.action.target.composed_path_selector helps identify the clicked element for auto-collected click actions, but it does not collect href or aria-label. These two attributes make the selector more useful: href identifies which link was clicked, and aria-label identifies icon-only controls that have no visible text.

We add both attributes while keeping the field PII-safe. No JIRA ticket exists for this change yet.

Changes

  • getComposedPathSelector now takes the RUM configuration instead of a single attribute name.
  • href is reduced to the origin plus a path where generated-looking segments (segments with a digit) are replaced with ?. The hash and any non-http(s) payload (mailto:, tel:, javascript:, data:, ...) are dropped. Query string values are dropped too, but the (deduplicated) parameter names are kept, since they are typically static field names rather than user data. This sanitization is href's only protection — it is never masked by the privacy level, data-dd-privacy, or enablePrivacyForActionName.
  • aria-label goes through the same masking pipeline as action names (enablePrivacyForActionName, defaultPrivacyLevel, data-dd-privacy, $DD_ALLOW).
  • Both attributes are gated behind a new composed_path_selector_attributes experimental flag, so we can validate cardinality and PII exposure before turning them on by default.
  • _dd.action.target.composed_path_selector stays outside beforeSend's modifiable fields, so the field's format stays stable for anything parsing it downstream. The sanitization above is therefore the only protection href has, by design.

See getComposedPathSelector.ts for the implementation.

Test instructions

  • Unit tests: yarn test:unit --spec packages/browser-rum-core/src/domain/getComposedPathSelector.spec.ts --spec packages/browser-rum-core/src/domain/action/trackClickActions.spec.ts
  • E2E test: yarn test:e2e -g "composed path selector"
  • Manual check: run yarn dev, add enableExperimentalFeatures: ['composed_path_selector_attributes'] to the sandbox DD_RUM.init() call in sandbox/index.html, click a link with a query string and an icon button with an aria-label, and confirm _dd.action.target.composed_path_selector on the intake payload contains the sanitized href (origin, grouped path, query param names only, never masked) and the aria-label (masked according to the privacy level), with no raw query values or tokens.

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

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>
@datadog-official

datadog-official Bot commented Aug 31, 2026

Copy link
Copy Markdown

Tests

All CI checks and tests passed.

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 73.21%
Overall Coverage: 77.04% (+0.01%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 3808b57 | Docs | View more details | Give us feedback!

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 31, 2026

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 181.63 KiB 182.86 KiB +1.24 KiB +0.68%
Rum Profiler 8.43 KiB 8.43 KiB 0 B 0.00%
Rum Recorder 25.32 KiB 25.32 KiB 0 B 0.00%
Logs 57.93 KiB 58.00 KiB +72 B +0.12%
Rum Salesforce N/A 140.88 KiB N/A N/A N/A
Rum Slim 139.68 KiB 140.88 KiB +1.19 KiB +0.85%
Worker 22.96 KiB 22.96 KiB 0 B 0.00%
Rum Shopify N/A 207.22 KiB N/A N/A N/A
Rum-shopify Profiler N/A 8.43 KiB N/A N/A N/A
Rum-shopify Recorder N/A 3.74 KiB N/A N/A N/A

LFBarreto and others added 6 commits August 31, 2026 14:35
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>
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.

1 participant