Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
HIGH severity

CVE-2026-73088 — browserslist

HIGHFix: browserslist/browserslist@f9914ad

CVE-2026-73088 is a high-severity (CVSS 7.5) CWE-248 vulnerability in browserslist. A fix is available for browserslist — see the affected versions and patch details below.

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

Also known asGHSA-73wf-gq98-2v4g
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 7, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-73088.

EPSS Exploitation Probability

via FIRST.org ↗
0.7%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs50th percentile — riskier than 50% of all scored CVEsHighest risk
0.00%0.39%0.77%1.16%0.4%0.7%0.7%Sep 26Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-73088 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 383,485 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

4Kother npm packages depend on this — each one inherits the vulnerability until it's patched upstream
browserslistnpm
241.4Mdownloads / week

Description

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.length → undefined.length → uncaught 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.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npmbrowserslistall versions4.28.7npm install browserslist@4.28.7

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for browserslist, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update browserslist to 4.28.7 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-73088 is resolved across your whole dependency graph.

  3. Workarounds

    Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatImportant

This is an Important vulnerability. The browserslist package, a front-end development tool, is vulnerable to prototype pollution when processing untrusted statistics data. This flaw can lead to a denial of service, as malicious input can crash applications that use the affected library. The impact is considered…

Workaround published by Red Hat
To reduce exposure, ensure that the `browserslist` tool processes only trusted `browserslist-stats.json`, `opts.stats`, and CLI `--stats` data. Avoid using the tool with untrusted input sources in development or build environments. If `browserslist` is integrated into automated pipelines, validate all input data originates from trusted sources.
Source: Red Hat security advisory for CVE-2026-73088 (CC BY 4.0)
ProductFixed inAdvisory
Red Hat Ansible Automation Platform 2.1ansible-automation-platform/automation-portal:1787047114RHSA-2026:56338
Red Hat Ansible Automation Platform 2.2ansible-automation-platform/automation-portal:1787047188RHSA-2026:56357
Red Hat Discovery 2discovery/discovery-ui-rhel9:1786634825RHSA-2026:54760
Red Hat Edge Manager 1.1rhem/flightctl-ui-ocp-rhel10:1789486226RHSA-2026:68044
Red Hat Edge Manager 1.1rhem/flightctl-ui-ocp-rhel9:1789486446RHSA-2026:68253
Red Hat Hardened Imagesgrafana13-1-main-13.1.3-0.1.1.hum1RHSA-2026:54517
Red Hat Hardened Imagesgrafana12-4-main-12.4.8-0.1.1.hum1RHSA-2026:54518
Red Hat Migration Toolkit 1.8rhmtc/openshift-migration-ui-rhel8:1789546373RHSA-2026:68681

Frequently Asked Questions

## Vulnerability Details **File**: `node.js` **Function**: `normalizeStats()` (line ~214), reached from `getStat()` (called **unconditionally** on every `browserslist()` call) and `loadStat()` ### Root Cause ```js 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].ve
O3 Security · Impact-Aware SCA

Is CVE-2026-73088 in your dependencies?

Find it across npm, including transitive dependencies.

CVE-2026-73088: browserslist DoS — Fixed in 4.28.7