diff --git a/.github/workflows/studio-tauri-smoke.yml b/.github/workflows/studio-tauri-smoke.yml index 85e1cc97c1..cc7d9006b2 100644 --- a/.github/workflows/studio-tauri-smoke.yml +++ b/.github/workflows/studio-tauri-smoke.yml @@ -60,17 +60,14 @@ 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. + # Must run BEFORE any `npm ci` so a tarball's prepare/postinstall + # cannot execute ahead of the structural scan. - 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) - # `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. + # `npm ci` resolves @tauri-apps/cli from studio/package-lock.json + # (transitives fully pinned, integrity hashes verified). run: npm ci --prefix studio --no-fund --no-audit - name: Verify pinned Tauri CLI version @@ -81,12 +78,9 @@ jobs: - 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) 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. + # Vite build needs esbuild's native-binary postinstall; the + # pre-install lockfile audit above is what gates that path + # against the npm postinstall-dropper class. run: | npm ci --no-fund --no-audit npm run build diff --git a/scripts/lockfile_supply_chain_audit.py b/scripts/lockfile_supply_chain_audit.py index c581b8b311..288038bc72 100644 --- a/scripts/lockfile_supply_chain_audit.py +++ b/scripts/lockfile_supply_chain_audit.py @@ -397,12 +397,8 @@ class Finding: def audit_npm_lockfile(path: Path) -> list[Finding]: findings: list[Finding] = [] if not path.exists(): - # A missing lockfile is a configuration error, not a clean bill - # of health: a default lockfile that quietly disappears (e.g. - # the install path was reverted to `npm install` without - # updating DEFAULT_NPM_LOCKFILES) would otherwise let this - # audit print `0 findings` while `npm ci` later fails. Fail - # loudly so the missing path surfaces as a finding. + # A missing requested lockfile is a config error, not a clean + # audit; surface it so a deleted default cannot pass silently. findings.append( Finding( path = str(path), @@ -570,8 +566,7 @@ _PACKAGE_HEADER = re.compile(r"^\[\[package\]\]\s*$") def audit_cargo_lockfile(path: Path) -> list[Finding]: findings: list[Finding] = [] if not path.exists(): - # See audit_npm_lockfile: refuse to silently treat a missing - # default lockfile as a clean scan. + # See audit_npm_lockfile: missing lockfile is a finding. findings.append( Finding( path = str(path), @@ -751,11 +746,8 @@ def main(argv: list[str] | None = None) -> int: return 0 root = Path(args.root).resolve() - # When the user passes an explicit `--npm-lockfile` or - # `--cargo-lockfile` they are scoping the scan to exactly those - # paths; do NOT silently graft the other ecosystem's defaults on - # top. Falling back to defaults is reserved for the no-args CI - # invocation, where every default path must exist. + # Explicit --npm-lockfile/--cargo-lockfile scopes the scan to those + # paths; defaults apply only to the no-args CI invocation. _user_explicit = args.npm_lockfile is not None or args.cargo_lockfile is not None if _user_explicit: npm_paths = [root / p for p in (args.npm_lockfile or ())]