diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index a1e7b2efa6..3f9a3610bb 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -53,6 +53,8 @@ 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/src-tauri/Cargo.toml' - 'studio/src-tauri/Cargo.lock' - 'pyproject.toml' @@ -278,7 +280,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/src-tauri/Cargo.lock" echo echo "No structural anomalies or known IOC strings." } >> "$GITHUB_STEP_SUMMARY" @@ -307,6 +309,22 @@ jobs: echo '```' } >> "$GITHUB_STEP_SUMMARY" + - name: npm audit (oxc-validator runtime) + # Same audit surface, separate npm project (oxc-parser, oxlint). + continue-on-error: true + working-directory: studio/backend/core/data_recipe/oxc-validator + run: | + set +e + npm audit --audit-level=high | tee "$GITHUB_WORKSPACE/logs-npm-audit-oxc.txt" + npm audit --json > "$GITHUB_WORKSPACE/logs-npm-audit-oxc.json" || true + { + echo "## npm audit (oxc-validator)" + echo + echo '```' + tail -200 "$GITHUB_WORKSPACE/logs-npm-audit-oxc.txt" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + # ───────────────────────────────────────────────────────────── # cargo: Studio Tauri shell # ───────────────────────────────────────────────────────────── @@ -348,6 +366,7 @@ 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/src-tauri/Cargo.lock \ --lockfile=requirements.txt:audit-reqs/unsloth-deps.txt \ --lockfile=requirements.txt:audit-reqs/studio.txt \ @@ -913,24 +932,39 @@ jobs: # 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. + # + # Two separate npm projects share this scan surface; scan each. run: | set -o pipefail LOG=logs-scan-npm.txt python3 scripts/scan_npm_packages.py 2>&1 | tee "$LOG" + LOG2=logs-scan-npm-oxc.txt + python3 scripts/scan_npm_packages.py \ + --lockfile studio/backend/core/data_recipe/oxc-validator/package-lock.json \ + 2>&1 | tee "$LOG2" { - echo "## scan_npm_packages" + echo "## scan_npm_packages (Studio frontend)" echo echo '### Findings (tail)' echo '```' tail -300 "$LOG" echo '```' + echo + echo "## scan_npm_packages (oxc-validator)" + echo + echo '### Findings (tail)' + echo '```' + tail -300 "$LOG2" + echo '```' } >> "$GITHUB_STEP_SUMMARY" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: always() with: name: scan-npm-packages-log - path: logs-scan-npm.txt + path: | + logs-scan-npm.txt + logs-scan-npm-oxc.txt retention-days: 30 # ───────────────────────────────────────────────────────────────────── @@ -1103,6 +1137,9 @@ jobs: BASE_SHA="${{ github.event.pull_request.base.sha }}" git show "$BASE_SHA:studio/frontend/package-lock.json" \ > /tmp/base-package-lock.json + # 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 - name: Diff for newly-added install-script deps if: github.event_name == 'pull_request' @@ -1110,6 +1147,9 @@ jobs: python3 scripts/check_new_install_scripts.py \ --base /tmp/base-package-lock.json \ --head studio/frontend/package-lock.json + 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 - name: Skip install-script diff (non-PR trigger) if: github.event_name != 'pull_request' 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), diff --git a/build.sh b/build.sh index 43c06bf3b8..de69f13d7c 100644 --- a/build.sh +++ b/build.sh @@ -33,11 +33,11 @@ _restore_gitignores() { } trap _restore_gitignores EXIT -# Use bun if available (faster), fall back to npm. Lockfile-strict on -# both paths so a release build can't pull a fresh caret-range patch -# of any transitive from the registry. +# Use bun if a bun.lock is committed (lockfile-strict, fast), else npm ci. +# bun install --frozen-lockfile cannot migrate from package-lock.json, so if +# only the npm lockfile is present we skip bun and go straight to npm ci. _install_ok=false -if command -v bun &>/dev/null; then +if [ -f bun.lock ] && command -v bun &>/dev/null; then if bun install --frozen-lockfile; then _install_ok=true else diff --git a/scripts/lockfile_supply_chain_audit.py b/scripts/lockfile_supply_chain_audit.py index ae215bf344..478be4a27e 100644 --- a/scripts/lockfile_supply_chain_audit.py +++ b/scripts/lockfile_supply_chain_audit.py @@ -652,7 +652,10 @@ def audit_cargo_lockfile(path: Path) -> list[Finding]: # ───────────────────────────────────────────────────────────────────── -DEFAULT_NPM_LOCKFILES = ("studio/frontend/package-lock.json",) +DEFAULT_NPM_LOCKFILES = ( + "studio/frontend/package-lock.json", + "studio/backend/core/data_recipe/oxc-validator/package-lock.json", +) DEFAULT_CARGO_LOCKFILES = ("studio/src-tauri/Cargo.lock",) @@ -671,7 +674,8 @@ def main(argv: list[str] | None = None) -> int: default = None, help = ( "Path to a package-lock.json (repeatable). " - "Default: studio/frontend/package-lock.json." + "Default: studio/frontend/package-lock.json plus " + "studio/backend/core/data_recipe/oxc-validator/package-lock.json." ), ) parser.add_argument( diff --git a/studio/setup.ps1 b/studio/setup.ps1 index dc7aa0e1c2..5aefcea216 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -1327,13 +1327,15 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) { $ErrorActionPreference = "Continue" Push-Location $FrontendDir - $UseBun = $null -ne (Get-Command bun -ErrorAction SilentlyContinue) + # Only use bun when a committed bun.lock is present. bun install + # --frozen-lockfile cannot migrate from package-lock.json, so without + # bun.lock the bun path would always fail. + $UseBun = ($null -ne (Get-Command bun -ErrorAction SilentlyContinue)) -and (Test-Path "bun.lock") # bun's package cache can become corrupt -- packages get stored with only # metadata but no actual content (bin/, lib/). When this happens bun install # exits 0 but leaves binaries missing. We validate after install and clear # the cache + retry once before falling back to npm. - # --frozen-lockfile so a fresh caret-range patch can't land via npm registry. if ($UseBun) { Write-Host " Using bun for package install (faster)" -ForegroundColor DarkGray $bunExit = Invoke-SetupCommand { bun install --frozen-lockfile } diff --git a/studio/setup.sh b/studio/setup.sh index 0c4a040bb5..f4b5bea076 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -367,7 +367,11 @@ _try_bun_install() { } _bun_install_ok=false -if command -v bun &>/dev/null; then +# bun install --frozen-lockfile cannot migrate from package-lock.json, so we +# only enter the bun path when a committed bun.lock exists. Without it, +# bun would fail every time and the corrupt-cache retry would clear the +# user's bun cache for nothing. +if [ -f bun.lock ] && command -v bun &>/dev/null; then substep "using bun for package install (faster)" if _try_bun_install; then _bun_install_ok=true