From 7fa0f0b8532d4d53fbf6ab6968676af4cb80a46e Mon Sep 17 00:00:00 2001 From: Amir Bredy Date: Thu, 17 Sep 2026 11:38:30 -0700 Subject: [PATCH 1/2] fix(deps): port patched CycloneDX tooling to macOS author Port Dependabot PR #133 metadata without changing author release versions. Add a security regression guard and validate the real SBOM commands on hosted Windows, Linux, macOS and Node 24 before removing the temporary workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2b11c4f8-c7fa-4b14-9833-5d0b1006223b --- .../temporary-cyclonedx-validation.yml | 60 +++++++++++++++++++ CHANGELOG.md | 3 + docs/src/changelog.md | 2 + package-lock.json | 8 +-- package.json | 2 +- scripts/release-metadata.test.mjs | 21 +++++++ 6 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/temporary-cyclonedx-validation.yml diff --git a/.github/workflows/temporary-cyclonedx-validation.yml b/.github/workflows/temporary-cyclonedx-validation.yml new file mode 100644 index 0000000..036ac93 --- /dev/null +++ b/.github/workflows/temporary-cyclonedx-validation.yml @@ -0,0 +1,60 @@ +name: Temporary CycloneDX release compatibility + +on: + push: + branches: [fix/mac-cyclonedx-20260917] + +permissions: + contents: read + +jobs: + sbom: + name: SBOM (${{ matrix.os }}, Node ${{ matrix.node }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + node: '22' + ref: c8fb672258bae0250ad8d96bbd49856a8d483f87 + - os: ubuntu-latest + node: '24' + ref: c8fb672258bae0250ad8d96bbd49856a8d483f87 + - os: windows-latest + node: '22' + ref: c8fb672258bae0250ad8d96bbd49856a8d483f87 + - os: macos-14 + node: '22' + ref: ${{ github.sha }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ matrix.ref }} + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: ${{ matrix.node }} + cache: npm + - name: Clean install + run: npm ci --include=optional + - name: Validate production SBOM and dependency security + shell: bash + run: | + set -euo pipefail + mkdir -p dependency-evidence + npm audit --json > dependency-evidence/audit.json + npx --no-install @cyclonedx/cyclonedx-npm \ + --omit dev \ + --output-format JSON \ + --output-file dependency-evidence/sbom.cdx.json + node -e "const assert = require('node:assert/strict'); const bom = require('./dependency-evidence/sbom.cdx.json'); assert.equal(require('./node_modules/@cyclonedx/cyclonedx-npm/package.json').version, '6.0.0'); assert.equal(bom.bomFormat, 'CycloneDX'); assert.ok(Array.isArray(bom.components) && bom.components.length > 0); assert.equal(bom.metadata.component.version, require('./package.json').version); console.log(bom.components.length + ' production components in SBOM');" + npx --no-install vitest run scripts/release-metadata.test.mjs + node scripts/verify-public-package-assets.mjs + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: always() + with: + name: cyclonedx-${{ matrix.os }}-node-${{ matrix.node }} + path: dependency-evidence/ + retention-days: 3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5634e79..41a6187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,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. - Port the dependency security fixes from the Full edition without changing the macOS Author version or capability boundary: require xmldom 0.8.15+, brace-expansion 1.1.18 / 2.1.4 / 5.0.9, and nanoid 3.3.18+. diff --git a/docs/src/changelog.md b/docs/src/changelog.md index b913d55..b410b3e 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -5,6 +5,8 @@ shipped semver tag; older entries summarize the 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. - **Dependency security:** Port patched xmldom, brace-expansion, and nanoid dependencies, and update Electron to 42.11.3 to remove the vulnerable legacy ZIP extractor. The macOS Author version and author-only capability diff --git a/package-lock.json b/package-lock.json index f813446..23a9339 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", @@ -1524,9 +1524,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 c9f66f6..f427203 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 61fcc48..a8369f0 100644 --- a/scripts/release-metadata.test.mjs +++ b/scripts/release-metadata.test.mjs @@ -77,6 +77,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), From 65eb7f57f4fecb15f37cc3f3d46283b3c17ff47d Mon Sep 17 00:00:00 2001 From: Amir Bredy Date: Thu, 17 Sep 2026 11:43:00 -0700 Subject: [PATCH 2/2] chore(ci): remove completed CycloneDX compatibility workflow Hosted Windows, Linux, macOS and Node 24 checks generated valid production SBOMs and reported zero audit findings. The final PR contains only dependency metadata, regression coverage and changelog entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2b11c4f8-c7fa-4b14-9833-5d0b1006223b --- .../temporary-cyclonedx-validation.yml | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 .github/workflows/temporary-cyclonedx-validation.yml diff --git a/.github/workflows/temporary-cyclonedx-validation.yml b/.github/workflows/temporary-cyclonedx-validation.yml deleted file mode 100644 index 036ac93..0000000 --- a/.github/workflows/temporary-cyclonedx-validation.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Temporary CycloneDX release compatibility - -on: - push: - branches: [fix/mac-cyclonedx-20260917] - -permissions: - contents: read - -jobs: - sbom: - name: SBOM (${{ matrix.os }}, Node ${{ matrix.node }}) - runs-on: ${{ matrix.os }} - timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - node: '22' - ref: c8fb672258bae0250ad8d96bbd49856a8d483f87 - - os: ubuntu-latest - node: '24' - ref: c8fb672258bae0250ad8d96bbd49856a8d483f87 - - os: windows-latest - node: '22' - ref: c8fb672258bae0250ad8d96bbd49856a8d483f87 - - os: macos-14 - node: '22' - ref: ${{ github.sha }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ matrix.ref }} - persist-credentials: false - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: ${{ matrix.node }} - cache: npm - - name: Clean install - run: npm ci --include=optional - - name: Validate production SBOM and dependency security - shell: bash - run: | - set -euo pipefail - mkdir -p dependency-evidence - npm audit --json > dependency-evidence/audit.json - npx --no-install @cyclonedx/cyclonedx-npm \ - --omit dev \ - --output-format JSON \ - --output-file dependency-evidence/sbom.cdx.json - node -e "const assert = require('node:assert/strict'); const bom = require('./dependency-evidence/sbom.cdx.json'); assert.equal(require('./node_modules/@cyclonedx/cyclonedx-npm/package.json').version, '6.0.0'); assert.equal(bom.bomFormat, 'CycloneDX'); assert.ok(Array.isArray(bom.components) && bom.components.length > 0); assert.equal(bom.metadata.component.version, require('./package.json').version); console.log(bom.components.length + ' production components in SBOM');" - npx --no-install vitest run scripts/release-metadata.test.mjs - node scripts/verify-public-package-assets.mjs - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: always() - with: - name: cyclonedx-${{ matrix.os }}-node-${{ matrix.node }} - path: dependency-evidence/ - retention-days: 3