Skip to content

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

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

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

Conversation

@renovate

@renovate renovate Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
browserslist 4.23.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).


Browserslist: Unbounded memory growth (no cache eviction) via distinct query results, leading to eventual OOM

CVE-2026-73089 / GHSA-c83g-rgw3-j3cx

More information

Details

Vulnerability Details

File: index.js
Location: cache (browserslist()'s result cache, line ~402) and
parseCache (parseQueries()'s AST cache)

Root Cause
var cache = {}
var parseCache = {}

function browserslist(queries, opts) {
  ...
  var cacheKey = JSON.stringify([queries, context])
  if (cache[cacheKey]) return cache[cacheKey]
  ...
  if (!env.env.BROWSERSLIST_DISABLE_CACHE) { cache[cacheKey] = result }
  return result
}

function parseQueries(queries) {
  var cacheKey = JSON.stringify(queries)
  if (cacheKey in parseCache) return parseCache[cacheKey]
  var result = parseWithoutCache(QUERIES, queries)
  if (!env.env.BROWSERSLIST_DISABLE_CACHE) { parseCache[cacheKey] = result }
  ...
}

Every distinct (queries, context) pair is cached forever — no size cap,
TTL, or eviction. browserslist.clearCaches() never resets either object
(it only resets node.js's own filesystem caches); the only opt-out is the
BROWSERSLIST_DISABLE_CACHE env var, controlled by the calling
application
, not an attacker.

Some short, valid queries amplify this badly. The since <year>-<month>-<day>
query type (/^since (\d+)-(\d+)-(\d+)$/i) accepts any digit
combination — Date.UTC() normalizes rather than rejects out-of-range
values — giving an effectively unbounded space of ~17-byte distinct cache
keys, each of which resolves to (and caches) a result close to the full
~8.5 KB browser list for any sufficiently old year.

Measured Impact

20,000 distinct since <year>-<month>-<day> queries (~330 KB total input,
--expose-gc before/after measurement to rule out uncollected garbage)
retained over 50 MB of heap permanently — roughly 150x
amplification, growing linearly with no cap observed up to 40,000 queries
(52.3 MB).

Attack Scenario

Any long-running process (server, daemon, warm CI worker) that calls
browserslist() with a query value that varies across requests/items and is
influenced, even partially, by external input accumulates one cache entry
per distinct value ever seen. An attacker who can influence that value
across many requests (this is a volumetric attack, unlike the
single-request DoS findings from this same research pass) sends a stream of
cheap, distinct queries (e.g. since 1900-01-01, since 1900-01-02, ...)
until the process runs out of memory and crashes.

Recommended Fix (implemented and verified)

Replace both plain-object caches with Maps bounded to a fixed maximum
entry count, evicting the oldest entry once the cap is reached (Map
preserves insertion order, so .keys().next().value is always oldest):

var CACHE_MAX_ENTRIES = 500

function boundedCacheSet(map, key, value) {
  if (map.size >= CACHE_MAX_ENTRIES) {
    map.delete(map.keys().next().value)
  }
  map.set(key, value)
}

var cache = new Map()
var parseCache = new Map()

(read sites changed to .has()/.get(), write sites to boundedCacheSet())

Verification:

  • NODE_ENV=test npx uvu test .test.js → 301/301 pass unmodified
    (test/cache.test.js exercises clearCaches()/BROWSERSLIST_DISABLE_CACHE
    against node.js's separate filesystem caches, unaffected here); confirmed
    a repeated identical call still returns the cached reference.
  • Re-ran the memory PoC post-fix: heap stayed flat at ~4.9 MB after 5,000,
    10,000, 20,000, and 40,000 distinct since-date queries (was
    10.5 → 16.5 → 28.4 → 52.3 MB pre-fix).
Impact
  • Who is affected: Long-running processes calling browserslist() with
    query values that vary across requests/items and are influenced by
    external input.
  • What an attacker achieves: DoS via eventual out-of-memory crash, given
    sustained traffic over time (not a single small payload).
  • Conditions required: No authentication; requires volume rather than a
    single request, hence Medium rather than High severity.
Verification Environment

browserslist @​ HEAD (== v4.28.6, current latest stable release) under local
Node.js v20.19.5, run with --expose-gc for accurate heap measurement.

Note

Found during a broader review of this codebase in the same research pass
that produced GHSA-rrmg-cfrq-23vv (parse.js algorithmic complexity),
GHSA-g6p8-hj8g-x889 (baseline regexp ReDoS), GHSA-73wf-gq98-2v4g
(normalizeStats crash/prototype write), and GHSA-h633-868p-5rfw
(SCOPED_CONFIG__PATTERN ReDoS) — all single-request DoS vectors. This one is
different in character (volumetric, not single-request) and is reported
separately/scored lower accordingly.

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


Configuration

📅 Schedule: (UTC)

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

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, 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.

Copilot AI lite review requested due to automatic review settings September 2, 2026 07:24
@github-actions github-actions Bot added the chore Hmm... label Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

COMPARE TO master

Total Size Diff 📉 -389 Bytes

Diff by File
Name Diff
pnpm-lock.yaml 📉 -389 Bytes

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The lockfile shows an inconsistent/invalid peer resolution where @withtyped/server (peer zod: ^3.19.1) is resolved with zod@4.4.3, indicating the lockfile likely needs regeneration or constraint adjustments before it’s safe to merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates the repository’s resolved npm dependency graph (via pnpm-lock.yaml) to address a Browserslist security advisory and refresh related transitive packages.

Changes:

  • Updated the resolved browserslist version used by packages/experience to 4.28.7.
  • Refreshed multiple transitive dependencies pulled in by the new resolution (e.g. caniuse-lite, electron-to-chromium, node-releases, update-browserslist-db, etc.).
  • Introduced additional resolution changes including browserslist@4.28.8 and zod@4.4.3 appearing in parts of the lockfile.
File summaries
File Description
pnpm-lock.yaml Updates the lockfile to resolve browserslist to 4.28.7 and refreshes associated transitive dependencies.
Review details

Copilot wasn't able to review any files in this pull request.

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

pnpm-lock.yaml:5074

  • This importer lists zod with specifier: 3.24.3 but resolves it to version: 4.4.3, which is inconsistent with the requested range and suggests the lockfile is out of sync with the workspace manifests/peer resolution.

Regenerate the lockfile so the resolved Zod version satisfies the specifier (likely 3.24.3 here).

  • Files reviewed: 0/1 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread pnpm-lock.yaml
Comment on lines 5066 to +5068
'@withtyped/server':
specifier: ^0.14.0
version: 0.14.0(zod@3.24.3)
version: 0.14.0(zod@4.4.3)
Comment thread pnpm-lock.yaml Outdated
Comment on lines +9453 to +9454
browserslist@4.28.8:
resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==}
Copilot AI review requested due to automatic review settings September 3, 2026 09:55
@renovate
renovate Bot force-pushed the renovate/npm-browserslist-vulnerability branch from 6be3a37 to 77590ae Compare September 3, 2026 09:55
@github-actions github-actions Bot added size/xs and removed size/xs labels Sep 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The lockfile resolves packages/schemaszod peer to an incompatible version and contains additional dependency changes beyond the stated browserslist-only scope.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Copilot wasn't able to review any files in this pull request.

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

pnpm-lock.yaml:5068

  • In packages/schemas, the lockfile resolves the zod peer to 4.4.3 even though the peer specifier is exactly 3.24.3, and it also picks the @withtyped/server peer variant for zod@4.4.3. This creates an unmet peer dependency and can lead to inconsistent type/runtime behavior; the lockfile should resolve this peer to 3.24.3 (or the peer range should be widened in package.json in a separate change).
  • Files reviewed: 0/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread pnpm-lock.yaml
Comment on lines 4745 to +4750
browserslist:
specifier: ^4.23.2
version: 4.23.2
version: 4.28.7
browserslist-to-esbuild:
specifier: ^2.1.1
version: 2.1.1(browserslist@4.23.2)
version: 2.1.1(browserslist@4.28.7)
Copilot AI review requested due to automatic review settings September 3, 2026 22:38
@renovate
renovate Bot force-pushed the renovate/npm-browserslist-vulnerability branch from 77590ae to 1fa5bfb Compare September 3, 2026 22:38
@github-actions github-actions Bot added size/xs and removed size/xs labels Sep 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The lockfile currently resolves packages/schemas to an invalid peer combination (@withtyped/server with zod@4.4.3 despite zod: ^3.19.1), which can break installs (especially with --frozen-lockfile).

Review details

Copilot wasn't able to review any files in this pull request.

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (3)

pnpm-lock.yaml:5068

  • In packages/schemas, @withtyped/server is locked as 0.14.0(zod@4.4.3), but @withtyped/server@0.14.0 declares a peer dependency of zod: ^3.19.1 (see lock entry), so this peer resolution is invalid and will likely produce install warnings/errors.
    pnpm-lock.yaml:5074
  • In packages/schemas, the lockfile records zod with specifier 3.24.3 but resolves it to 4.4.3, which does not match the workspace package.json dependencies and can break pnpm install --frozen-lockfile (and also conflicts with @withtyped/server's zod: ^3.19.1 peer).
    pnpm-lock.yaml:20911
  • The lockfile contains a snapshots entry for @withtyped/server@0.14.0(zod@4.4.3), but @withtyped/server@0.14.0 peers zod: ^3.19.1. This snapshot should be removed so the graph only uses the zod@3.24.3 variant.
  • Files reviewed: 0/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 8, 2026 00:01
@renovate
renovate Bot force-pushed the renovate/npm-browserslist-vulnerability branch from 1fa5bfb to 07adf7b Compare September 8, 2026 00:01
@github-actions github-actions Bot added size/xs and removed size/xs labels Sep 8, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The lockfile now resolves @logto/schemas’ zod peer to 4.4.3 despite a 3.24.3 specifier/peer constraint, which is likely unintended and can cause peer mismatch or build/test issues.

Review details

Copilot wasn't able to review any files in this pull request.

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

pnpm-lock.yaml:5074

  • In packages/schemas importer, the lockfile records zod with specifier 3.24.3 but resolves it to 4.4.3. This does not satisfy @logto/schemas' declared peerDependency on zod (packages/schemas/package.json:91-93) and likely indicates an unintended peer-resolution conflict (or lockfile drift) introduced alongside the browserslist update; please regenerate the lockfile after resolving the intended zod version/peer range (e.g., keep zod at 3.24.3 here, or widen the peer range if zod v4 is intended and compatible).
  • Files reviewed: 0/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 9, 2026 22:18
@renovate
renovate Bot force-pushed the renovate/npm-browserslist-vulnerability branch from 07adf7b to 01d1dcf Compare September 9, 2026 22:18
@github-actions github-actions Bot added size/xs and removed size/xs labels Sep 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The lockfile has an unresolved Zod peer-version mismatch.

Review details

Copilot wasn't able to review any files in this pull request.

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

pnpm-lock.yaml:5068

  • This importer belongs to packages/schemas, whose peer dependency is exactly zod: 3.24.3 (packages/schemas/package.json:91-93), but the lockfile now selects the zod 4 peer context for @withtyped/server and selects zod@4.4.3 below. That leaves the lockfile inconsistent with the unchanged manifest and can install an unmet major-version peer (or fail peer validation); keep this importer wired to the existing zod 3.24.3 snapshot.
  • Files reviewed: 0/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 10, 2026 18:15
@renovate
renovate Bot force-pushed the renovate/npm-browserslist-vulnerability branch from 01d1dcf to 160f6bc Compare September 10, 2026 18:15
@github-actions github-actions Bot added size/xs and removed size/xs labels Sep 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The lockfile update appears to unintentionally introduce a major Zod version change/mismatch (v3 → v4) in packages/schemas, which is out of scope for a Browserslist-only security bump and could be breaking.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Copilot wasn't able to review any files in this pull request.

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (2)

pnpm-lock.yaml:5068

  • The lockfile updates @withtyped/server in packages/schemas to resolve against zod@4.4.3, which is a major Zod upgrade and appears unrelated to the stated Browserslist security bump. This broadens the PR scope and risks breaking runtime/type compatibility; consider pinning/overriding to keep Zod v3 here, or explicitly upgrading Zod in the manifests as a separate, intentional change and regenerating the lockfile.
    pnpm-lock.yaml:20912
  • The lockfile now contains both @withtyped/server@0.14.0(zod@3.24.3) and @withtyped/server@0.14.0(zod@4.4.3), implying mixed Zod major versions in the dependency graph. This can lead to duplicated Zod installs and incompatible schema/type instances; it would be safer to keep a single Zod major version across the workspace and regenerate the lockfile.
  • Files reviewed: 0/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread pnpm-lock.yaml
Comment on lines 5072 to +5074
zod:
specifier: 3.24.3
version: 3.24.3
version: 4.4.3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

1 participant