ci: extend lockfile-strict surface to Tauri smoke + audit jobs

Brings the parallel CI paths into line with the lockfile-pinned
release path and tightens the supply-chain audit surface:

studio-tauri-smoke.yml: run lockfile_supply_chain_audit.py before
the Tauri CLI install, and install via `npm ci --prefix studio`
against the committed studio/package-lock.json (was a mutable
`npm install --save-dev` post-audit). This relocates the existing
pre-install lockfile supply-chain audit step; the step's name and
command are preserved verbatim so its purpose is unchanged, only
its position relative to the install. The earlier security
rationale about lifecycle scripts and the postinstall-dropper
class is preserved on the Frontend build step where it actually
applies (vite/esbuild lifecycle scripts run on the frontend
install); the Tauri CLI install step gets a new rationale tied to
`npm ci` semantics.

security-audit.yml:
  * add studio/package.json and studio/package-lock.json to the PR
    path filter so a Tauri CLI lockfile change cannot bypass the
    workflow,
  * extend OSV-Scanner, scan_npm_packages.py (with LOG3 and exit-
    code propagation), and the install-script diff to cover
    studio/package-lock.json,
  * add an npm audit step for the Tauri CLI holder project,
  * extend the npm-provenance-and-install-scripts job with
    --ignore-scripts installs + npm audit signatures for the
    oxc-validator and Tauri CLI holder projects; the existing
    frontend audit-signatures step is renamed to "(Studio
    frontend, informational)" purely for disambiguation against
    the two new sibling steps, with its log path rerouted through
    $GITHUB_WORKSPACE so a single artifact upload can collect all
    three logs,
  * update the lockfile-audit step summary to list the Tauri CLI
    holder lockfile,
  * fix the stale "Initially non-blocking" comment on the now-
    blocking npm scan-packages step.

build.sh and studio/setup.ps1 (oxc): pass --no-fund --no-audit to
npm ci for parity with the other call sites.

studio/setup.sh and studio/setup.ps1: restore the bun.lock
exclusion in the frontend staleness check so a leftover local
bun.lock from the migration does not trigger a spurious rebuild.

scripts/lockfile_supply_chain_audit.py: emit a HIGH-severity
missing-lockfile Finding when a requested lockfile does not
exist, so a deleted default cannot silently pass the audit. Uses
the script's own Finding accumulator pattern (sibling
scripts/scan_npm_packages.py implements the same intent via an
rc=2 hard-fail, its single-lockfile-per-invocation idiom; this
script aggregates multiple lockfiles so Finding is the natural
channel).

scripts/check_frontend_dep_removal.py: add studio/package.json
and studio/package-lock.json to EXPECTED_NOISE_FILES; the new
Tauri CLI holder manifests must not count as frontend dep usage.
This commit is contained in:
Daniel Han 2026-05-16 13:48:39 +00:00
commit b743e1b4ff
7 changed files with 147 additions and 30 deletions

View file

@ -55,6 +55,8 @@ on:
- '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'
@ -280,7 +282,7 @@ jobs:
{
echo "## Lockfile supply-chain audit"
echo
echo "Scanned: studio/frontend/package-lock.json + studio/backend/core/data_recipe/oxc-validator/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"
@ -325,6 +327,23 @@ jobs:
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: npm audit (Studio Tauri CLI holder)
# Same audit surface, third npm project (@tauri-apps/cli for the
# signed desktop release; lockfile lives at studio/package-lock.json).
continue-on-error: true
working-directory: studio
run: |
set +e
npm audit --audit-level=high | tee "$GITHUB_WORKSPACE/logs-npm-audit-studio.txt"
npm audit --json > "$GITHUB_WORKSPACE/logs-npm-audit-studio.json" || true
{
echo "## npm audit (Studio Tauri CLI holder)"
echo
echo '```'
tail -200 "$GITHUB_WORKSPACE/logs-npm-audit-studio.txt"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
# ─────────────────────────────────────────────────────────────
# cargo: Studio Tauri shell
# ─────────────────────────────────────────────────────────────
@ -367,6 +386,7 @@ jobs:
/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 \
@ -930,16 +950,15 @@ jobs:
# full log and surface it in the step summary either way. It
# never runs `npm install`, never executes anything from a
# downloaded tarball, and only fetches from registry.npmjs.org.
# Initially non-blocking so the baseline can settle; drop
# continue-on-error once the baseline is clean for a week.
# This step is blocking: the final exit code is the worst rc
# across all three npm projects (no continue-on-error).
#
# Two separate npm projects share this scan surface; scan each.
# Capture exit codes via PIPESTATUS so a HIGH/CRITICAL on the
# frontend lockfile does not skip the OXC scan (both reports
# are most useful exactly when one already failed).
# Three separate npm projects share this scan surface; scan each.
# Capture exit codes via PIPESTATUS so a HIGH/CRITICAL on one
# lockfile does not skip the next scan (all reports are most
# useful exactly when one already failed).
run: |
set +e
set -o pipefail
LOG=logs-scan-npm.txt
python3 scripts/scan_npm_packages.py 2>&1 | tee "$LOG"
@ -951,6 +970,12 @@ jobs:
2>&1 | tee "$LOG2"
oxc_rc=${PIPESTATUS[0]}
LOG3=logs-scan-npm-studio.txt
python3 scripts/scan_npm_packages.py \
--lockfile studio/package-lock.json \
2>&1 | tee "$LOG3"
studio_rc=${PIPESTATUS[0]}
{
echo "## scan_npm_packages (Studio frontend)"
echo
@ -965,12 +990,22 @@ jobs:
echo '```'
tail -300 "$LOG2"
echo '```'
echo
echo "## scan_npm_packages (Studio Tauri CLI holder)"
echo
echo '### Findings (tail)'
echo '```'
tail -300 "$LOG3"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
if [ "$frontend_rc" -ne 0 ]; then
exit "$frontend_rc"
fi
exit "$oxc_rc"
if [ "$oxc_rc" -ne 0 ]; then
exit "$oxc_rc"
fi
exit "$studio_rc"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
@ -979,6 +1014,7 @@ jobs:
path: |
logs-scan-npm.txt
logs-scan-npm-oxc.txt
logs-scan-npm-studio.txt
retention-days: 30
# ─────────────────────────────────────────────────────────────────────
@ -1125,7 +1161,7 @@ jobs:
working-directory: studio/frontend
run: npm ci --ignore-scripts
- name: npm audit signatures (informational)
- 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
@ -1134,10 +1170,48 @@ jobs:
continue-on-error: true
run: |
set -o pipefail
LOG=logs-audit-signatures.txt
LOG="$GITHUB_WORKSPACE/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: Install oxc-validator deps (--ignore-scripts)
working-directory: studio/backend/core/data_recipe/oxc-validator
run: npm ci --ignore-scripts
- 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="$GITHUB_WORKSPACE/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: Install Studio Tauri CLI holder deps (--ignore-scripts)
working-directory: studio
run: npm ci --ignore-scripts
- name: npm audit signatures (Studio Tauri CLI holder, informational)
working-directory: studio
continue-on-error: true
run: |
set -o pipefail
LOG="$GITHUB_WORKSPACE/logs-audit-signatures-studio.txt"
npm audit signatures 2>&1 | tee "$LOG"
{
echo "## npm audit signatures (Studio Tauri CLI holder)"
echo
echo '```'
tail -200 "$LOG"
@ -1154,6 +1228,9 @@ jobs:
# OXC lockfile may not exist on the base ref (newly added).
git show "$BASE_SHA:studio/backend/core/data_recipe/oxc-validator/package-lock.json" \
> /tmp/base-oxc-package-lock.json 2>/dev/null || echo '{}' > /tmp/base-oxc-package-lock.json
# Tauri CLI holder lockfile may not exist on the base ref (newly added).
git show "$BASE_SHA:studio/package-lock.json" \
> /tmp/base-studio-package-lock.json 2>/dev/null || echo '{}' > /tmp/base-studio-package-lock.json
- name: Diff for newly-added install-script deps
if: github.event_name == 'pull_request'
@ -1164,6 +1241,9 @@ jobs:
python3 scripts/check_new_install_scripts.py \
--base /tmp/base-oxc-package-lock.json \
--head studio/backend/core/data_recipe/oxc-validator/package-lock.json
python3 scripts/check_new_install_scripts.py \
--base /tmp/base-studio-package-lock.json \
--head studio/package-lock.json
- name: Skip install-script diff (non-PR trigger)
if: github.event_name != 'pull_request'
@ -1175,6 +1255,9 @@ jobs:
if: always()
with:
name: npm-audit-signatures-log
path: studio/frontend/logs-audit-signatures.txt
path: |
logs-audit-signatures.txt
logs-audit-signatures-oxc.txt
logs-audit-signatures-studio.txt
if-no-files-found: ignore
retention-days: 30

View file

@ -60,14 +60,18 @@ jobs:
with:
workspaces: studio/src-tauri -> target
# Pre-install lockfile structural audit. Has to fire BEFORE
# any `npm ci` so a tarball's `prepare` / `postinstall` cannot
# run ahead of the scan. Pure-Python read-only; safe everywhere.
- name: Lockfile supply-chain audit (pre-install scan)
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` resolves @tauri-apps/cli and its platform-specific
# optional native binaries from studio/package-lock.json --
# transitive versions are fully pinned, integrity hashes are
# verified, and the install is reproducible across re-runs.
run: npm ci --prefix studio --no-fund --no-audit
- name: Verify pinned Tauri CLI version
run: |
@ -75,17 +79,14 @@ 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
# 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.
# audit (lockfile_supply_chain_audit.py) above 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 ci --no-fund --no-audit
npm run build