From f4258ad1f3089543dd788f272302f13ed2319cf1 Mon Sep 17 00:00:00 2001 From: Arina Neshlyaeva Date: Wed, 7 Oct 2026 13:39:27 -0700 Subject: [PATCH 1/4] Add scheduled npm audit fix workflow Runs weekly (and on demand) in client-vscode, applies in-range fixes via npm audit fix, verifies install and compile, and opens a PR for vulnerabilities Dependabot did not update. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/npm-audit-fix.yml | 108 ++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 .github/workflows/npm-audit-fix.yml diff --git a/.github/workflows/npm-audit-fix.yml b/.github/workflows/npm-audit-fix.yml new file mode 100644 index 0000000..1e6a08b --- /dev/null +++ b/.github/workflows/npm-audit-fix.yml @@ -0,0 +1,108 @@ +name: npm audit fix + +on: + schedule: + - cron: '0 6 * * 2' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: npm-audit-fix + cancel-in-progress: true + +jobs: + audit-fix: + runs-on: ubuntu-latest + defaults: + run: + working-directory: client-vscode + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: client-vscode/package-lock.json + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Audit before fix + id: before + run: | + npm audit --json > "$RUNNER_TEMP/audit-before.json" || true + echo "count=$(jq '.metadata.vulnerabilities.total // 0' "$RUNNER_TEMP/audit-before.json")" >> "$GITHUB_OUTPUT" + + # Without --force: only in-range updates, so no breaking major bumps of direct deps. + - name: Apply npm audit fix + if: steps.before.outputs.count != '0' + run: npm audit fix --ignore-scripts || true + + - name: Detect lockfile changes + id: diff + run: | + if git diff --quiet -- package.json package-lock.json; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Verify fixed tree installs and compiles + if: steps.diff.outputs.changed == 'true' + run: | + npm ci --ignore-scripts + npm run compile + + - name: Build PR body + if: steps.diff.outputs.changed == 'true' + run: | + npm audit --json > "$RUNNER_TEMP/audit-after.json" || true + { + echo "Automated \`npm audit fix\` for vulnerable dependencies that Dependabot did not update." + echo + echo "### Fixed" + jq -r --slurpfile after "$RUNNER_TEMP/audit-after.json" ' + .vulnerabilities | to_entries[] + | select(.key as $k | ($after[0].vulnerabilities | has($k)) | not) + | "- `\(.key)` (\(.value.severity)): \([.value.via[] | objects | .title] | unique | join("; "))" + ' "$RUNNER_TEMP/audit-before.json" + echo + echo "### Still vulnerable (needs a major bump or \`overrides\`)" + jq -r ' + .vulnerabilities | to_entries[] + | "- `\(.key)` (\(.value.severity)) via \([.value.via[] | if type == "string" then . else .name end] | unique | join(", "))" + ' "$RUNNER_TEMP/audit-after.json" | grep . || echo "_None_" + echo + echo "### Changes" + echo '```' + git diff --stat -- package.json package-lock.json + echo '```' + } > "$RUNNER_TEMP/pr-body.md" + cat "$RUNNER_TEMP/pr-body.md" >> "$GITHUB_STEP_SUMMARY" + + - name: Create pull request + if: steps.diff.outputs.changed == 'true' + uses: peter-evans/create-pull-request@v7 + with: + branch: automation/npm-audit-fix + delete-branch: true + add-paths: | + client-vscode/package.json + client-vscode/package-lock.json + commit-message: Fix vulnerable npm dependencies via npm audit fix + title: Fix vulnerable npm dependencies (npm audit fix) + body-path: ${{ runner.temp }}/pr-body.md + labels: dependencies, security + + - name: Report unfixable vulnerabilities + if: steps.diff.outputs.changed == 'false' && steps.before.outputs.count != '0' + run: | + echo "### npm audit: vulnerabilities with no in-range fix" >> "$GITHUB_STEP_SUMMARY" + npm audit >> "$GITHUB_STEP_SUMMARY" || true From f9321ff28a33e16a019811722a41ec89535e5038 Mon Sep 17 00:00:00 2001 From: Arina Neshlyaeva Date: Wed, 7 Oct 2026 14:12:17 -0700 Subject: [PATCH 2/4] Harden npm audit fix workflow - Fail on invalid or errored npm audit reports instead of skipping - Guard jq against missing data; fall back to 'via' for transitive entries - Open a PR only when the vulnerability count drops - Split into a read-only audit/verify job and a PR job that runs no project code; no persisted checkout credentials - Create the PR with AUDIT_FIX_TOKEN so build.yml runs on it - Surface npm audit fix errors, queue runs instead of cancelling, fence audit output in the step summary Co-Authored-By: Claude Opus 5.5 --- .github/workflows/npm-audit-fix.yml | 128 +++++++++++++++++++++------- 1 file changed, 98 insertions(+), 30 deletions(-) diff --git a/.github/workflows/npm-audit-fix.yml b/.github/workflows/npm-audit-fix.yml index 1e6a08b..2b8ab79 100644 --- a/.github/workflows/npm-audit-fix.yml +++ b/.github/workflows/npm-audit-fix.yml @@ -6,16 +6,18 @@ on: workflow_dispatch: permissions: - contents: write - pull-requests: write + contents: read concurrency: group: npm-audit-fix - cancel-in-progress: true + cancel-in-progress: false jobs: + # Runs updated third-party code (npm ci, tsc), so it gets no write access and no persisted credentials. audit-fix: runs-on: ubuntu-latest + outputs: + fixed: ${{ steps.after.outputs.fixed }} defaults: run: working-directory: client-vscode @@ -23,6 +25,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v4 @@ -37,46 +41,76 @@ jobs: - name: Audit before fix id: before run: | - npm audit --json > "$RUNNER_TEMP/audit-before.json" || true - echo "count=$(jq '.metadata.vulnerabilities.total // 0' "$RUNNER_TEMP/audit-before.json")" >> "$GITHUB_OUTPUT" + f="$RUNNER_TEMP/audit-before.json" + npm audit --json > "$f" || true + if ! jq -e '.metadata.vulnerabilities.total | numbers' "$f" > /dev/null 2>&1; then + echo "::error::npm audit did not return a valid report" + cat "$f" + exit 1 + fi + echo "count=$(jq '.metadata.vulnerabilities.total' "$f")" >> "$GITHUB_OUTPUT" # Without --force: only in-range updates, so no breaking major bumps of direct deps. + # Exits non-zero whenever vulnerabilities remain, so only real npm errors are surfaced. - name: Apply npm audit fix if: steps.before.outputs.count != '0' - run: npm audit fix --ignore-scripts || true + run: | + log="$RUNNER_TEMP/audit-fix.log" + if ! npm audit fix --ignore-scripts > "$log" 2>&1; then + if grep -qE '^npm (ERR!|error)' "$log"; then + echo "::warning::npm audit fix reported errors; see log below" + fi + fi + cat "$log" - - name: Detect lockfile changes - id: diff + - name: Audit after fix + id: after + if: steps.before.outputs.count != '0' + env: + BEFORE: ${{ steps.before.outputs.count }} run: | - if git diff --quiet -- package.json package-lock.json; then - echo "changed=false" >> "$GITHUB_OUTPUT" + f="$RUNNER_TEMP/audit-after.json" + npm audit --json > "$f" || true + if ! jq -e '.metadata.vulnerabilities.total | numbers' "$f" > /dev/null 2>&1; then + echo "::error::npm audit did not return a valid report after fix" + cat "$f" + exit 1 + fi + after=$(jq '.metadata.vulnerabilities.total' "$f") + if [ "$after" -lt "$BEFORE" ] && ! git diff --quiet -- package.json package-lock.json; then + echo "fixed=true" >> "$GITHUB_OUTPUT" else - echo "changed=true" >> "$GITHUB_OUTPUT" + echo "fixed=false" >> "$GITHUB_OUTPUT" fi - name: Verify fixed tree installs and compiles - if: steps.diff.outputs.changed == 'true' + if: steps.after.outputs.fixed == 'true' run: | npm ci --ignore-scripts npm run compile - - name: Build PR body - if: steps.diff.outputs.changed == 'true' + - name: Build PR body and patch + if: steps.after.outputs.fixed == 'true' run: | - npm audit --json > "$RUNNER_TEMP/audit-after.json" || true + out="$RUNNER_TEMP/audit-fix-out" + mkdir -p "$out" + git diff -- package.json package-lock.json > "$out/fix.patch" { echo "Automated \`npm audit fix\` for vulnerable dependencies that Dependabot did not update." echo echo "### Fixed" jq -r --slurpfile after "$RUNNER_TEMP/audit-after.json" ' - .vulnerabilities | to_entries[] - | select(.key as $k | ($after[0].vulnerabilities | has($k)) | not) - | "- `\(.key)` (\(.value.severity)): \([.value.via[] | objects | .title] | unique | join("; "))" - ' "$RUNNER_TEMP/audit-before.json" + ($after[0].vulnerabilities // {}) as $a + | (.vulnerabilities // {}) | to_entries[] + | select(.key as $k | $a | has($k) | not) + | ([.value.via[] | objects | .title] | unique | join("; ")) as $t + | "- `\(.key)` (\(.value.severity)): " + + (if $t == "" then "via " + ([.value.via[] | strings] | unique | join(", ")) else $t end) + ' "$RUNNER_TEMP/audit-before.json" | grep . || echo "_None_" echo echo "### Still vulnerable (needs a major bump or \`overrides\`)" jq -r ' - .vulnerabilities | to_entries[] + (.vulnerabilities // {}) | to_entries[] | "- `\(.key)` (\(.value.severity)) via \([.value.via[] | if type == "string" then . else .name end] | unique | join(", "))" ' "$RUNNER_TEMP/audit-after.json" | grep . || echo "_None_" echo @@ -84,13 +118,53 @@ jobs: echo '```' git diff --stat -- package.json package-lock.json echo '```' - } > "$RUNNER_TEMP/pr-body.md" - cat "$RUNNER_TEMP/pr-body.md" >> "$GITHUB_STEP_SUMMARY" + } > "$out/pr-body.md" + cat "$out/pr-body.md" >> "$GITHUB_STEP_SUMMARY" + + - name: Upload fix + if: steps.after.outputs.fixed == 'true' + uses: actions/upload-artifact@v4 + with: + name: audit-fix + path: ${{ runner.temp }}/audit-fix-out/ + retention-days: 1 + + - name: Report unfixable vulnerabilities + if: steps.before.outputs.count != '0' && steps.after.outputs.fixed != 'true' + run: | + { + echo "### npm audit: vulnerabilities with no in-range fix" + echo '```' + npm audit || true + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + # Never runs project code; only applies the verified patch and opens the PR. + # AUDIT_FIX_TOKEN (not GITHUB_TOKEN) so the PR triggers build.yml. + create-pr: + needs: audit-fix + if: needs.audit-fix.outputs.fixed == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Download fix + uses: actions/download-artifact@v4 + with: + name: audit-fix + path: ${{ runner.temp }}/audit-fix + + - name: Apply patch + run: git apply "$RUNNER_TEMP/audit-fix/fix.patch" - name: Create pull request - if: steps.diff.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: + token: ${{ secrets.AUDIT_FIX_TOKEN }} branch: automation/npm-audit-fix delete-branch: true add-paths: | @@ -98,11 +172,5 @@ jobs: client-vscode/package-lock.json commit-message: Fix vulnerable npm dependencies via npm audit fix title: Fix vulnerable npm dependencies (npm audit fix) - body-path: ${{ runner.temp }}/pr-body.md + body-path: ${{ runner.temp }}/audit-fix/pr-body.md labels: dependencies, security - - - name: Report unfixable vulnerabilities - if: steps.diff.outputs.changed == 'false' && steps.before.outputs.count != '0' - run: | - echo "### npm audit: vulnerabilities with no in-range fix" >> "$GITHUB_STEP_SUMMARY" - npm audit >> "$GITHUB_STEP_SUMMARY" || true From 56f0aa8db16566c1d35c63bed19ca7a9efa5f88a Mon Sep 17 00:00:00 2001 From: Arina Neshlyaeva Date: Wed, 7 Oct 2026 14:12:18 -0700 Subject: [PATCH 3/4] Build with Node 22, required by @vscode/vsce 4.x Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9b4630..b08dbfd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' cache: 'npm' cache-dependency-path: client-vscode/package-lock.json From 1a647c583460be75a831e2330b3c0dd9c1c866b0 Mon Sep 17 00:00:00 2001 From: aneshlya <2833813+aneshlya@users.noreply.github.com> Date: Wed, 7 Oct 2026 21:29:45 +0000 Subject: [PATCH 4/4] Fix vulnerable npm dependencies via npm audit fix --- client-vscode/package-lock.json | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/client-vscode/package-lock.json b/client-vscode/package-lock.json index 815fe98..3cc2987 100644 --- a/client-vscode/package-lock.json +++ b/client-vscode/package-lock.json @@ -304,9 +304,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -324,9 +321,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -344,9 +338,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -364,9 +355,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -384,9 +372,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1859,10 +1844,11 @@ } }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1"