Dashboard › spotlight › Session 18wakMf8Hwza
18wakMf8HwzafbJhpPerform a rigorous, skeptical adversarial CORRECTNESS review of a pnpm-overrides dependency-fix PR. Focus STRICTLY on REAL bugs and dependency-resolution correctness. IGNORE style.
Repo: /home/byk/Code/getsentry/spotlight
The PR branch is fetched locally as ref pr-1337 (base main). Diff it with:
git -C /home/byk/Code/getsentry/spotlight diff main...pr-1337 -- package.json
and inspect pnpm-lock.yaml resolution as needed.
Background: This repo just bumped electron-builder 24 -> 26.15.7 (commit 43797cca on main). That bump reportedly broke the macOS build (pnpm --filter=@spotlightjs/spotlight build:mac) in two ways. PR #1337 fixes the root-level pnpm.overrides in package.json to repair it.
The two claims to verify:
CLAIM 1 — brace-expansion:
minimatch@10 as a new transitive dep.minimatch@10 requires brace-expansion@^5, which exports a NAMED expand function."brace-expansion": ">=2.1.0 <3" forced brace-expansion down to 2.x everywhere, and 2.x uses a DEFAULT export, so minimatch@10's const { expand } = require('brace-expansion') got undefined → TypeError: (0, brace_expansion_1.expand) is not a function.brace-expansion@<2.0.0: >=1.1.12 <2, brace-expansion@>=2.0.0 <3.0.0: >=2.1.2 <3, brace-expansion@>=3.0.0 <4.0.0: >=3.0.1 <4, brace-expansion@>=4.0.0: >=4.0.1.CLAIM 2 — @xmldom/xmldom:
"@xmldom/xmldom": ">=0.8.13" let it float to 0.9.10.@xmldom/xmldom@0.9.x's parseFromString requires a mimeType arg that plist@3.1.0 (used by app-builder-lib@26's plist parsing) does not pass → DOMParser.parseFromString: the provided mimeType "undefined" is not valid.">=0.8.13 <0.9". ...I'll perform a rigorous adversarial correctness review. Let me start by setting up the investigation.
[
{
"content": "Diff package.json main...pr-1337 to confirm exactly what changed",
"status": "in_progress",
"priority": "high"
},
{
"content": "Verify CLAIM 1: minimatch@10 requires brace-expansion@^5, pulled by eb26",
"status": "pending",
"priority": "high"
},
{
"content": "Verify brace-expansion v5 named export vs v2 default export story",
"status": "pending",
"priority": "high"
},
{
"content": "Confirm lockfile has multiple brace-expansion majors coexisting on pr-1337",
"status": "pending",
"priority": "high"
},
{
"content": "Verify per-major override ranges are correct/non-contradictory/published",
"status": "pending",
"priority": "high"
},
{
"content": "Verify Dependabot #294 GHSA-3jxr-9vmj-r5cp: 2.1.0 vuln, 2.1.2 patch, resolved",
"status": "pending",
"priority": "high"
},
{
"content": "Verify CLAIM 2: @xmldom/xmldom resolves <0.9, still non-vuln, no >=0.9 need",
"status": ...
diff --git a/package.json b/package.json
index ea82b8d1..c8c81801 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,10 @@
"next": "^15.4.8",
"cookie": ">=0.7.0",
"esbuild": ">=0.28.1",
- "brace-expansion": ">=2.1.0 <3",
+ "brace-expansion@<2.0.0": ">=1.1.12 <2",
+ "brace-expansion@>=2.0.0 <3.0.0": ">=2.1.2 <3",
+ "brace-expansion@>=3.0.0 <4.0.0": ">=3.0.1 <4",
+ "brace-expansion@>=4.0.0": ">=4.0.1",
"tmp": ">=0.2.6",
"js-yaml": ">=4.2.0 <5",
"body-parser": ">=2.2.1",
@@ -53,7 +56,7 @@
"fast-uri": ">=3.1.2",
"ip-address": ">=10.1.1",
"lodash": ">=4.18.0",
- "@xmldom/xmldom": ">=0.8.13",
+ "@xmldom/xmldom": ">=0.8.13 <0.9",
"@opentelemetry/core": ">=2.8.0",
"@babel/core": ">=7.29.6 <8",
"postcss": ">=8.5.10",
The diff confirms only the two overrides changed. Now let me investigate the claims in parallel.
=== minimatch@10 deps ===
minimatch@10.0.0 dependencies = { 'brace-expansion': '^4.0.0' }
minimatch@10.0.0 version = '10.0.0'
minimatch@10.0.1 dependencies = { 'brace-expansion': '^2.0.1' }
minimatch@10.0.1 version = '10.0.1'
minimatch@10.0.2 dependencies = { 'brace-expansion': '^4.0.1' }
minimatch@10.0.2 version = '10.0.2'
minimatch@10.0.3 dependencies = { '@isaacs/brace-expansion': '^5.0.0' }
minimatch@10.0.3 version = '10.0.3'
minimatch@10.1.0 dependencies = { '@isaacs/brace-expansion': '^5.0.0' }
minimatch@10.1.0 version = '10.1.0'
minimatch@10.1.1 dependencies = { '@isaacs/brace-expansion': '^5.0.0' }
minimatch@10.1.1 version = '10.1.1'
minimatch@10.1.2 dependencies = { '@isaacs/brace-expansion': '^5.0.1' }
minimatch@10.1.2 version = '10.1.2'
minimatch@10.1.3 dependencies = { 'brace-expansion': '^5.0.2' }
minimatch@10.1.3 version = '10.1.3'
minimatch@10.2.0 dependencies = { 'brace-expansion': '^5.0.2' }
minimatch@10.2.0 version = '10.2.0'
minimatch@10.2.1 dependencies = { ...
=== brace-expansion@ entries in pr-1337 lockfile === 75: brace-expansion@<2.0.0: '>=1.1.12 <2' 76: brace-expansion@>=2.0.0 <3.0.0: '>=2.1.2 <3' 77: brace-expansion@>=3.0.0 <4.0.0: '>=3.0.1 <4' 78: brace-expansion@>=4.0.0: '>=4.0.1' 3272: brace-expansion@1.1.16: 3275: brace-expansion@2.1.2: 3278: brace-expansion@5.0.7: 10682: brace-expansion@1.1.16: 10687: brace-expansion@2.1.2: 10691: brace-expansion@5.0.7: === minimatch@ entries === 63: minimatch@<4.0.0: ~3.1.4 64: minimatch@>=5.0.0 <6.0.0: ~5.1.8 65: minimatch@>=8.0.0 <9.0.0: ~8.0.6 66: minimatch@>=9.0.0 <10.0.0: ~9.0.7 5394: minimatch@10.2.5: 5398: minimatch@3.1.5: 5401: minimatch@5.1.9: 5405: minimatch@8.0.7: 5409: minimatch@9.0.9: 13324: minimatch@10.2.5: 13328: minimatch@3.1.5: 13332: minimatch@5.1.9: 13336: minimatch@8.0.7: 13340: minimatch@9.0.9:
Important nuance discovered. The claim says "minimatch@10 requires brace-expansion@^5" but this is only true for minimatch@10.1.3+. The lockfile resolves minimatch@10.2.5 which requires brace-expansion@^5.0.5. Let me verify the resolution details and the export shapes.
=== minimatch@10.2.5 block (root ctx) ===
min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
minimatch@10.2.5:
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
engines: {node: 18 || 20 || >=22}
minimatch@3.1.5:
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
minimatch@5.1.9:
resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
engines: {node: '>=10'}
minimatch@8.0.7:
resolution: {integrity: sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==}
engines: {node: '>=16 || 14 >=14.17'}
minimatch@9.0.9:
resolution: {integrity: ...
=== who depends on minimatch@10.2.5 ? (importer refs) ===
5394: minimatch@10.2.5:
10389: minimatch: 10.2.5
11960: minimatch: 10.2.5
13324: minimatch@10.2.5:
--- search dependents ---
5392- engines: {node: '>=4'}
5393-
5394: minimatch@10.2.5:
--
10387- json5: 2.2.3
10388- lazy-val: 1.0.5
10389: minimatch: 10.2.5
--
11958- glob@13.0.6:
11959- dependencies:
11960: minimatch: 10.2.5
--
13322- min-indent@1.0.1: {}
13323-
13324: minimatch@10.2.5:
Let me trace what pulls minimatch@10.2.5 (line 10389 context) and verify brace-expansion@5.0.7's dependency requirement, plus the minimatch@10.2.5 brace-expansion binding in snapshot.
=== context around 10389 (who has minimatch: 10.2.5) ===
'@electron/notarize': 2.5.0
'@electron/osx-sign': 1.3.3
'@electron/rebuild': 4.2.0
'@electron/universal': 2.0.3
'@malept/flatpak-bundler': 0.4.0
'@noble/hashes': 2.2.0
'@peculiar/webcrypto': 1.7.1
'@types/fs-extra': 9.0.13
ajv: 8.18.0
asn1js: 3.0.10
async-exit-hook: 2.0.1
builder-util: 26.15.3
builder-util-runtime: 9.7.0
chromium-pickle-js: 0.2.0
ci-info: 4.3.1
debug: 4.4.3
dmg-builder: 26.15.7(electron-builder-squirrel-windows@24.13.3)
dotenv: 16.5.0
dotenv-expand: 11.0.7
ejs: 3.1.10
electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.15.7)
electron-publish: 26.15.3
fs-extra: 10.1.0
hosted-git-info: 4.1.0
isbinaryfile: 5.0.2
jiti: 2.4.2
js-yaml: 4.2.0
json5: 2.2.3
lazy-val: 1.0.5
minimatch: 10.2.5
pkijs: 3.4.0
plist: 3.1.0
...
=== minimatch@10.2.5 snapshot deps (13324) ===
minimatch@10.2.5:
dependencies:
brace-expansion: 5.0.7
minimatch@3.1.5:
dependencies:
brace-expansion: 1.1.16
minimatch@5.1.9:
dependencies:
brace-expansion: 2.1.2
minimatch@8.0.7:
dependencies:
brace-expansion: 2.1.2
minimatch@9.0.9:
dependencies:
brace-expansion: 2.1.2
minimist@1.2.8: {}
Confirmed. The block at 10360 is app-builder-lib@26.15.7 (has @electron/notarize 2.5.0, osx-sign 1.3.3, builder-util 26.15.3, plist 3.1.0) — it depends on minimatch: 10.2.5, which depends on brace-expansion: 5.0.7. And multiple coexisting majors are confirmed:
minimatch@3.1.5 → brace-expansion@1.1.16minimatch@5.1.9, 8.0.7, 9.0.9 → brace-expansion@2.1.2minimatch@10.2.5 → brace-expansion@5.0.7Now let me verify the export shape story (v5 named expand vs v2 default), brace-expansion@5.0.7 deps, and the security advisory.
=== be@5.0.7 pkg meta ===
dependencies = { 'balanced-match': '^4.0.2' }
type = 'module'
main = './dist/commonjs/index.js'
exports = {
'.': {
import: { types: './dist/esm/index.d.ts', default: './dist/esm/index.js' },
require: {
types: './dist/commonjs/index.d.ts',
default: './dist/commonjs/index.js'
}
},
'./package.json': './package.json'
}
=== be@2.1.2 pkg meta ===
dependencies = { 'balanced-match': '^1.0.0' }
main = 'index.js'
=== be@1.1.16 ===
dependencies = { 'concat-map': '0.0.1', 'balanced-match': '^1.0.0' }
main = 'index.js'
=== GHSA-3jxr-9vmj-r5cp advisory ===
--- fetch advisory ---
summary: brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups
severity: high
pkg: brace-expansion range: >= 3.0.0, < 5.0.7 patched: 5.0.7
pkg: brace-expansion range: < 1.1.16 patched: 1.1.16
pkg: brace-expansion range: >= 2.0.0, < 2.1.2 patched: 2.1.2
Critical findings on the advisory. Let me verify the export shapes concretely (the CJS require shape for v5 vs v2) and check the full advisory version ranges against the overrides. First, the advisory:
GHSA-3jxr-9vmj-r5cp patched versions:
< 1.1.16 → patched 1.1.16>= 2.0.0, < 2.1.2 → patched 2.1.2>= 3.0.0, < 5.0.7 → patched 5.0.7Now let me check the override ranges against this carefully, and verify the actual CJS export shape.
=== v5 CJS main head ===
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EXPANSION_MAX = void 0;
exports.expand = expand;
const balanced_match_1 = require("balanced-match");
const escSlash = '\0SLASH' + Math.random() + '\0';
const escOpen = '\0OPEN' + Math.random() + '\0';
const escClose = '\0CLOSE' + Math.random() + '\0';
const escComma = '\0COMMA' + Math.random() + '\0';
const escPeriod = '\0PERIOD' + Math.random() + '\0';
const escSlashPattern = new RegExp(escSlash, 'g');
const escOpenPattern = new RegExp(escOpen, 'g');
const escClosePattern = new RegExp(escClose, 'g');
const escCommaPattern = new RegExp(escComma, 'g');
const escPeriodPattern = new RegExp(escPeriod, 'g');
const slashPattern = /\\\\/g;
const openPattern = /\\{/g;
const closePattern = /\\}/g;
const commaPattern = /\\,/g;
const periodPattern = /\\\./g;
=== v5 exports keyword check ===
3:exports.EXPANSION_MAX = void 0;
4:exports.expand = expand;
21:exports.EXPANSION_MAX = ...
Confirmed: v5 does exports.expand = expand (named export), so const { expand } = require('brace-expansion') works. Let me check v2's export shape to confirm the default-export story.
=== v2.1.2 main head + exports ===
3:module.exports = expandTop;
--- last lines ---
}
return expansions;
}
}
=== minimatch@10.2.5 usage of brace-expansion ===
package/dist/commonjs/index.js:4:const brace_expansion_1 = require("brace-expansion");
package/dist/commonjs/index.js:146:// Invalid sets are not expanded.
package/dist/commonjs/index.js:154: // shortcut. no need to expand.
package/dist/commonjs/index.js:157: return (0, brace_expansion_1.expand)(pattern, { max: options.braceExpandMax });
package/dist/commonjs/index.js:161:// parse a component of the expanded set.
package/dist/commonjs/index.js:267: // step 2: expand braces
The mechanism is fully confirmed:
minimatch@10.2.5 does const brace_expansion_1 = require("brace-expansion") then calls (0, brace_expansion_1.expand)(...) — it needs the named expand.brace-expansion@5.x: exports.expand = expand → named export present → works.brace-expansion@2.1.2: module.exports = expandTop → NO named .expand → brace_expansion_1.expand is undefined → TypeError: (0, brace_expansion_1.expand) is not a function.So the old blanket >=2.1.0 <3 override forcing 2.x onto minimatch@10 exactly reproduces the reported bug. CLAIM 1's root cause is correct.
Now let me examine the advisory-vs-override analysis carefully — there's a subtle point about the >=3.0.0 <4.0.0 range.
[
{
"content": "Diff package.json main...pr-1337 to confirm exactly what changed",
"status": "completed",
"priority": "high"
},
{
"content": "Verify CLAIM 1: minimatch@10 requires brace-expansion@^5, pulled by eb26",
"status": "completed",
"priority": "high"
},
{
"content": "Verify brace-expansion v5 named export vs v2 default export story",
"status": "completed",
"priority": "high"
},
{
"content": "Confirm lockfile has multiple brace-expansion majors coexisting on pr-1337",
"status": "completed",
"priority": "high"
},
{
"content": "Verify per-major override ranges are correct/non-contradictory/published",
"status": "in_progress",
"priority": "high"
},
{
"content": "Verify Dependabot #294 GHSA-3jxr-9vmj-r5cp: 2.1.0 vuln, 2.1.2 patch, resolved",
"status": "in_progress",
"priority": "high"
},
{
"content": "Verify CLAIM 2: @xmldom/xmldom resolves <0.9, still non-vuln, no >=0.9 need",
...
Now the critical override-vs-advisory analysis. Let me lay out the published versions and advisory ranges:
Published brace-expansion versions: 1.1.x (max 1.1.16), 2.0.0–2.1.2, 3.0.0–3.0.2, 4.0.0–4.0.1, 5.0.2–5.0.7
Advisory GHSA-3jxr-9vmj-r5cp patched ranges:
< 1.1.16 vulnerable → patched at 1.1.16>= 2.0.0, < 2.1.2 vulnerable → patched at 2.1.2>= 3.0.0, < 5.0.7 vulnerable → patched at 5.0.7Note the advisory groups 3.x, 4.x, and 5.x (<5.0.7) all as vulnerable, patched only at 5.0.7. Now checking each override:
=== published 3.x and 4.x === brace-expansion@3.0.0 '3.0.0' brace-expansion@3.0.1 '3.0.1' brace-expansion@3.0.2 '3.0.2' ---4.x--- brace-expansion@4.0.0 '4.0.0' brace-expansion@4.0.1 '4.0.1' === does override >=3.0.0 <4.0.0 : >=3.0.1 <4 resolve to a NON-vulnerable version? advisory says <5.0.7 vuln for the >=3.0.0 branch === highest 3.x = 3.0.2, which is < 5.0.7 => STILL VULNERABLE per advisory
Key theoretical finding: the override branches brace-expansion@>=3.0.0 <4.0.0: >=3.0.1 <4 and brace-expansion@>=4.0.0: >=4.0.1 would pin to 3.0.2 / 4.0.1, both of which are < 5.0.7 and thus still vulnerable to GHSA-3jxr-9vmj-r5cp per the advisory. However — those branches only matter if something actually requests a 3.x or 4.x brace-expansion. Let me check whether anything in the tree does, since the lockfile only shows 1.1.16, 2.1.2, 5.0.7 actually resolved.
The advisory ranges (>=3.0.0 <5.0.7 all vulnerable, patched only at 5.0.7) mean 3.x and 4.x lines are dead — the maintainer never patched them; the fix jumped to 5.0.7. So pinning a 3.x/4.x requester to 3.0.2/4.0.1 would be a latent security hole IF such a requester existed. Let me verify none exists in this tree.
=== Any dep requiring brace-expansion 3.x or 4.x? Search all specifiers ===
13326: brace-expansion: 5.0.7
13330: brace-expansion: 1.1.16
13334: brace-expansion: 2.1.2
13338: brace-expansion: 2.1.2
13342: brace-expansion: 2.1.2
=== unique resolved brace-expansion versions in snapshots ===
1 brace-expansion: 1.1.16
3 brace-expansion: 2.1.2
1 brace-expansion: 5.0.7
Confirmed: only 1.1.16, 2.1.2, and 5.0.7 resolve in the actual tree. Nothing requests 3.x or 4.x, so the 3.x/4.x override branches are dormant. This means:
Now let me verify the >=1.1.12 <2 branch. The advisory patches 1.x at 1.1.16. The override allows >=1.1.12, and 1.1.12–1.1.15 are still vulnerable (<1.1.16). But the lockfile resolved 1.1.16 (max of the <2 range). Let me confirm why it landed on 1.1.16 and not something lower.
=== who needs brace-expansion 1.x (minimatch@3.1.5 needs ^1.1.7) ===
{ 'brace-expansion': '^1.1.7' }
=== 1.x range >=1.1.12 <2 with maximal-satisfying → 1.1.16 (correct, patched). But floor 1.1.12 is technically vulnerable if pinned low ===
=== 2.x override >=2.1.2 <3 resolves 2.1.2 = patched. GOOD ===
=== Now @xmldom/xmldom on pr-1337 ===
87: '@xmldom/xmldom': '>=0.8.13 <0.9'
2976: '@xmldom/xmldom@0.8.13':
10227: '@xmldom/xmldom@0.8.13': {}
13712: '@xmldom/xmldom': 0.8.13
13718: '@xmldom/xmldom': 0.8.13
CLAIM 2 partly confirmed: on pr-1337 @xmldom/xmldom now resolves to 0.8.13 (was floating to 0.9.10 before). Let me verify who depends on it, that 0.8.13 satisfies the original security fix, whether anything needs >=0.9, and check what version main had.
=== dependents of @xmldom/xmldom (13712, 13718 context) ===
plimit-lit@1.6.1:
dependencies:
queue-lit: 1.5.2
plist@3.1.0:
dependencies:
'@xmldom/xmldom': 0.8.13
base64-js: 1.5.1
xmlbuilder: 15.1.1
plist@3.1.1:
dependencies:
'@xmldom/xmldom': 0.8.13
base64-js: 1.5.1
xmlbuilder: 15.1.1
=== what did main resolve @xmldom/xmldom to? ===
2970: '@xmldom/xmldom@0.9.10':
10207: '@xmldom/xmldom@0.9.10': {}
=== main override line ===
84: '@xmldom/xmldom': '>=0.8.13'
13679: '@xmldom/xmldom': 0.9.10
13685: '@xmldom/xmldom': 0.9.10
=== xmldom versions & the >=0.9 mimeType change ===
['0.8.0', '0.8.1', '0.8.2', '0.8.3', '0.8.4', '0.8.5', '0.8.6', '0.8.7', '0.8.8', '0.8.9', '0.8.10', '0.8.11', '0.8.12', '0.8.13', '0.9.0-beta.1', '0.9.0-beta.2', '0.9.0-beta.3', '0.9.0-beta.4', '0.9.0-beta.5', '0.9.0-beta.6', '0.9.0-beta.7', '0.9.0-beta.8', '0.9.0-beta.9', '0.9.0-beta.10', '0.9.0-beta.11', '0.9.0', '0.9.1', '0.9.2', '0.9.3', '0.9.4', '0.9.5', '0.9.6', '0.9.7', '0.9.8', '0.9.9', '0.9.10']
=== does plist@3.1.0 require a specific xmldom range? ===
{
'base64-js': '^1.5.1',
xmlbuilder: '^15.1.1',
'@xmldom/xmldom': '^0.8.8'
}
=== xmldom security advisories affecting 0.8.x ===
0.8.13
Strong confirmation of CLAIM 2:
plist@3.1.0 declares @xmldom/xmldom: ^0.8.8 — so 0.9.10 was NEVER a natural resolution; the blanket override >=0.8.13 FORCED plist onto 0.9.10 (which the ^0.8.8 spec would never pick on its own). On main, both plist@3.1.0 and plist@3.1.1 got 0.9.10 forced. On pr-1337 they correctly get 0.8.13.^0.8.8. Capping at <0.9 creates no unmet requirement.Let me confirm the xmldom security advisory patched version and that 0.9.x is genuinely incompatible with plist's no-mimeType call, then run the frozen-lockfile install.
=== xmldom advisory (the reason for >=0.8.13 override originally) === GHSA-2v35-w6hq-6mfw high | xmldom: Uncontrolled recursion in XML serialization leads to | patched: ['0.8.13', '0.9.10'] GHSA-f6ww-3ggp-fr8h high | xmldom has XML injection through unvalidated DocumentType se | patched: ['0.8.13', '0.9.10'] GHSA-x6wf-f3px-wcqx high | xmldom has XML node injection through unvalidated processing | patched: ['0.8.13', '0.9.10'] GHSA-j759-j44w-7fr8 high | xmldom has XML node injection through unvalidated comment se | patched: ['0.8.13', '0.9.10'] GHSA-wh4c-j3r5-mjhp high | xmldom: XML injection via unsafe CDATA serialization allows | patched: ['0.8.12', '0.9.9'] GHSA-crh6-fp67-6883 critical | xmldom allows multiple root nodes in a DOM | patched: ['0.7.7', '0.8.4', '0.9.0-beta.4'] GHSA-9pgh-qqpf-7wqj critical | Withdrawn: Improperly Controlled Modification of Object Prot | patched: ['0.9.0-beta.2', '0.8.3', '0.7.6'] GHSA-5fg8-2547-mr8q medium | Misinterpretation of malicious XML input | ...
=== 0.9.x parseFromString mimeType requirement === === 0.8.x parseFromString signature ===
The advisory confirms: all the high-severity xmldom advisories are patched at 0.8.13 (in the 0.8.x line) AND 0.9.10. So >=0.8.13 <0.9 resolving to 0.8.13 fully satisfies every security fix — no security regression from the cap. Let me find the actual parseFromString source location to confirm the mimeType requirement in 0.9.x.
=== xmldom 0.9.10 files === ls: cannot access 'xmldom-0.9.10/lib/': No such file or directory === grep mimeType across 0.9.10 lib === grep: xmldom-0.9.10/lib/: No such file or directory === the mimeType validation === grep: xmldom-0.9.10/lib/*.js: No such file or directory
./brace-expansion-2.1.2.tgz ./xmldom-xmldom-0.9.10.tgz ./brace-expansion-5.0.7.tgz ./xmldom-xmldom-0.8.13.tgz ./brace-expansion-1.1.16.tgz === extracted dirs === xmldom-xmldom-0.8.13 xmldom-xmldom-0.8.13.tgz xmldom-xmldom-0.9.10 xmldom-xmldom-0.9.10.tgz === find package.json main === ./xmldom-xmldom-0.8.13/package.json ./xmldom-xmldom-0.9.10/package.json === retry extract === ls: cannot access '@xmldom*': No such file or directory brace-expansion-1.1.16.tgz brace-expansion-2.1.2.tgz brace-expansion-5.0.7.tgz xmldom-xmldom-0.8.13.tgz xmldom-xmldom-0.9.10.tgz
=== 0.9.10 layout ===
CHANGELOG.md
index.d.ts
lib
LICENSE
package.json
readme.md
SECURITY.md
=== 0.9.10 mimeType validity check ===
xmldom-xmldom-0.9.10/lib/conventions.js:269: * Only returns true if `value` matches MIME_TYPE.HTML, which indicates an HTML document.
xmldom-xmldom-0.9.10/lib/conventions.js:271: * @param {string} mimeType
xmldom-xmldom-0.9.10/lib/conventions.js:272: * @returns {mimeType is 'text/html'}
xmldom-xmldom-0.9.10/lib/conventions.js:278:function isHTMLMimeType(mimeType) {
xmldom-xmldom-0.9.10/lib/conventions.js:279: return mimeType === MIME_TYPE.HTML;
xmldom-xmldom-0.9.10/lib/conventions.js:285: * @param {string} mimeType
xmldom-xmldom-0.9.10/lib/conventions.js:291:function hasDefaultHTMLNamespace(mimeType) {
xmldom-xmldom-0.9.10/lib/conventions.js:292: return isHTMLMimeType(mimeType) || mimeType === MIME_TYPE.XML_XHTML_APPLICATION;
xmldom-xmldom-0.9.10/lib/conventions.js:304:var MIME_TYPE = freeze({
xmldom-xmldom-0.9.10/lib/conventions.js:364:var _MIME_TYPES = ...
=== 0.9.10 parseFromString mimeType handling ===
71: * and only `fatalError`s are thrown as a `ParseError`,
73: * If the provided method throws, a `ParserError` is thrown,
89: * When the `mimeType` for HTML, XHTML or SVG are passed to `parseFromString`,
141: * and only `fatalError`s are thrown as a `ParseError`,
143: * If the provided method throws, a `ParserError` is thrown,
155: throw new TypeError('errorHandler object is no longer supported, switch to onError!');
181: * When the `mimeType` for HTML, XHTML or SVG are passed to `parseFromString`,
200: * - Any `fatalError` throws a `ParseError` which prevents further processing.
201: * - Any error thrown by `onError` is converted to a `ParseError` which prevents further
213: * @throws {ParseError}
214: * for any `fatalError` or anything that is thrown by `onError`
215: * @throws {TypeError}
217: * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
220:DOMParser.prototype.parseFromString = function ...
Definitive proof of CLAIM 2. In @xmldom/xmldom@0.9.10, dom-parser.js:220-222:
DOMParser.prototype.parseFromString = function (source, mimeType) {
...
throw new TypeError('DOMParser.parseFromString: the provided mimeType "' + mimeType + '" is not valid.');
This matches the reported error string exactly. In 0.8.13, parseFromString = function(source,mimeType) does not enforce a valid mimeType. So plist@3.1.0 calling parseFromString(xml) with no mimeType breaks under 0.9.x but works under 0.8.x. The <0.9 cap is necessary and correct.
Now the final critical check: frozen-lockfile integrity in a throwaway copy.
=== files needed for install === package.json packages/spotlight/package.json packages/website/package.json pnpm-lock.yaml pnpm-workspace.yaml
=== worktree checkout of pr-1337 into throwaway === Preparing worktree (detached HEAD a18d0c92) HEAD is now at a18d0c92 fix(deps): repair electron-builder 26 dependency resolution done
The PR head commit message is "fix(deps): repair electron-builder 26 dependency resolution". Now let me run the frozen lockfile-only install in the worktree.
=== pnpm version === 9.15.9 === frozen lockfile-only install === Scope: all 3 workspace projects Done in 832ms using pnpm v9.15.9 EXIT: 0
Frozen lockfile validates cleanly (exit 0, no resolution mismatch). Let me verify no lockfile drift would occur (a real full resolution check) and check peer warnings by doing a lockfile-only resolution without frozen, comparing the lockfile hash.
=== sha before === 5c67682ece3431b88d581ff9a135ed66462928287f53fea8aa1ed5eaa5d9d91e pnpm-lock.yaml === re-resolve lockfile-only (non-frozen) to detect drift === Scope: all 3 workspace projects Done in 1s using pnpm v9.15.9 === sha after === 5c67682ece3431b88d581ff9a135ed66462928287f53fea8aa1ed5eaa5d9d91e pnpm-lock.yaml === git diff on lockfile (drift?) ===
No drift — the lockfile is fully consistent with package.json (sha256 identical after re-resolution, no git diff). The overrides are stable and complete.
Let me do a couple final skeptical checks: (1) confirm the full diff is ONLY package.json + pnpm-lock.yaml (no silent changes elsewhere), (2) verify the brace-expansion 5.0.7 resolution meets minimatch@10.2.5's ^5.0.5 requirement, and (3) sanity check whether there's any OTHER new minimatch@10 / brace-expansion@5 consumer I might've missed that could hit a 3.x/4.x branch.
=== full file-level diff main...pr-1337 ===
package.json | 7 +++--
pnpm-lock.yaml | 89 +++++++++++++++++++++++++++++++---------------------------
2 files changed, 52 insertions(+), 44 deletions(-)
=== 5.0.7 satisfies ^5.0.5? yes. Verify all brace-expansion specifiers requested anywhere ===
1 brace-expansion: 1.1.16
3 brace-expansion: 2.1.2
1 brace-expansion: 5.0.7
Diff is exactly 2 files: package.json (7 lines) + pnpm-lock.yaml (89 lines). Nothing else silently altered. .lore.md is NOT in this diff (good — not touched).
Let me do one last verification: confirm the @electron/notarize@2.5.0 / osx-sign@1.3.3 chain (from the eb26 upgrade) is intact on pr-1337, and check nothing regressed in the mac-relevant chain. Also verify the lockfile overrides: section matches package.json exactly.
=== lockfile overrides section (top) === 52:overrides: 75: brace-expansion@<2.0.0: '>=1.1.12 <2' 76: brace-expansion@>=2.0.0 <3.0.0: '>=2.1.2 <3' 77: brace-expansion@>=3.0.0 <4.0.0: '>=3.0.1 <4' 78: brace-expansion@>=4.0.0: '>=4.0.1' 87: '@xmldom/xmldom': '>=0.8.13 <0.9' === the diff of pnpm-lock overrides block main vs pr === 75c75,78 < brace-expansion: '>=2.1.0 <3' --- > brace-expansion@<2.0.0: '>=1.1.12 <2' > brace-expansion@>=2.0.0 <3.0.0: '>=2.1.2 <3' > brace-expansion@>=3.0.0 <4.0.0: '>=3.0.1 <4' > brace-expansion@>=4.0.0: '>=4.0.1' 84c87 < '@xmldom/xmldom': '>=0.8.13' --- > '@xmldom/xmldom': '>=0.8.13 <0.9' 93,95d95 < importers: < < .:
The lockfile overrides: section exactly mirrors package.json. Everything is consistent. Let me clean up the scratch and finalize.
=== verify real tree untouched === M .lore.md ?? .opencode/ ?? packages/website/content.config.ts === worktrees === /home/byk/Code/getsentry/spotlight 041d871e [main] /home/byk/.local/share/opencode/worktree/125e3ebeb747c35789e81a4b036ef58ca480e265/tidy-orchid 8e8fd0f6 [docs/turbo-json-env-config]
Scratch cleaned up. Real tree untouched (only pre-existing .lore.md modification and untracked .opencode/ + content.config.ts, which are the known intentional exclusions — I did not touch them).
[
{
"content": "Diff package.json main...pr-1337 to confirm exactly what changed",
"status": "completed",
"priority": "high"
},
{
"content": "Verify CLAIM 1: minimatch@10 requires brace-expansion@^5, pulled by eb26",
"status": "completed",
"priority": "high"
},
{
"content": "Verify brace-expansion v5 named export vs v2 default export story",
"status": "completed",
"priority": "high"
},
{
"content": "Confirm lockfile has multiple brace-expansion majors coexisting on pr-1337",
"status": "completed",
"priority": "high"
},
{
"content": "Verify per-major override ranges are correct/non-contradictory/published",
"status": "completed",
"priority": "high"
},
{
"content": "Verify Dependabot #294 GHSA-3jxr-9vmj-r5cp: 2.1.0 vuln, 2.1.2 patch, resolved",
"status": "completed",
"priority": "high"
},
{
"content": "Verify CLAIM 2: @xmldom/xmldom resolves <0.9, still non-vuln, no >=0.9 need",
...
Repo: /home/byk/Code/getsentry/spotlight · branch pr-1337 (head a18d0c92 "fix(deps): repair electron-builder 26 dependency resolution") · base main (041d871e)
Only two files: package.json (7 lines) and pnpm-lock.yaml (89 lines). No other tracked file touched; .lore.md NOT in the diff. The lockfile overrides: block mirrors package.json exactly (lock lines 75-78, 87).
Root cause reproduced end-to-end:
app-builder-lib@26.15.7 (pnpm-lock:10360-10389, holds @electron/notarize 2.5.0, osx-sign 1.3.3, plist 3.1.0) depends on minimatch: 10.2.5.minimatch@10.2.5 requires brace-expansion@^5.0.5 (npm view) and resolves brace-expansion: 5.0.7 (lock:13324-13326).brace-expansion@5.0.7/dist/commonjs/index.js:4 → exports.expand = expand (NAMED).brace-expansion@2.1.2/index.js:3 → module.exports = expandTop (DEFAULT, no .expand).minimatch@10.2.5/dist/commonjs/index.js:4,157 → const brace_expansion_1 = require("brace-expansion") … (0, brace_expansion_1.expand)(...).>=2.1.0 <3 forcing 2.x → .expand is undefined → the exact TypeError reported. Confirmed real.Multiple majors coexist under the per-major overrides (lock snapshots 13324-13342):
minimatch@3.1.5 → brace-expansion 1.1.16minimatch@5.1.9 / 8.0.7 / 9.0.9 → brace-expansion 2.1.2minimatch@10.2.5 → brace-expansion 5.0.7Only 1.1.16, 2.1.2, 5.0.7 actually resolve (grep across all snapshots: 1×1.1.16, 3×2.1.2, 1×5.0.7). The per-major approach correctly lets them coexist instead of forcing one.
Dependabot #294 / GHSA-3jxr-9vmj-r5cp (high, DoS): advisory patched ranges = <1.1.16→1.1.16, >=2.0.0 <2.1.2→2.1.2, >=3.0.0 <5.0.7→5.0.7. **All three resolved versions (1.1.16, 2.1.2, 5.0.7) are exactly the patched ...
Date: Jul 21, 2026 * 🔴 [requested-review] (13:24) User requested a rigorous, skeptical adversarial CORRECTNESS review of a pnpm-overrides dependency-fix PR, focused strictly on real bugs/dependency-resolution correctness, ignoring style. * 🔴 (13:24) User stated repo is /home/byk/Code/getsentry/spotlight; PR branch fetched locally as ref pr-1337, base main. * 🔴 (13:24) User stated background: r…