Skip to content

🐛 Sanitize and validate name in startView/setViewName - #4969

Draft
carlosdelest wants to merge 2 commits into
mainfrom
carlos.delgado/startview-setviewname-sanitize-fc6c63
Draft

🐛 Sanitize and validate name in startView/setViewName#4969
carlosdelest wants to merge 2 commits into
mainfrom
carlos.delgado/startview-setviewname-sanitize-fc6c63

Conversation

@carlosdelest

Copy link
Copy Markdown

Motivation

RUM view events were landing with custom.view.name as a nested object (e.g. {"name": "test"}) instead of a string, breaking downstream consumers that expect view.name to be a string.

Every other name-bearing public RUM API (addAction, startAction, addTiming, addDurationVital, addFeatureFlagEvaluation, ...) sanitizes its name argument before it reaches an event. startView and setViewName were the exceptions — nothing validated or coerced the type of name, so a caller passing a non-string value (e.g. startView({ name: { name: 'test' } })) flowed unchanged into the emitted event.

Changes

  • packages/browser-rum-core/src/boot/rumPublicApi.ts: added a sanitizeStringOption() helper. Non-string values (excluding undefined) are dropped with a display.warn(); valid strings are passed through sanitize(). Wired into startView (name, service, version) and setViewName.
  • packages/browser-rum-core/src/domain/view/trackViews.ts: added a matching sanitizeViewName() guard, applied where name is assigned in newView() and setViewName(), as defense-in-depth in case an internal caller bypasses the public API.
  • Added unit tests covering object/number/null name inputs for both startView and setViewName, confirming view.name becomes undefined (never an object) and a warning is logged, and that existing valid-string behavior is unchanged.

Note that sanitize() alone does not fix this: it deep-clones objects into JSON-safe form but does not coerce them to strings, so an explicit runtime type guard was required.

Test instructions

  1. yarn test:unit --spec packages/browser-rum-core/src/boot/rumPublicApi.spec.ts
  2. yarn test:unit --spec packages/browser-rum-core/src/domain/view/trackViews.spec.ts
  3. Manually: in the sandbox app, call DD_RUM.startView({ name: { name: 'test' } }) and confirm the resulting view event has no view.name and a console warning appears.

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

Every other name-bearing public RUM API sanitizes its name argument
before it reaches an event, but startView and setViewName never did.
A caller passing a non-string name (e.g. an object) landed unchanged
in the view event, producing a malformed custom.view.name that broke
downstream consumers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@carlosdelest

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

Comment on lines +734 to +742
strategy.startView({
...sanitizedOptions,
...('name' in sanitizedOptions && { name: sanitizeStringOption(sanitizedOptions.name, 'view name') }),
...(sanitizedOptions.service !== undefined && {
service: sanitizeStringOption(sanitizedOptions.service, 'view service'),
}),
...(sanitizedOptions.version !== undefined && {
version: sanitizeStringOption(sanitizedOptions.version, 'view version'),
}),

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: Can we keep things simple, like:

strategy.startView({
  ...sanitizedOptions,
  name: sanitizeStringOption(sanitizedOptions.name, '...'),
  service: sanitizeStringOption(sanitizedOptions.service, '...'),
  version: sanitizeStringOption(sanitizedOptions.version, '...'),
  handlingStack,
})

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.

Also, this might be a minor breaking change. Before, this kind of worked:

DD_RUM.startView({ version: 42 })

Maybe it's fine though.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good call — simplified to the direct form you suggested (pushed in 8376d49). It does mean service/version now always show up as explicit keys (possibly undefined) in the object passed to strategy.startView, same as name already did before this PR — consistent with how every other sanitized field in this file is handled.

this might be a minor breaking change

Yep. DD_RUM.startView({ version: 42 }) will now have version dropped (with a console warning) instead of silently flowing a non-string into the event.

Given the bug report was exactly this kind of type mismatch reaching the intake schema, I think it's the right tradeoff, but happy to change if you'd rather prefer for this stay lenient for service/version specifically (as opposed to name).


let sessionIsActive = true
let name = viewOptions?.name
let name = sanitizeViewName(viewOptions?.name)

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: Why do we sanitize the name twice?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ouch — turns out we don't need to. Every call path (rumPublicApi.startView/setViewName, all the framework integrations, and the pre-start buffering in preStartRum.ts) already goes through rumPublicApi's sanitization before viewOptions/updatedName ever reaches trackViews.ts.

Removed the redundant guard there in 8376d49 — single sanitization point at the public API boundary.

Address review feedback on #4969: keep the startView option spread
simple (matching the pattern used elsewhere in this file) instead of
conditionally spreading each field, and remove the sanitizeViewName
guard in trackViews.ts since every call path already goes through
rumPublicApi's sanitization before reaching the strategy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cit-pr-commenter-54b7da

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 181.20 KiB 181.46 KiB +268 B +0.14%
Rum Profiler 8.43 KiB 8.43 KiB 0 B 0.00%
Rum Recorder 22.31 KiB 22.31 KiB 0 B 0.00%
Logs 57.52 KiB 57.52 KiB 0 B 0.00%
Rum Salesforce N/A 139.48 KiB N/A N/A N/A
Rum Slim 139.21 KiB 139.47 KiB +266 B +0.19%
Worker 22.96 KiB 22.96 KiB 0 B 0.00%
Rum Shopify N/A 202.39 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.72 KiB N/A N/A N/A

@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Aug 21, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 75.00%
Overall Coverage: 77.11% (-0.01%)

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

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