fix(security): raise shell-quote, add compression and source-map-js floors

- react-devtools-core > shell-quote ^1.8.4 -> ^1.11.0 (GHSA-pqg4-j6r4-53mv,
  critical, quote() command injection; resolves 1.12.0). Raises the #95 floor.
- @expo/cli > compression ^1.8.2 (GHSA-vc2v-76pw-4v95, high, dev server DoS).
- source-map-js@^1.0.0 -> ^1.2.2 (GHSA-68fv-2mgg-jv7q, high, postcss pipeline).

Lock: only the three package entries change, package key set identical.
Smoke test: floor guard plus a call to the consumed API for each package.
Export bundle byte-identical before/after (44 files). npm audit 43 -> 40,
critical 0. Build and dev tooling only, nothing ships in the APK.

Fixes #133
Fixes #134
Fixes #135

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-10-06 19:07:48 -04:00
parent aa41642a87
commit 8b27054be9
3 changed files with 81 additions and 10 deletions

23
package-lock.json generated
View file

@ -4861,14 +4861,15 @@
}
},
"node_modules/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz",
"integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"destroy": "1.2.0",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
@ -4876,6 +4877,10 @@
},
"engines": {
"node": ">= 0.8.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/compression/node_modules/debug": {
@ -11283,9 +11288,9 @@
}
},
"node_modules/shell-quote": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz",
"integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==",
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz",
"integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -11354,9 +11359,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"

View file

@ -72,6 +72,7 @@
"uuid": "^11.1.1",
"postcss": "^8.5.18",
"postcss-selector-parser@^6.0.0": "^6.1.3",
"source-map-js@^1.0.0": "^1.2.2",
"http-cache-semantics@^4.0.0": "^4.3.0",
"js-yaml@^3.0.0": "^3.15.2",
"js-yaml@^4.0.0": "^4.3.2",
@ -81,12 +82,13 @@
"@babel/core": "^7.29.6",
"browserslist": "^4.28.7",
"@expo/cli": {
"compression": "^1.8.2",
"ws": "^8.20.1",
"undici": "^6.28.1",
"tar": "^7.5.21"
},
"react-devtools-core": {
"shell-quote": "^1.8.4",
"shell-quote": "^1.11.0",
"ws": "^7.5.11"
},
"react-native": {

View file

@ -335,6 +335,70 @@ check('every installed postcss-selector-parser copy is 6.x and >= 6.1.3', () =>
);
});
// --- Override floors (#133, #134, #135) ---
// shell-quote: scoped to react-devtools-core (raised from #95's ^1.8.4).
// compression: scoped to @expo/cli (dev server middleware).
// source-map-js: version-scoped to major 1 (postcss, tailwindcss pipeline).
// Each floor is paired with a call to the API its consumer actually uses.
check('every installed shell-quote copy is >= 1.11.0 and still quotes/parses', () => {
const copies = installedCopies('shell-quote');
assert.ok(copies.length > 0, 'shell-quote is not installed');
const strays = copies.filter((c) => !versionAtLeast(c.version, '1.11.0'));
assert.deepEqual(
strays.map((c) => `${c.version} at ${path.relative(ROOT, c.dir)}`),
[],
'shell-quote must be >= 1.11.0'
);
for (const c of copies) {
const { quote, parse } = require(c.dir);
const where = `shell-quote ${c.version} at ${path.relative(ROOT, c.dir)}`;
assert.equal(quote(['code', '-g', 'a b.js:1']), "code -g 'a b.js:1'", `${where}: quote`);
assert.deepEqual(parse('a "b c" d'), ['a', 'b c', 'd'], `${where}: parse`);
}
});
check('every installed compression copy is >= 1.8.2 and builds a middleware', () => {
const copies = installedCopies('compression');
assert.ok(copies.length > 0, 'compression is not installed');
const strays = copies.filter((c) => !versionAtLeast(c.version, '1.8.2'));
assert.deepEqual(
strays.map((c) => `${c.version} at ${path.relative(ROOT, c.dir)}`),
[],
'compression must be >= 1.8.2'
);
for (const c of copies) {
const middleware = require(c.dir)();
assert.equal(typeof middleware, 'function', `compression ${c.version}: middleware`);
assert.equal(middleware.length, 3, `compression ${c.version}: (req, res, next) signature`);
}
});
check('every installed source-map-js copy is 1.x >= 1.2.2 and round-trips a mapping', () => {
const copies = installedCopies('source-map-js');
assert.ok(copies.length > 0, 'source-map-js is not installed');
const strays = copies.filter(
(c) => majorOf(c.version) !== '1' || !versionAtLeast(c.version, '1.2.2')
);
assert.deepEqual(
strays.map((c) => `${c.version} at ${path.relative(ROOT, c.dir)}`),
[],
'source-map-js must be 1.x >= 1.2.2'
);
for (const c of copies) {
const { SourceMapGenerator, SourceMapConsumer } = require(c.dir);
const gen = new SourceMapGenerator({ file: 'out.css' });
gen.addMapping({
source: 'in.css',
original: { line: 3, column: 2 },
generated: { line: 1, column: 0 },
});
const pos = new SourceMapConsumer(gen.toJSON()).originalPositionFor({ line: 1, column: 0 });
const where = `source-map-js ${c.version} at ${path.relative(ROOT, c.dir)}`;
assert.deepEqual([pos.source, pos.line, pos.column], ['in.css', 3, 2], where);
}
});
// The call that broke under the blanket override: each minimatch copy must
// still expand braces through its own brace-expansion major.
check('every installed minimatch copy still matches globs and braces', () => {