Skip to content

chore(deps): update dependency browserslist to v4.28.7 [security] - #134

Open
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/npm-browserslist-vulnerability
Open

chore(deps): update dependency browserslist to v4.28.7 [security]#134
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/npm-browserslist-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
browserslist 4.22.24.28.7 age confidence

Browserslist: Uncaught crash / prototype write via untrusted browserslist-stats.json custom stats (normalizeStats)

CVE-2026-73088 / GHSA-73wf-gq98-2v4g

More information

Details

Vulnerability Details

File: node.js
Function: normalizeStats() (line ~214), reached from getStat() (called
unconditionally on every browserslist() call) and loadStat()

Root Cause
function normalizeStats(data, stats) {
  if (!data) { data = {} }
  if (stats && 'dataByBrowser' in stats) { stats = stats.dataByBrowser }
  if (typeof stats !== 'object') return undefined

  var normalized = {}
  for (var i in stats) {
    var versions = Object.keys(stats[i])
    if (versions.length === 1 && data[i] && data[i].versions.length === 1) {
      var normal = data[i].versions[0]
      normalized[i] = {}
      normalized[i][normal] = stats[i][versions[0]]
    } else {
      normalized[i] = stats[i]
    }
  }
  return normalized
}

stats is untrusted: it comes from JSON.parse()-ing a
browserslist-stats.json file — auto-discovered by walking up the directory
tree from the project root on every browserslist() call, regardless of
the query
(env.getStat(opts, browserslist.data) runs unconditionally
inside browserslist()) — or from opts.stats passed programmatically /
via the CLI's --stats= flag. data is browserslist.data, a plain object
populated only with real browser names.

Two independent bugs from the same root cause (unguarded for...in over
untrusted keys used with plain-object bracket access/assignment):

  1. Crash: data[i] has no hasOwnProperty guard. If stats contains a
    key that also happens to be an inherited Object.prototype member name —
    "__proto__", "toString", "valueOf", "constructor",
    "hasOwnProperty", "isPrototypeOf", etc. — data[i] resolves to that
    inherited function/object (always truthy), and the code then does
    data[i].versions.lengthundefined.lengthuncaught TypeError,
    for any such key whose JSON value has exactly one sub-key, e.g.:
    { "toString": { "onekey": 5 }, "chrome": { "100": 50 } }
  2. Prototype write: normalized[i] = ... on the fresh
    normalized = {} — if i is exactly "__proto__" (and normalized has
    no own property by that name yet), this computed assignment invokes the
    real Object.prototype.__proto__ setter, changing normalized's actual
    [[Prototype]] instead of creating a plain property.

Because this runs on every browserslist() call regardless of the
query, simply committing a poisoned browserslist-stats.json anywhere in a
project's directory tree breaks every subsequent Browserslist call in that
project — including calls made by Autoprefixer, Babel preset-env,
Stylelint, or PostCSS internally, for completely unrelated queries.

Attack Scenario
  1. Attacker submits a PR (or a compromised dependency) adding a
    browserslist-stats.json file anywhere between the project root and
    filesystem root, containing e.g.
    {"toString": {"onekey": 5}, "chrome": {"100": 50}}.
  2. The victim's build/CI pipeline runs any tool that calls browserslist()
    internally, for any query.
  3. The auto-discovered poisoned file crashes the process with an uncaught
    TypeError on the very first call.
Measured Impact

Confirmed crash (real browserslist() call, v4.28.6) with stats keys:
__proto__, toString, valueOf, hasOwnProperty, constructor,
isPrototypeOf — each paired with a one-key JSON object — for any query,
including browserslist('defaults') which never mentions stats.

Recommended Fix (implemented and verified)
var normalized = Object.create(null)
for (var i in stats) {
  var versions = Object.keys(stats[i])
  var known = Object.prototype.hasOwnProperty.call(data, i) && data[i]
  if (versions.length === 1 && known && known.versions.length === 1) {
    var normal = known.versions[0]
    normalized[i] = Object.create(null)
    normalized[i][normal] = stats[i][versions[0]]
  } else {
    normalized[i] = stats[i]
  }
}
return normalized

normalized uses Object.create(null) so a write to "__proto__" is an
ordinary property set, never a [[Prototype]] change; data[i] is replaced
with an explicit hasOwnProperty check so it never resolves to an inherited
Object.prototype member.

Verification:

  • NODE_ENV=test npx uvu test .test.js → 301/301 pass unmodified
    (test/custom.test.js, test/shareable-stats.test.js, test/cover.test.js
    exercise the stats-handling paths).
  • All 6 previously crash-inducing keys, tested individually, now resolve
    without error.
  • The realistic file-based auto-discovery scenario (poisoned
    browserslist-stats.json + an unrelated browserslist('defaults') call)
    now returns a normal result instead of crashing.
Impact
  • Who is affected: Any project whose build/CI invokes Browserslist
    (directly or via Autoprefixer/Babel/Stylelint/PostCSS) in a directory tree
    an attacker can place a file into (external PR, compromised dependency),
    or any app that passes user-influenced data into opts.stats.
  • What an attacker achieves: Immediate DoS — crashes the invoking
    process on the first Browserslist call after the file is present, for any
    query, no special syntax needed.
  • Conditions required: No authentication — only the ability to add a
    file to the project's directory tree, or influence opts.stats.
Verification Environment

browserslist @​ HEAD (== v4.28.6, current latest stable release) under local
Node.js v20.19.5. Pure JS library — executed directly, no server needed.

Note

Found via a systematic review of prototype-pollution-adjacent patterns in
this codebase after confirming two unrelated algorithmic-complexity issues
(reported separately as GHSA-rrmg-cfrq-23vv and GHSA-g6p8-hj8g-x889) in the
same research pass. A similar for...in + bracket-write pattern in
index.js's copyObject() (used by normalizeAndroidData) was already
guarded against __proto__/constructor/prototype keys by a prior,
unrelated commit — that guard was never applied to this function.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

browserslist/browserslist (browserslist)

v4.28.7

Compare Source

v4.28.6

Compare Source

v4.28.5

Compare Source

v4.28.4

Compare Source

  • Fixed SyntaxError regression of 4.28.3.

v4.28.3

Compare Source

  • Fixed baseline query case-insensitivity (by @​swwind).

v4.28.2

Compare Source

v4.28.1

Compare Source

  • Removed Baseline warning since we have it own warning.

v4.28.0

Compare Source

v4.27.0

Compare Source

  • Added BROWSERSLIST_TRACE_WARNING environment variable.

v4.26.3

Compare Source

v4.26.2

Compare Source

  • Fixed baseline-browser-mapping version requirement.

v4.26.1

Compare Source

  • Updated Firefox ESR.

v4.26.0

Compare Source

v4.25.4

Compare Source

v4.25.3

Compare Source

v4.25.2

Compare Source

  • Fixed Node.js --permission support (by @​broofa).

v4.25.1

Compare Source

  • Updated Firefox ESR.

v4.25.0

Compare Source

  • Added cover 95% in browserslist-config-mycompany stats query support.

v4.24.5

Compare Source

  • Fixed support ESM shared config.
  • Fixed docs (by Alexander Pushkov & マルコメ).

v4.24.4

Compare Source

v4.24.3

Compare Source

v4.24.2

Compare Source

  • Clarify outdated caniuse-lite warning text.

v4.24.1

Compare Source

  • Added months since last caniuse-lite update to the warning (by @​mezhnin).

v4.24.0

Compare Source

  • Added browserslist.findConfigFile() helper (by @​JLHwung).

v4.23.3

Compare Source

v4.23.2

Compare Source

  • Updated Firefox ESR.

v4.23.1

Compare Source

  • Fixed feature query with mobile to desktop when caniuse lags (by @​steverep).

v4.23.0

Compare Source

v4.22.3

Compare Source

  • Fixed white spaces support in supports query (@​g-plane).
  • Fixed shared config like @company/package/browserslist-config (@​boucodes).

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate

renovate Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📊 Package size report   No changes

File Before (Size / Gzip / Brotli) After (Size / Gzip / Brotli)
Total (Includes all files) 158.5 kB / 67.8 kB / 62.0 kB 158.5 kB / 67.8 kB / -0%↓62.0 kB
Tarball size 63.9 kB -0.02%↓63.8 kB
Unchanged files
File Size (Size / Gzip / Brotli)
.babelrc 144 B / 114 B / 106 B
.editorconfig 190 B / 142 B / 129 B
.eslintignore 14 B / 34 B / 18 B
.github/.misspell-fixer.ignore 18 B / 38 B / 22 B
.github/workflows/bundle-size.yml 761 B / 413 B / 355 B
.github/workflows/eslint.yml 586 B / 330 B / 260 B
.github/workflows/github-page.yml 975 B / 517 B / 436 B
.github/workflows/misspell.yml 681 B / 391 B / 323 B
.github/workflows/release-please.yml 1.4 kB / 559 B / 462 B
.github/workflows/test.yaml 783 B / 411 B / 334 B
.husky/pre-commit 58 B / 76 B / 62 B
.stylelintrc 214 B / 138 B / 115 B
CHANGELOG.md 4.8 kB / 975 B / 802 B
CODE_OF_CONDUCT.md 3.2 kB / 1.5 kB / 1.1 kB
code-typing.png 25.7 kB / 24.9 kB / 24.8 kB
demo/index.html 844 B / 437 B / 253 B
demo/index.js 1.4 kB / 630 B / 536 B
demo/webpack.config.js 816 B / 439 B / 373 B
dist/motorcortex-code-typing.cjs.js 37.3 kB / 10.4 kB / 9.3 kB
dist/motorcortex-code-typing.esm.js 37.3 kB / 10.4 kB / 9.2 kB
dist/motorcortex-code-typing.umd.js 27.4 kB / 9.1 kB / 8.2 kB
LICENCE 1.1 kB / 649 B / 466 B
package.json 2.6 kB / 997 B / 866 B
README.md 3.8 kB / 1.4 kB / 1.2 kB
renovate.json 174 B / 138 B / 117 B
rollup.config.js 941 B / 369 B / 321 B
src/CodeEditorClip.js 1.2 kB / 529 B / 420 B
src/index.js 757 B / 311 B / 250 B
src/themes/dark.js 1.1 kB / 419 B / 319 B
src/WriteCode.js 673 B / 362 B / 301 B
webpack.config.js 1.7 kB / 742 B / 631 B

🤖 This report was automatically generated by pkg-size-action

@renovate
renovate Bot force-pushed the renovate/npm-browserslist-vulnerability branch from f36443b to ca26111 Compare September 3, 2026 23:45
@sonarqubecloud

sonarqubecloud Bot commented Sep 3, 2026

Copy link
Copy Markdown

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.

0 participants