From 5cfbb7b6cf936b2cf90c3e995f1fa931353789e6 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 19 May 2026 14:10:49 +0000 Subject: [PATCH] ci: extend supply-chain audit + smoke jobs to new lockfiles Point the existing audit + install + content-sanity workflows at the two lockfiles PR #5604 introduced (studio/package-lock.json and studio/backend/core/data_recipe/oxc-validator/package-lock.json) so the Studio Tauri CLI holder and the oxc-validator runtime benefit from the same per-PR coverage the frontend already has. release-desktop.yml: - Tauri CLI install: `npm install --save-dev --prefix studio` -> `npm ci --prefix studio --no-fund --no-audit`. - Frontend install: `npm install --no-fund --no-audit` -> `npm ci --no-fund --no-audit`. - New "Lockfile supply-chain audit" step inserted BEFORE the Tauri CLI install so the structural audit fires before any tarball-side lifecycle script. studio-tauri-smoke.yml: - Tauri CLI install: `npm install --save-dev --prefix studio` -> `npm ci --prefix studio --no-fund --no-audit`. - Existing audit step relocated to run BEFORE the Tauri CLI install (same ordering rationale). wheel-smoke.yml: - One-line content-sanity assertion ensuring the oxc-validator package-lock.json is shipped in the wheel. security-audit.yml: - `paths` trigger gains the 4 new entries (oxc + Tauri CLI holder package.json + package-lock.json). - `Scanned:` summary string updated to list the two new lockfiles. - OSV-scanner picks up `--lockfile=` for both. - Two new `npm audit` steps: oxc-validator runtime + Studio Tauri CLI holder. - `npm audit signatures` block split into three (frontend / oxc / Tauri CLI), each preceded by `npm ci --ignore-scripts` in its own directory. - `check_new_install_scripts.py` invocation extended to diff both new lockfiles base->head, with `git show ... 2>/dev/null || echo '{}'` fallback for PRs whose base predates #5604. - `upload-artifact` paths extended for the new log files. Deliberately NOT included: removing `continue-on-error` from the existing `scan_npm_packages` job. That is a policy flip from advisory to blocking and is orthogonal to install-path hardening. --- .github/workflows/release-desktop.yml | 33 +++--- .github/workflows/security-audit.yml | 125 +++++++++++++++++++++-- .github/workflows/studio-tauri-smoke.yml | 22 ++-- .github/workflows/wheel-smoke.yml | 1 + 4 files changed, 150 insertions(+), 31 deletions(-) diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 810bb644ba..78d9929f18 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -361,14 +361,21 @@ jobs: with: node-version: 24 + - name: Lockfile supply-chain audit (pre-install scan) + shell: bash + # Runs BEFORE any `npm ci` so the structural audit + # (lockfile_supply_chain_audit.py) catches an injection-pattern + # lockfile before any tarball-side lifecycle script runs. The + # `npm ci` invocations below consume committed lockfiles + # (shipped in #5604) so they refuse to deviate from the + # audited shape. + run: python3 scripts/lockfile_supply_chain_audit.py + - name: Install pinned Tauri CLI - # Lifecycle scripts (esbuild native-binary postinstall, etc.) are - # required for `vite build`. The pre-install lockfile structural - # audit (lockfile_supply_chain_audit.py) is the practical defence - # against the npm postinstall-dropper class -- it fires BEFORE any - # tarball runs, on the injection pattern itself rather than an - # advisory-DB lookup. - run: npm install --save-dev --prefix studio @tauri-apps/cli@2.10.1 --no-fund --no-audit + # `npm ci --prefix studio` consumes the committed + # studio/package-lock.json (shipped in #5604) and refuses to + # install if the resolved tree drifts from that lockfile. + run: npm ci --prefix studio --no-fund --no-audit - name: Verify pinned Tauri CLI shell: bash @@ -443,13 +450,11 @@ jobs: - name: Install frontend dependencies working-directory: studio/frontend - # Lifecycle scripts (esbuild native-binary postinstall, etc.) are - # required for `vite build`. The pre-install lockfile structural - # audit (lockfile_supply_chain_audit.py) is the practical defence - # against the npm postinstall-dropper class -- it fires BEFORE any - # tarball runs, on the injection pattern itself rather than an - # advisory-DB lookup. - run: npm install --no-fund --no-audit + # `npm ci` consumes the committed studio/frontend/package-lock.json + # and refuses to install if the resolved tree drifts from that + # lockfile. The pre-install audit step above already verified the + # lockfile shape before any lifecycle script runs. + run: npm ci --no-fund --no-audit # ── Rust ── - name: Install Rust stable diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index a1e7b2efa6..9a6a0b5255 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -53,6 +53,10 @@ on: - 'studio/backend/requirements/**' - 'studio/frontend/package.json' - 'studio/frontend/package-lock.json' + - 'studio/backend/core/data_recipe/oxc-validator/package.json' + - 'studio/backend/core/data_recipe/oxc-validator/package-lock.json' + - 'studio/package.json' + - 'studio/package-lock.json' - 'studio/src-tauri/Cargo.toml' - 'studio/src-tauri/Cargo.lock' - 'pyproject.toml' @@ -278,7 +282,7 @@ jobs: { echo "## Lockfile supply-chain audit" echo - echo "Scanned: studio/frontend/package-lock.json + studio/src-tauri/Cargo.lock" + echo "Scanned: studio/frontend/package-lock.json + studio/backend/core/data_recipe/oxc-validator/package-lock.json + studio/package-lock.json + studio/src-tauri/Cargo.lock" echo echo "No structural anomalies or known IOC strings." } >> "$GITHUB_STEP_SUMMARY" @@ -307,6 +311,42 @@ jobs: echo '```' } >> "$GITHUB_STEP_SUMMARY" + # ───────────────────────────────────────────────────────────── + # npm: oxc-validator runtime (data_recipe parser sandbox) + # ───────────────────────────────────────────────────────────── + - name: npm audit (oxc-validator runtime) + continue-on-error: true + working-directory: studio/backend/core/data_recipe/oxc-validator + run: | + set +e + npm audit --audit-level=high | tee ../../../../../logs-npm-audit-oxc.txt + npm audit --json > ../../../../../logs-npm-audit-oxc.json || true + { + echo "## npm audit (oxc-validator runtime)" + echo + echo '```' + tail -200 ../../../../../logs-npm-audit-oxc.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + # ───────────────────────────────────────────────────────────── + # npm: Studio Tauri CLI holder + # ───────────────────────────────────────────────────────────── + - name: npm audit (Studio Tauri CLI holder) + continue-on-error: true + working-directory: studio + run: | + set +e + npm audit --audit-level=high | tee ../logs-npm-audit-tauri-cli.txt + npm audit --json > ../logs-npm-audit-tauri-cli.json || true + { + echo "## npm audit (Studio Tauri CLI holder)" + echo + echo '```' + tail -200 ../logs-npm-audit-tauri-cli.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + # ───────────────────────────────────────────────────────────── # cargo: Studio Tauri shell # ───────────────────────────────────────────────────────────── @@ -348,6 +388,8 @@ jobs: /tmp/osv-scanner --version /tmp/osv-scanner scan source \ --lockfile=studio/frontend/package-lock.json \ + --lockfile=studio/backend/core/data_recipe/oxc-validator/package-lock.json \ + --lockfile=studio/package-lock.json \ --lockfile=studio/src-tauri/Cargo.lock \ --lockfile=requirements.txt:audit-reqs/unsloth-deps.txt \ --lockfile=requirements.txt:audit-reqs/studio.txt \ @@ -639,6 +681,10 @@ jobs: logs-pip-audit.txt logs-npm-audit.txt logs-npm-audit.json + logs-npm-audit-oxc.txt + logs-npm-audit-oxc.json + logs-npm-audit-tauri-cli.txt + logs-npm-audit-tauri-cli.json logs-cargo-audit.txt logs-osv-scanner.txt logs-semgrep.txt @@ -1077,7 +1123,15 @@ jobs: working-directory: studio/frontend run: npm ci --ignore-scripts - - name: npm audit signatures (informational) + - name: Install oxc-validator deps (--ignore-scripts) + working-directory: studio/backend/core/data_recipe/oxc-validator + run: npm ci --ignore-scripts + + - name: Install Studio Tauri CLI holder deps (--ignore-scripts) + working-directory: studio + run: npm ci --ignore-scripts + + - name: npm audit signatures (Studio frontend, informational) # Surfaces unsigned / mis-signed packages from the npm # transparency log. continue-on-error during baseline-build # phase; promote to hard gate once the lockfile is fully @@ -1089,7 +1143,37 @@ jobs: LOG=logs-audit-signatures.txt npm audit signatures 2>&1 | tee "$LOG" { - echo "## npm audit signatures" + echo "## npm audit signatures (Studio frontend)" + echo + echo '```' + tail -200 "$LOG" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: npm audit signatures (oxc-validator, informational) + working-directory: studio/backend/core/data_recipe/oxc-validator + continue-on-error: true + run: | + set -o pipefail + LOG=logs-audit-signatures-oxc.txt + npm audit signatures 2>&1 | tee "$LOG" + { + echo "## npm audit signatures (oxc-validator)" + echo + echo '```' + tail -200 "$LOG" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: npm audit signatures (Studio Tauri CLI holder, informational) + working-directory: studio + continue-on-error: true + run: | + set -o pipefail + LOG=logs-audit-signatures-tauri-cli.txt + npm audit signatures 2>&1 | tee "$LOG" + { + echo "## npm audit signatures (Studio Tauri CLI holder)" echo echo '```' tail -200 "$LOG" @@ -1101,16 +1185,40 @@ jobs: run: | set -e BASE_SHA="${{ github.event.pull_request.base.sha }}" - git show "$BASE_SHA:studio/frontend/package-lock.json" \ - > /tmp/base-package-lock.json + # `|| echo '{}'` fallback covers PRs whose base predates the + # new lockfiles (they only landed in #5604); without it the + # diff step would crash trying to read a non-existent blob. + git show "$BASE_SHA:studio/frontend/package-lock.json" 2>/dev/null \ + > /tmp/base-package-lock.json \ + || echo '{}' > /tmp/base-package-lock.json + git show "$BASE_SHA:studio/backend/core/data_recipe/oxc-validator/package-lock.json" 2>/dev/null \ + > /tmp/base-package-lock-oxc.json \ + || echo '{}' > /tmp/base-package-lock-oxc.json + git show "$BASE_SHA:studio/package-lock.json" 2>/dev/null \ + > /tmp/base-package-lock-tauri-cli.json \ + || echo '{}' > /tmp/base-package-lock-tauri-cli.json - - name: Diff for newly-added install-script deps + - name: Diff for newly-added install-script deps (Studio frontend) if: github.event_name == 'pull_request' run: | python3 scripts/check_new_install_scripts.py \ --base /tmp/base-package-lock.json \ --head studio/frontend/package-lock.json + - name: Diff for newly-added install-script deps (oxc-validator) + if: github.event_name == 'pull_request' + run: | + python3 scripts/check_new_install_scripts.py \ + --base /tmp/base-package-lock-oxc.json \ + --head studio/backend/core/data_recipe/oxc-validator/package-lock.json + + - name: Diff for newly-added install-script deps (Studio Tauri CLI holder) + if: github.event_name == 'pull_request' + run: | + python3 scripts/check_new_install_scripts.py \ + --base /tmp/base-package-lock-tauri-cli.json \ + --head studio/package-lock.json + - name: Skip install-script diff (non-PR trigger) if: github.event_name != 'pull_request' run: | @@ -1121,6 +1229,9 @@ jobs: if: always() with: name: npm-audit-signatures-log - path: studio/frontend/logs-audit-signatures.txt + path: | + studio/frontend/logs-audit-signatures.txt + studio/backend/core/data_recipe/oxc-validator/logs-audit-signatures-oxc.txt + studio/logs-audit-signatures-tauri-cli.txt if-no-files-found: ignore retention-days: 30 diff --git a/.github/workflows/studio-tauri-smoke.yml b/.github/workflows/studio-tauri-smoke.yml index 1156c264ae..da6d05a6b6 100644 --- a/.github/workflows/studio-tauri-smoke.yml +++ b/.github/workflows/studio-tauri-smoke.yml @@ -60,14 +60,19 @@ jobs: with: workspaces: studio/src-tauri -> target + - name: Lockfile supply-chain audit (pre-install scan) + # Runs BEFORE any npm install / npm ci so the structural audit + # (lockfile_supply_chain_audit.py) catches an injection-pattern + # lockfile before a single postinstall script is executed. + run: python3 scripts/lockfile_supply_chain_audit.py + - name: Install pinned Tauri CLI (matches release-desktop.yml) - # Lifecycle scripts (esbuild native-binary postinstall, etc.) are - # required for `vite build`. The pre-install lockfile structural - # audit (lockfile_supply_chain_audit.py) is the practical defence - # against the npm postinstall-dropper class -- it fires BEFORE any - # tarball runs, on the injection pattern itself rather than an - # advisory-DB lookup. - run: npm install --save-dev --prefix studio @tauri-apps/cli@2.10.1 --no-fund --no-audit + # `npm ci --prefix studio` consumes the committed + # studio/package-lock.json (shipped in #5604) and refuses to + # install if the resolved tree drifts from that lockfile. The + # pre-install audit above already verified the lockfile shape + # itself before any tarball-side lifecycle script runs. + run: npm ci --prefix studio --no-fund --no-audit - name: Verify pinned Tauri CLI version run: | @@ -75,9 +80,6 @@ jobs: echo "$out" [ "$out" = "tauri-cli 2.10.1" ] || { echo "::error::expected tauri-cli 2.10.1, got $out"; exit 1; } - - name: Lockfile supply-chain audit (pre-install scan) - run: python3 scripts/lockfile_supply_chain_audit.py - - name: Frontend build (npm ci, vite) working-directory: studio/frontend # Lifecycle scripts (esbuild native-binary postinstall, etc.) are diff --git a/.github/workflows/wheel-smoke.yml b/.github/workflows/wheel-smoke.yml index 3de3c33ca2..1ebd09da72 100644 --- a/.github/workflows/wheel-smoke.yml +++ b/.github/workflows/wheel-smoke.yml @@ -87,6 +87,7 @@ jobs: n = z.namelist() checks = { "lockfile shipped": any(s.endswith("studio/frontend/package-lock.json") for s in n), + "oxc lockfile shipped": any(s.endswith("oxc-validator/package-lock.json") for s in n), "frontend dist shipped": any(s.endswith("studio/frontend/dist/index.html") for s in n), "no node_modules": not any("studio/frontend/node_modules/" in s for s in n), "no bun.lock": not any(s.endswith("studio/frontend/bun.lock") for s in n),