diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ff6900..abc8e1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ ### Security +- Upgrade the pinned CycloneDX SBOM generator to 6.0.0 to fix Windows + workspace-argument shell injection (`GHSA-q69g-4hcv-6jg4`). Guard the + manifest and every lockfile copy against reintroducing a vulnerable release. - Require `@xmldom/xmldom` 0.8.15 or later in the 0.8 line for Electron packaging dependencies. This addresses the XML injection, malformed-input, and quadratic-time parsing advisories affecting the previous 0.8.13 copy. diff --git a/docs/src/changelog.md b/docs/src/changelog.md index bb8ae4a..4a2cdad 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -6,6 +6,8 @@ foundational work by theme. ## Unreleased +- **SBOM tooling security:** Pin CycloneDX npm 6.0.0 to address + `GHSA-q69g-4hcv-6jg4`, with regression coverage for resolved versions. - **Build dependency security:** Require patched `@xmldom/xmldom` 0.8.15+ within the 0.8 line for Electron packaging tools, with a lockfile guard against reintroducing vulnerable copies. diff --git a/package-lock.json b/package-lock.json index ea13793..002c9f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "packages/*" ], "devDependencies": { - "@cyclonedx/cyclonedx-npm": "5.0.0", + "@cyclonedx/cyclonedx-npm": "6.0.0", "@playwright/test": "^1.59.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", @@ -1534,9 +1534,9 @@ } }, "node_modules/@cyclonedx/cyclonedx-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@cyclonedx/cyclonedx-npm/-/cyclonedx-npm-5.0.0.tgz", - "integrity": "sha512-pEz/pqYJHQ0ZvY5xFJa+GQ5AjNL7JOEJCXCHymvKo2N1VnzKE1ROpkumduJt8a0DukQjQfBGRXouGQ9xqJtJaQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@cyclonedx/cyclonedx-npm/-/cyclonedx-npm-6.0.0.tgz", + "integrity": "sha512-kpWjjV0j5y0mMHUB5dSx1hxweH8K2blSqkgdQ6eHgU7aClB4CcXGhbHtGY6WVHSo3A01Rt7WOLas/wQ1E+tBDg==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 5539171..f143046 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "locales:review": "node scripts/review-locales.mjs" }, "devDependencies": { - "@cyclonedx/cyclonedx-npm": "5.0.0", + "@cyclonedx/cyclonedx-npm": "6.0.0", "@playwright/test": "^1.59.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", diff --git a/scripts/release-metadata.test.mjs b/scripts/release-metadata.test.mjs index 17c7580..d64191a 100644 --- a/scripts/release-metadata.test.mjs +++ b/scripts/release-metadata.test.mjs @@ -91,6 +91,27 @@ describe('public release metadata', () => { expect(nonPublic).toEqual([]); }); + it('pins a patched CycloneDX SBOM generator without vulnerable resolved copies', async () => { + const [packageJson, lockfile] = await Promise.all([ + read('package.json').then(JSON.parse), + read('package-lock.json').then(JSON.parse), + ]); + const dependency = '@cyclonedx/cyclonedx-npm'; + const pinned = packageJson.devDependencies[dependency]; + const version = /^(\d+)\.\d+\.\d+$/.exec(pinned); + const copies = Object.entries(lockfile.packages) + .filter(([packagePath]) => packagePath.endsWith(`node_modules/${dependency}`)); + + expect(version, pinned).not.toBeNull(); + expect(Number(version?.[1])).toBeGreaterThanOrEqual(6); + expect(lockfile.packages[''].devDependencies[dependency]).toBe(pinned); + expect(copies.length).toBeGreaterThan(0); + for (const [packagePath, metadata] of copies) { + expect(metadata.version, packagePath).toBe(pinned); + expect(metadata.dev, packagePath).toBe(true); + } + }); + it('pins patched brace-expansion versions in every supported major', async () => { const [packageJson, lockfile] = await Promise.all([ read('package.json').then(JSON.parse),