Skip to content

🐛 [RUM-0000] Fix cookie parsing losing pairs after a valueless segment - #4978

Open
ofri-peretz wants to merge 2 commits into
DataDog:mainfrom
ofri-peretz:fix/cookie-parsing-linear
Open

🐛 [RUM-0000] Fix cookie parsing losing pairs after a valueless segment#4978
ofri-peretz wants to merge 2 commits into
DataDog:mainfrom
ofri-peretz:fix/cookie-parsing-linear

Conversation

@ofri-peretz

Copy link
Copy Markdown

Motivation

findCommaSeparatedValue, findAllCommaSeparatedValues and findCommaSeparatedValues share one regex:

const COMMA_SEPARATED_KEY_VALUE = /(\S+?)\s*=\s*(.*?)(?:;|$)/g

It has two problems, and the correctness one is the reason for this PR.

It stops finding cookies after a segment with no =

After such a segment the next match starts on the ;, so the captured name becomes ;second and every pair beyond that point is unreachable:

findCommaSeparatedValue('a=1;;b=2', 'b')          // undefined
findCommaSeparatedValue('noequals;foo=1', 'foo')  // undefined

Browsers produce those. document.cookie = 'foo' stores a cookie with no name, and reading document.cookie back returns a bare value with no =. One of those anywhere in the jar hides every cookie written after it — including _dd_s, since getCookie() and getInitCookie() both go through this function, as does getDocumentTraceId() for trace-id / trace-time.

It backtracks quadratically

(\S+?) expands one character at a time from every start position looking for an =, and the g flag repeats that from each index:

input parse time
8,000 18.5 ms
32,000 274.0 ms
128,000 4,464.6 ms
512,000 69,608.6 ms

document.cookie is attacker-influenced on any site that lets a visitor set a cookie, and this runs on the main thread on every session read.

Changes

One forEachCommaSeparatedValue helper that walks the string by index — indexOf(';'), indexOf('='), slice — and the three public functions built on it. Linear, and the 512,000-character row above becomes 0.07 ms.

Worth noting what is deliberately unchanged: \s*=\s* trimmed whitespace around the separator but nothing trimmed the end of a value, so foo = a ; yields 'a '. The new code reproduces that exactly, and the existing white-space test pins it.

Test plan

  • Every existing expectation in stringUtils.spec.ts verified against the new implementation, including the special-characters case (!#$%&'*+-.^_\|~), foo=a=b`, and the leading/trailing empty-value cases.
  • New specs for the two lost-cookie cases (a=1;;b=2, noequals;foo=1) and for findAllCommaSeparatedValues across a valueless segment. Each fails on the old regex and passes on this one.
  • New spec asserting a 512 KB separator-free string parses in under a second — it takes ~70 s on the old pattern.
  • I could not run karma locally (it wants a full monorepo install and a browser). The behavioural checks above were run against a faithful copy of the shipped implementation; if CI disagrees I will fix it promptly.

The commit is SSH-signed per CONTRIBUTING; it may show as unverified until I finish registering the key with GitHub.

🤖 Generated with Claude Code

findCommaSeparatedValue and friends shared a regex that resumed matching on
the semicolon after a segment with no "=" in it, so the name came out as
";second" and every pair beyond that point became unreachable:

  findCommaSeparatedValue("a=1;;b=2", "b")         // undefined
  findCommaSeparatedValue("noequals;foo=1", "foo") // undefined

Browsers produce those: document.cookie = "foo" stores a cookie with no
name, and reading document.cookie back gives a bare value with no "=". One
of those anywhere in the jar hid every cookie written after it, including
the SDK own session cookie.

The same pattern also backtracked quadratically, and document.cookie is
attacker-influenced on any site that lets a visitor set one:

    8,000 chars      18.5ms
   32,000 chars     274.0ms
  128,000 chars   4,464.6ms
  512,000 chars  69,608.6ms

Scanning by index has neither behaviour and is linear; the last row becomes
0.07ms.
@ofri-peretz
ofri-peretz requested a review from a team as a code owner August 25, 2026 02:46
@github-actions

Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


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


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

…acking

Adds eslint-plugin-secure-coding and enables no-redos-vulnerable-regex on
packages/browser-core/src/tools/utils, the directory the previous commit
fixed. Without a rule the next lazy quantifier written against
document.cookie ships the same stall again.

Scoped rather than repository-wide: tools/utils is clean under the rule
today, while tools/stackTrace/computeStackTrace.ts:156 reports and deserves
its own change.

Pinned to 4.3.0, published nine days ago, so it clears the 7-day
minimumReleaseAge in renovate.json rather than asking for an exception.
@ofri-peretz

Copy link
Copy Markdown
Author

Added the regression guard, which the first push was missing.

eslint-plugin-secure-coding@4.3.0 as a devDependency, with no-redos-vulnerable-regex enabled on packages/browser-core/src/tools/utils/*.ts — the directory this PR fixes. Without a rule, the next lazy quantifier written against document.cookie reintroduces the same main-thread stall and nothing catches it.

Three things I checked before proposing a dependency:

  • minimumReleaseAge. renovate.json sets "7 days". Rather than ask for an exception, the pin is 4.3.0 — published nine days ago, so it already satisfies your own policy.
  • The scope is clean today. tools/utils reports zero findings under this rule, so the block turns CI red only if a new one is introduced. Repository-wide would have failed immediately: tools/stackTrace/computeStackTrace.ts:156 reports, because GECKO_LINE_RE trades characters between (.*?) and the alternation that follows. That's a real finding and a separate fix — happy to send it next if you'd like the scope widened.
  • Exact pin, matching defaultSemverRangePrefix: '' in your .yarnrc.yml.

The lockfile was updated with yarn add -D --mode=update-lockfile, so yarn.lock moves but nothing else does.

@sbarrio
sbarrio requested a review from bdibon August 25, 2026 07:08
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