From 248ac0bbf71fb0e7ea6d896d9f3b87c5d1ec1b7b Mon Sep 17 00:00:00 2001 From: Anupam Mediratta Date: Fri, 4 Sep 2026 23:11:16 +0530 Subject: [PATCH] fix: update brace-expansion to 2.1.4 within minimatch's declared range brace-expansion 2.1.2 is affected by CVE-2026-14257 (GHSA-mh99-v99m-4gvg, patched 2.1.3) and CVE-2026-69152 (GHSA-rgw5-rvv9-x895, patched 2.1.4), which bypasses the CVE-2026-14257 mitigation. Version 2.1.4 is the first 2.x release patched for both. It enters the tree only through googleapis > googleapis-common > gaxios > rimraf > glob > minimatch, which declares ^2.0.2. 2.1.4 satisfies that range, so this is an in-range lockfile update: package.json is unchanged and no dependency override is required. Add test/dependencies.test.js to assert the resolved version stays patched and keeps satisfying every declared range in the lockfile. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01R7oXdhMLN1izUhx4JJbGWc --- AGENTS.md | 1 + package-lock.json | 6 ++-- test/dependencies.test.js | 74 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 test/dependencies.test.js diff --git a/AGENTS.md b/AGENTS.md index 0698b09..a6dd7e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,6 +69,7 @@ synchronization, and heartbeat checks. - `test/network.test.js`: Offline pause/resume and post-sleep network-settling coverage. - `test/recovery.test.js`: Recovery backup collision and Drive restoration coverage. +- `test/dependencies.test.js`: Locked transitive-dependency security and compatibility invariants. - `raycast-extension/`: Optional Raycast extension for active-browser document pairing. - `examples/google-docs-sync.example.json`: Inert example workspace pairing diff --git a/package-lock.json b/package-lock.json index a311992..4db851a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -578,9 +578,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" diff --git a/test/dependencies.test.js b/test/dependencies.test.js new file mode 100644 index 0000000..bbcc9e8 --- /dev/null +++ b/test/dependencies.test.js @@ -0,0 +1,74 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const lockfile = JSON.parse(fs.readFileSync(path.join(projectRoot, "package-lock.json"), "utf8")); + +// brace-expansion reaches the tree only through +// googleapis > googleapis-common > gaxios > rimraf > glob > minimatch, which declares +// ^2.0.2. Version 2.1.4 is the first 2.x release patched for both CVE-2026-14257 +// (GHSA-mh99-v99m-4gvg, patched 2.1.3) and CVE-2026-69152 (GHSA-rgw5-rvv9-x895, patched +// 2.1.4), which bypasses the CVE-2026-14257 mitigation. +const BRACE_EXPANSION_MINIMUM = [2, 1, 4]; + +function parseVersion(version) { + const numbers = /^(\d+)\.(\d+)\.(\d+)/.exec(version); + assert.ok(numbers, `expected a semantic version, received ${version}`); + return numbers.slice(1, 4).map(Number); +} + +function compareVersions(left, right) { + for (let index = 0; index < 3; index += 1) { + if (left[index] !== right[index]) return left[index] - right[index]; + } + return 0; +} + +// Deliberately narrow: only caret ranges are understood, and anything else fails loudly +// rather than being silently treated as satisfied. +function satisfiesCaret(version, range) { + assert.match(range, /^\^\d+\.\d+\.\d+$/, `unsupported dependency range ${range}`); + const lowerBound = parseVersion(range.slice(1)); + return version[0] === lowerBound[0] && compareVersions(version, lowerBound) >= 0; +} + +function resolvedVersion(name) { + const entry = lockfile.packages[`node_modules/${name}`]; + assert.ok(entry, `expected ${name} in the lockfile`); + return parseVersion(entry.version); +} + +function declaredRanges(name) { + return Object.entries(lockfile.packages) + .flatMap(([location, entry]) => { + const ranges = { + ...entry.dependencies, + ...entry.optionalDependencies, + ...entry.peerDependencies, + }; + return ranges[name] ? [{ location: location || "the project root", range: ranges[name] }] : []; + }); +} + +test("brace-expansion is patched for CVE-2026-14257 and CVE-2026-69152", () => { + const version = resolvedVersion("brace-expansion"); + assert.ok( + compareVersions(version, BRACE_EXPANSION_MINIMUM) >= 0, + `brace-expansion ${version.join(".")} is older than the patched ${BRACE_EXPANSION_MINIMUM.join(".")}`, + ); +}); + +test("the resolved brace-expansion satisfies every declared dependency range", () => { + const version = resolvedVersion("brace-expansion"); + const dependers = declaredRanges("brace-expansion"); + assert.ok(dependers.length > 0, "expected at least one declared brace-expansion range"); + for (const { location, range } of dependers) { + assert.ok( + satisfiesCaret(version, range), + `brace-expansion ${version.join(".")} does not satisfy ${range} declared by ${location}`, + ); + } +});