OSV 1.4.0 · github-reviewed · 修改于 2026-09-02 00:41
发布时间
2026-09-02 00:41
GitHub 审查时间
2026-09-02 00:41
NVD 发布时间
2026-08-12 01:19
源文件
advisories/github-reviewed/2026/09/GHSA-73wf-gq98-2v4g/GHSA-73wf-gq98-2v4g.json
File: node.js
Function: normalizeStats() (line ~214), reached from getStat() (called
unconditionally on every browserslist() call) and loadStat()
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):
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
→ → ,
for any such key whose JSON value has exactly one sub-key, e.g.:
data[i].versions.lengthundefined.lengthTypeError{ "toString": { "onekey": 5 }, "chrome": { "100": 50 } }
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.
browserslist-stats.json file anywhere between the project root and
filesystem root, containing e.g.
{"toString": {"onekey": 5}, "chrome": {"100": 50}}.browserslist()
internally, for any query.TypeError on the very first call.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.
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).browserslist-stats.json + an unrelated browserslist('defaults') call)
now returns a normal result instead of crashing.opts.stats.opts.stats.browserslist @ HEAD (== v4.28.6, current latest stable release) under local Node.js v20.19.5. Pure JS library — executed directly, no server needed.
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.