From 5851a43c9f8e8fbb4b1c632a445e8497dc035422 Mon Sep 17 00:00:00 2001 From: Torrey Atcitty Date: Mon, 20 Jul 2026 14:02:11 -0700 Subject: [PATCH] Fix high/critical audit findings and add CI dependency-audit gate Upgrades: - http-proxy-middleware ^1.0.3 -> ^3.0.6 (GHSA-c7qv-q95q-8v27, high). Nothing imports it directly and the existing resolutions entry already forced nested copies to 3.0.7; this aligns the direct dependency. - websocket-driver -> ^0.7.5 via resolutions (GHSA-xv26-6w52-cph6, critical, via webpack-dev-server > sockjs). Two advisories cannot be fixed by upgrade and are allowlisted with justifications in scripts/audit-allowlist.json: - GHSA-v7cw-rxgx-j29p (compound-components): false positive; our dep is a pinned git URL, the advisory targets a name-squatted npm package. - GHSA-2p57-rm9w-gvfp (ip): no patched release exists; dev-only via webpack-dev-server v3. Removable by upgrading to webpack-dev-server 4+. The new audit workflow runs scripts/audit-check.js, which parses `yarn audit --json` and fails on any non-allowlisted high/critical advisory (yarn 1's --level flag does not gate its exit code). Production build verified passing after the upgrades. Co-Authored-By: Claude Fable 5 --- .github/workflows/audit.yml | 21 +++++++++++ package.json | 3 +- scripts/audit-allowlist.json | 12 ++++++ scripts/audit-check.js | 72 ++++++++++++++++++++++++++++++++++++ yarn.lock | 43 +++------------------ 5 files changed, 113 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/audit.yml create mode 100644 scripts/audit-allowlist.json create mode 100644 scripts/audit-check.js diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..63a167c --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,21 @@ +name: Dependency Audit +on: push + +jobs: + audit: + name: yarn audit (high/critical must pass) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '20' + + # Runs `yarn audit` against yarn.lock (no install needed) and fails on + # any high/critical advisory not allowlisted (with justification) in + # scripts/audit-allowlist.json. + - name: Audit dependencies + run: node scripts/audit-check.js diff --git a/package.json b/package.json index 0f1b33b..4f92fc5 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "file-loader": "^6.0.0", "fs-extra": "^9.0.0", "html-webpack-plugin": "4.2.1", - "http-proxy-middleware": "^1.0.3", + "http-proxy-middleware": "^3.0.6", "mini-css-extract-plugin": "^0.9.0", "minimist": "1.2.8", "mocha": "^7.1.2", @@ -135,6 +135,7 @@ "picomatch": "^2.3.2", "async": "^3.2.2", "webpack-dev-middleware": "^5.3.4", + "websocket-driver": "^0.7.5", "ansi-regex": "^5.0.1", "ansi-html": "0.0.8", "braces": "^3.0.3", diff --git a/scripts/audit-allowlist.json b/scripts/audit-allowlist.json new file mode 100644 index 0000000..46a00fe --- /dev/null +++ b/scripts/audit-allowlist.json @@ -0,0 +1,12 @@ +[ + { + "id": "GHSA-v7cw-rxgx-j29p", + "module": "compound-components", + "justification": "False positive: our compound-components is installed directly from the Compound-Foundation GitHub repo (pinned commit), not from the npm registry. The advisory flags a malicious package squatting the same name on the public registry; yarn audit matches by name only." + }, + { + "id": "GHSA-2p57-rm9w-gvfp", + "module": "ip", + "justification": "No patched version of `ip` exists (advisory covers <=2.0.1, i.e. every release). Dev-only dependency of webpack-dev-server v3 (local dev server); not part of the production build. Remove when webpack-dev-server is upgraded to v4+, which no longer depends on `ip`." + } +] diff --git a/scripts/audit-check.js b/scripts/audit-check.js new file mode 100644 index 0000000..939d3ab --- /dev/null +++ b/scripts/audit-check.js @@ -0,0 +1,72 @@ +#!/usr/bin/env node +// CI gate: fail if `yarn audit` reports any high/critical advisory. +// +// A wrapper is needed because yarn 1's `--level` flag only filters the +// report — the exit code is a severity bitmask that still reflects lower +// severities, so `yarn audit --level high` can't be used directly as a gate. +// +// Advisories listed in scripts/audit-allowlist.json (with a written +// justification) are skipped. + +const { spawnSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const allowlistPath = path.join(__dirname, 'audit-allowlist.json'); +const allowlist = fs.existsSync(allowlistPath) + ? JSON.parse(fs.readFileSync(allowlistPath, 'utf8')) + : []; +const allowedIds = new Map(allowlist.map((e) => [e.id, e])); + +const res = spawnSync('yarn', ['audit', '--json'], { + cwd: path.join(__dirname, '..'), + encoding: 'utf8', + maxBuffer: 64 * 1024 * 1024, +}); + +const failing = new Map(); +const skipped = new Map(); +let sawSummary = false; + +for (const line of (res.stdout || '').split('\n')) { + if (!line.trim()) continue; + let event; + try { + event = JSON.parse(line); + } catch { + continue; + } + if (event.type === 'auditSummary') sawSummary = true; + if (event.type !== 'auditAdvisory') continue; + const a = event.data.advisory; + if (a.severity !== 'high' && a.severity !== 'critical') continue; + const id = a.github_advisory_id || String(a.id); + if (allowedIds.has(id)) { + skipped.set(id, a); + } else { + failing.set(id, a); + } +} + +if (!sawSummary) { + console.error('audit-check: `yarn audit` produced no summary — audit did not run correctly.'); + console.error(res.stderr || ''); + process.exit(1); +} + +for (const [id, a] of skipped) { + console.log(`ALLOWLISTED ${a.severity}: ${a.module_name} (${id}) — ${allowedIds.get(id).justification}`); +} + +if (failing.size > 0) { + console.error('\naudit-check: high/critical vulnerabilities found:\n'); + for (const [id, a] of failing) { + console.error(` ${a.severity.toUpperCase()}: ${a.module_name} — ${a.title}`); + console.error(` ${a.url || 'https://github.com/advisories/' + id}`); + console.error(` vulnerable: ${a.vulnerable_versions} patched: ${a.patched_versions}\n`); + } + console.error(`${failing.size} advisory(ies) must be fixed (or allowlisted with justification in scripts/audit-allowlist.json).`); + process.exit(1); +} + +console.log('audit-check: no unaddressed high/critical vulnerabilities.'); diff --git a/yarn.lock b/yarn.lock index cfe274c..1ca39cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2557,13 +2557,6 @@ dependencies: "@types/node" "*" -"@types/http-proxy@^1.17.5": - version "1.17.7" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.7.tgz#30ea85cc2c868368352a37f0d0d3581e24834c6f" - integrity sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w== - dependencies: - "@types/node" "*" - "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -3720,7 +3713,7 @@ brace-expansion@^2.0.2: dependencies: balanced-match "^1.0.0" -braces@^2.3.1, braces@^2.3.2, braces@^3.0.1, braces@^3.0.3, braces@~3.0.2: +braces@^2.3.1, braces@^2.3.2, braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -7166,17 +7159,6 @@ http-proxy-middleware@0.19.1, http-proxy-middleware@^3.0.6: is-plain-object "^5.0.0" micromatch "^4.0.8" -http-proxy-middleware@^1.0.3: - version "1.3.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz#43700d6d9eecb7419bf086a128d0f7205d9eb665" - integrity sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg== - dependencies: - "@types/http-proxy" "^1.17.5" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" @@ -7736,11 +7718,6 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -8448,14 +8425,6 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" @@ -9425,7 +9394,7 @@ picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1, picomatch@^2.3.2, picomatch@^4.0.3: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1, picomatch@^2.3.2, picomatch@^4.0.3: version "2.3.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== @@ -12347,10 +12316,10 @@ webpack@^4.35.2: watchpack "^1.7.4" webpack-sources "^1.4.1" -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== +websocket-driver@>=0.5.1, websocket-driver@^0.7.4, websocket-driver@^0.7.5: + version "0.7.5" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.5.tgz#569d22764ab21f2de20af0e74b411e8ae5a0fa46" + integrity sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA== dependencies: http-parser-js ">=0.5.1" safe-buffer ">=5.1.0"