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.
This commit is contained in:
Daniel Han 2026-05-19 14:10:49 +00:00
commit 5cfbb7b6cf
4 changed files with 150 additions and 31 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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),