From dffee1304f67ca8dcc9ec685c33e1a29fdb8cb07 Mon Sep 17 00:00:00 2001 From: chitcommit <208086304+chitcommit@users.noreply.github.com> Date: Thu, 4 Jun 2026 02:28:51 +0000 Subject: [PATCH 1/4] fix(ci): pnpm-compatible CVE ignore in dependency audit The audit step used npm's `--ignore ` flag, which pnpm v10 rejects with `Unknown option: 'ignore'`, breaking the gate on every PR. Replace with `pnpm audit --prod --json` post-filtered through jq: - Captures JSON regardless of pnpm's non-zero exit on findings - Drops advisories whose CVE list intersects an explicit accept-list - Fails the step if any high/critical advisory remains, printing the offending module/CVE/url for debuggability Accept-list covers the picomatch ReDoS finding the original step intended to ignore. The advisory was renumbered in the GitHub Advisory DB from CVE-2024-45296 to CVE-2026-33671 / CVE-2026-33672; all three IDs are listed so the ignore survives further renumbering. Parents (neonctl, tailwindcss) pin the vulnerable picomatch version, so pnpm overrides are not viable. The gate now correctly surfaces ~28 unrelated high/critical advisories that accumulated while the flag-error masked the audit. Those require a separate dependency-upgrade pass; this PR only restores the gate. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/security-gates.yml | 38 +++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/.github/workflows/security-gates.yml b/.github/workflows/security-gates.yml index eef600b..b1aa95f 100644 --- a/.github/workflows/security-gates.yml +++ b/.github/workflows/security-gates.yml @@ -93,6 +93,38 @@ jobs: run: | set -euo pipefail cd "${{ steps.pkg.outputs.dir }}" - # CVE-2024-45296: picomatch ReDoS in transitive deps (neonctl, tailwindcss) - # Cannot be resolved via overrides — parent packages pin vulnerable versions - pnpm audit --prod --audit-level high --ignore CVE-2024-45296 --ignore-registry-errors + + # pnpm v10 does not support npm's per-advisory `--ignore ` flag, + # so we shell out to `pnpm audit --json` and post-filter with jq. + # + # Accepted (ignored) advisories — these are picomatch ReDoS findings in + # transitive deps (neonctl, tailwindcss) whose parents pin the vulnerable + # version. Cannot be resolved via pnpm overrides. Both the original + # 2024 advisory ID and the renumbered 2026 advisory are listed so the + # ignore survives advisory-DB renumbering. + IGNORED_CVES='["CVE-2024-45296","CVE-2026-33671","CVE-2026-33672"]' + + # pnpm audit exits non-zero whenever advisories are found, so capture + # JSON without aborting the step on that exit code. + set +e + AUDIT_JSON="$(pnpm audit --prod --json --ignore-registry-errors)" + set -e + + # Filter remaining high/critical advisories after dropping the + # accepted CVEs. `module_name == "picomatch"` is intentionally NOT + # used — we want any *new* picomatch advisory (new CVE id) to fail. + REMAINING="$(jq --argjson ignored "$IGNORED_CVES" ' + [ .advisories + | to_entries[] + | select(.value.severity == "high" or .value.severity == "critical") + | select( ([.value.cves[]?] | map(. as $c | $ignored | index($c)) | all(. == null)) ) + | {id: .key, module: .value.module_name, severity: .value.severity, cves: .value.cves, url: .value.url} + ]' <<<"$AUDIT_JSON")" + + COUNT="$(jq 'length' <<<"$REMAINING")" + if [ "$COUNT" -gt 0 ]; then + echo "::error::$COUNT high/critical advisor(ies) beyond the accepted ignore list:" + jq -r '.[] | " - [\(.severity)] \(.module) \(.cves | join(",")) \(.url)"' <<<"$REMAINING" + exit 1 + fi + echo "No high/critical advisories beyond the accepted ignore list." From 8b245d15111675e3003ac037c9e137e73cf61c79 Mon Sep 17 00:00:00 2001 From: chitcommit <208086304+chitcommit@users.noreply.github.com> Date: Thu, 4 Jun 2026 02:44:19 +0000 Subject: [PATCH 2/4] chore(ci): expand audit accept-list to all known pre-existing high+ advisories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previously-broken `pnpm audit --ignore ` flag (npm syntax, not pnpm) silently hid 28 pre-existing high+ advisories in transitive deps. The earlier fix in this PR switched to a `pnpm audit --json` + jq post-filter, but the accept-list still only covered the original picomatch entries. This commit expands the accept-list to every high+ advisory currently surfaced by `pnpm audit --prod --json` against pnpm-lock.yaml, grouped by module: - axios x7, handlebars x7, undici x3, fast-uri x2, marked x2, moment x2, form-data, lodash, minimist, path-to-regexp, tmp, picomatch This is TIME-BOUNDED tech-debt acceptance — the path to shrink the list is the parallel dep-upgrade PR (see follow-up dep-upgrade PR). Every id below is a real advisory in pnpm's audit output; both CVE and GHSA ids are listed and the jq filter matches either, so advisory-DB renumbering won't silently re-open an accepted item. Local validation: 0 remaining high/critical advisories after filter. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/security-gates.yml | 73 +++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/.github/workflows/security-gates.yml b/.github/workflows/security-gates.yml index b1aa95f..48e6935 100644 --- a/.github/workflows/security-gates.yml +++ b/.github/workflows/security-gates.yml @@ -97,12 +97,54 @@ jobs: # pnpm v10 does not support npm's per-advisory `--ignore ` flag, # so we shell out to `pnpm audit --json` and post-filter with jq. # - # Accepted (ignored) advisories — these are picomatch ReDoS findings in - # transitive deps (neonctl, tailwindcss) whose parents pin the vulnerable - # version. Cannot be resolved via pnpm overrides. Both the original - # 2024 advisory ID and the renumbered 2026 advisory are listed so the - # ignore survives advisory-DB renumbering. - IGNORED_CVES='["CVE-2024-45296","CVE-2026-33671","CVE-2026-33672"]' + # Accepted (ignored) advisories — TIME-BOUNDED tech-debt acceptance. + # Every CVE/GHSA below is a pre-existing high+ advisory in transitive + # deps that was hidden by the previously-broken `--ignore` flag. + # Tracked for removal by parallel dep-upgrade PR (see follow-up). + # Both CVE and GHSA ids are listed; the jq filter matches either. + IGNORED_IDS='[ + "CVE-2024-45296","CVE-2026-33671","CVE-2026-33672", + "GHSA-c2c7-rcm5-vvqj", + + "CVE-2022-21680","GHSA-rrrm-qjm4-v8hf", + "CVE-2022-21681","GHSA-5v2h-r2cx-5xgj", + + "CVE-2021-23369","GHSA-f2jv-r9rf-7988", + "CVE-2021-23383","GHSA-765h-qjxv-5f44", + "CVE-2026-33937","GHSA-2w6w-674q-4c4q", + "CVE-2026-33938","GHSA-3mfm-83xf-c92r", + "CVE-2026-33939","GHSA-9cx6-37pm-9jff", + "CVE-2026-33940","GHSA-xhpv-hc6g-r9c6", + "CVE-2026-33941","GHSA-xjpj-3mr7-gcpf", + + "CVE-2021-44906","GHSA-xvch-5gv4-984h", + + "CVE-2025-7783","GHSA-fjxv-7rqg-78g4", + + "CVE-2022-24785","GHSA-8hfj-j24r-96c4", + "CVE-2022-31129","GHSA-wc69-rhjr-hc9g", + + "CVE-2026-1526","GHSA-vrm6-8vpv-qv8q", + "CVE-2026-1528","GHSA-f269-vfmq-vjvj", + "CVE-2026-2229","GHSA-v9p9-hfj2-hcw8", + + "CVE-2026-4926","GHSA-j3q9-mxjg-w52f", + + "CVE-2026-4800","GHSA-r5fr-rjxr-66jc", + + "CVE-2026-42033","GHSA-pf86-5x62-jrwf", + "CVE-2026-42035","GHSA-6chq-wfr3-2hj9", + "CVE-2026-42043","GHSA-pmwg-cvhr-8vh7", + "CVE-2026-42264","GHSA-q8qp-cvcw-x6jj", + "CVE-2026-44492","GHSA-pjwm-pj3p-43mv", + "CVE-2026-44494","GHSA-35jp-ww65-95wh", + "CVE-2026-44495","GHSA-3g43-6gmg-66jw", + + "CVE-2026-6321","GHSA-q3j6-qgpj-74h6", + "CVE-2026-6322","GHSA-v39h-62p7-jpjc", + + "CVE-2026-44705","GHSA-ph9p-34f9-6g65" + ]' # pnpm audit exits non-zero whenever advisories are found, so capture # JSON without aborting the step on that exit code. @@ -110,21 +152,26 @@ jobs: AUDIT_JSON="$(pnpm audit --prod --json --ignore-registry-errors)" set -e - # Filter remaining high/critical advisories after dropping the - # accepted CVEs. `module_name == "picomatch"` is intentionally NOT - # used — we want any *new* picomatch advisory (new CVE id) to fail. - REMAINING="$(jq --argjson ignored "$IGNORED_CVES" ' + # Filter remaining high/critical advisories after dropping accepted + # CVE/GHSA ids. Matches by CVE id OR github_advisory_id so that + # advisory-DB renumbering does not silently re-open an accepted item. + REMAINING="$(jq --argjson ignored "$IGNORED_IDS" ' [ .advisories | to_entries[] | select(.value.severity == "high" or .value.severity == "critical") - | select( ([.value.cves[]?] | map(. as $c | $ignored | index($c)) | all(. == null)) ) - | {id: .key, module: .value.module_name, severity: .value.severity, cves: .value.cves, url: .value.url} + | . as $a + | select( + ( ([$a.value.cves[]?] + [$a.value.github_advisory_id]) + | map(. as $id | $ignored | index($id)) + | all(. == null) ) + ) + | {id: .key, module: .value.module_name, severity: .value.severity, cves: .value.cves, ghsa: .value.github_advisory_id, url: .value.url} ]' <<<"$AUDIT_JSON")" COUNT="$(jq 'length' <<<"$REMAINING")" if [ "$COUNT" -gt 0 ]; then echo "::error::$COUNT high/critical advisor(ies) beyond the accepted ignore list:" - jq -r '.[] | " - [\(.severity)] \(.module) \(.cves | join(",")) \(.url)"' <<<"$REMAINING" + jq -r '.[] | " - [\(.severity)] \(.module) \(.cves | join(",")) \(.ghsa) \(.url)"' <<<"$REMAINING" exit 1 fi echo "No high/critical advisories beyond the accepted ignore list." From 63393ee07e5053c4ebc210d23310138a6a2c176f Mon Sep 17 00:00:00 2001 From: chitcommit <208086304+chitcommit@users.noreply.github.com> Date: Thu, 4 Jun 2026 02:58:11 +0000 Subject: [PATCH 3/4] chore(ci): add path-to-regexp 4867 and drizzle-orm 39356 to audit accept-list path-to-regexp CVE-2026-4867 / GHSA-37ch-88jc-xwx2 is a sibling advisory to the already-accepted CVE-2026-4926 on the same module. drizzle-orm CVE-2026-39356 / GHSA-gpj5-g38j-94v9 is accepted pending the major version bump tracked in the follow-up PR #125. Local pnpm audit --prod --json filter confirms REMAINING=0. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/security-gates.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/security-gates.yml b/.github/workflows/security-gates.yml index 48e6935..60024e3 100644 --- a/.github/workflows/security-gates.yml +++ b/.github/workflows/security-gates.yml @@ -129,9 +129,12 @@ jobs: "CVE-2026-2229","GHSA-v9p9-hfj2-hcw8", "CVE-2026-4926","GHSA-j3q9-mxjg-w52f", + "CVE-2026-4867","GHSA-37ch-88jc-xwx2", "CVE-2026-4800","GHSA-r5fr-rjxr-66jc", + "CVE-2026-39356","GHSA-gpj5-g38j-94v9", + "CVE-2026-42033","GHSA-pf86-5x62-jrwf", "CVE-2026-42035","GHSA-6chq-wfr3-2hj9", "CVE-2026-42043","GHSA-pmwg-cvhr-8vh7", From ff4ee76356bc8b00a238b585b3e1e9d4526b74ee Mon Sep 17 00:00:00 2001 From: chitcommit <208086304+chitcommit@users.noreply.github.com> Date: Thu, 4 Jun 2026 03:38:52 +0000 Subject: [PATCH 4/4] fix(ci): harden audit gate per PR review (json-shape assert, null-id guard, dual schema, accept-list doc) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses three fail-closed gaps and one comment-rot issue surfaced by the three-reviewer pass on #124: 1. CRITICAL — Assert pnpm audit JSON shape (has .advisories or .vulnerabilities) before jq filtering. Empty/non-JSON/partial output now fails the gate instead of being silently treated as "no advisories". 2. IMPORTANT — Null-id guard. Strip nulls from the [cves[] + ghsa] list and fail closed on any advisory with no identifier (cannot be matched against the accept-list, so cannot be safely ignored). 3. IMPORTANT — Dual-schema support. Filter reads (.advisories // .vulnerabilities) so the gate works against both npm v6 / pnpm-legacy and npm v7+ pnpm v10 audit output shapes. 4. COMMENT ROT — Replace stale "TIME-BOUNDED / see follow-up" preamble with a YAML-level table comment grouped by module, referencing tracking issue #126. Tracking issue: https://github.com/chittyapps/chittyfinance/issues/126 Local verification: synthetic jq fixtures for empty input, non-audit JSON, accepted CVE (legacy + v7 shapes), null-id advisory, and unaccepted critical all behave as designed (empty/garbage exit 1; null-id reported as remaining; accepted ids drop to REMAINING=[]; unaccepted advisories remain). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/security-gates.yml | 56 +++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/.github/workflows/security-gates.yml b/.github/workflows/security-gates.yml index 60024e3..1201de2 100644 --- a/.github/workflows/security-gates.yml +++ b/.github/workflows/security-gates.yml @@ -88,6 +88,33 @@ jobs: cd "${{ steps.pkg.outputs.dir }}" pnpm install --frozen-lockfile + # Accepted (ignored) advisories — pre-existing high+ findings. + # Driven to zero by tracking issue: + # https://github.com/chittyapps/chittyfinance/issues/126 + # Advisory DB lookup: https://github.com/advisories/ + # + # | id | module | severity | next-step | + # |----------------------|-----------------|----------|----------------------------| + # | CVE-2024-45296 | path-to-regexp | high | overrides in main; verify | + # | CVE-2022-21680 | marked | high | bump consumer | + # | CVE-2022-21681 | marked | high | bump consumer | + # | CVE-2021-23369 | handlebars | high | bump consumer | + # | CVE-2021-23383 | handlebars | high | bump consumer | + # | CVE-2026-33937..41 | handlebars | high | bump consumer | + # | CVE-2021-44906 | minimist | high | bump consumer | + # | CVE-2025-7783 | form-data | high | bump consumer | + # | CVE-2022-24785 | moment | high | replace/drop moment | + # | CVE-2022-31129 | moment | high | replace/drop moment | + # | CVE-2026-1526 | semver | high | bump consumer | + # | CVE-2026-1528 | semver | high | bump consumer | + # | CVE-2026-2229 | semver | high | bump consumer | + # | CVE-2026-4926 | tough-cookie | high | bump consumer | + # | CVE-2026-4867 | path-to-regexp | high | overrides in main; verify | + # | CVE-2026-4800 | micromatch | high | bump consumer | + # | CVE-2026-39356 | drizzle-orm | high | major bump (separate PR) | + # | CVE-2026-42033..495 | esbuild/postcss | high | bump consumer | + # | CVE-2026-6321/6322 | tar | high | bump consumer | + # | CVE-2026-44705 | ws | high | bump consumer | - name: Enforce audit high threshold if: steps.pkg.outputs.dir != '' run: | @@ -96,11 +123,6 @@ jobs: # pnpm v10 does not support npm's per-advisory `--ignore ` flag, # so we shell out to `pnpm audit --json` and post-filter with jq. - # - # Accepted (ignored) advisories — TIME-BOUNDED tech-debt acceptance. - # Every CVE/GHSA below is a pre-existing high+ advisory in transitive - # deps that was hidden by the previously-broken `--ignore` flag. - # Tracked for removal by parallel dep-upgrade PR (see follow-up). # Both CVE and GHSA ids are listed; the jq filter matches either. IGNORED_IDS='[ "CVE-2024-45296","CVE-2026-33671","CVE-2026-33672", @@ -153,22 +175,36 @@ jobs: # JSON without aborting the step on that exit code. set +e AUDIT_JSON="$(pnpm audit --prod --json --ignore-registry-errors)" + RC=$? set -e + # Fail closed if pnpm produced no parseable audit JSON (network blip, + # registry error, warnings on stdout, partial output, etc.). Accepts + # either npm v6 / pnpm legacy (`.advisories`) or npm v7+ shape + # (`.vulnerabilities`). + if ! echo "$AUDIT_JSON" | jq -e 'has("advisories") or has("vulnerabilities")' >/dev/null 2>&1; then + echo "::error::pnpm audit produced no parseable JSON (rc=$RC). Output was:" + echo "$AUDIT_JSON" | head -c 500 + exit 1 + fi + # Filter remaining high/critical advisories after dropping accepted # CVE/GHSA ids. Matches by CVE id OR github_advisory_id so that # advisory-DB renumbering does not silently re-open an accepted item. + # Null-id guard: an advisory with no CVE AND no GHSA cannot be matched + # against the accept-list, so we fail closed on it. REMAINING="$(jq --argjson ignored "$IGNORED_IDS" ' - [ .advisories + [ ( (.advisories // .vulnerabilities) // {} ) | to_entries[] | select(.value.severity == "high" or .value.severity == "critical") | . as $a + | (([$a.value.cves[]?] + [$a.value.github_advisory_id]) + | map(select(. != null))) as $ids | select( - ( ([$a.value.cves[]?] + [$a.value.github_advisory_id]) - | map(. as $id | $ignored | index($id)) - | all(. == null) ) + ($ids | length) == 0 + or ($ids | all(. as $id | ($ignored | index($id)) == null)) ) - | {id: .key, module: .value.module_name, severity: .value.severity, cves: .value.cves, ghsa: .value.github_advisory_id, url: .value.url} + | {id: $a.key, module: $a.value.module_name, severity: $a.value.severity, cves: $a.value.cves, ghsa: $a.value.github_advisory_id, url: $a.value.url} ]' <<<"$AUDIT_JSON")" COUNT="$(jq 'length' <<<"$REMAINING")"