unsloth/.github/workflows/wheel-smoke.yml
Daniel Han 12c6538b06 CI: SHA-pin every action; fix 4 bugs in advisory-audit
Last security-audit run revealed 4 step-level errors hidden by
continue-on-error (the job reported pass but each fix is real):

  1. OSV-Scanner curl 404 -> tar exit 2. v2.x ships a raw binary
     (`osv-scanner_linux_amd64`), not a tarball. Drop tar -xzf,
     curl -o the binary directly + chmod +x.
  2. cargo audit `parse error: TOML parse error at line 5 col 8`
     on RUSTSEC-2026-0073.md. cargo-audit 0.21 doesn't parse the
     CVSS 4.0 schema used in 2026 advisories. Bump pin to ^0.22.
  3. TruffleHog `flag 'no-update' cannot be repeated`. The
     trufflesecurity/trufflehog action passes --no-update
     internally already; remove our duplicate from extra_args.
  4. cyclonedx-py `unrecognized arguments: --schema-version 1.6
     --outfile ...`. cyclonedx-bom 4.x renamed to `--sv` for spec
     version and `-o` for the output file.

Plus pin every remaining mutable-ref action to a 40-char SHA. The
new GHA pinning verifier flagged 4 third-party + 40 first-party
mutable refs; this commit pins all 44 to the latest SHA *within
the existing major version* (no auto-upgrades). Mappings:

  actions/checkout         @v4    -> 34e114876b... (v4.3.1)
  actions/setup-node       @v4    -> 49933ea528... (v4.4.0)
  actions/setup-python     @v5    -> a26af69be9... (v5.6.0)
  actions/stale            @v10   -> b5d41d4e1d... (v10.2.0)
  actions/upload-artifact  @v4    -> ea165f8d65... (v4.6.2)
  actions/cache            @v4    -> 0057852bfa... (v4.3.0)
  swatinem/rust-cache      @v2    -> 23869a5bd6... (v2.9.1)
  dtolnay/rust-toolchain   @stable-> 29eef336d9... (stable @ 2026-05-07)

44 pins applied across 11 workflow files. The pin verifier now
reports zero unpinned `uses:`. Dependabot's github-actions
ecosystem (already configured in .github/dependabot.yml) will
auto-bump these SHAs in weekly batches.

This closes the same attack class that hit litellm 1.82.7: an
attacker who hijacks a tag (as in the aquasecurity/trivy-action
March 2026 incident) cannot redirect our workflows because we no
longer follow tag refs.
2026-05-07 01:12:40 +00:00

127 lines
4.8 KiB
YAML

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
# Builds the PyPI wheel from the PR branch, then verifies the built wheel
# actually contains what we expect to ship and does NOT contain the broken
# Studio bundle that 2026.5.1 published. This is the single workflow that
# would have blocked the 2026.5.1 release before twine upload.
#
# Verified locally end-to-end against this branch:
# - python -m build produces unsloth-<version>-py3-none-any.whl in 13s
# - wheel content sanity passes:
# lockfile shipped, frontend dist shipped,
# no node_modules in wheel, no bun.lock in wheel,
# main bundle has unstable_Provider hits=1 (assistant-ui internals only).
# - Studio backend imports cleanly from the installed wheel with the
# lightweight dep set below.
name: Wheel CI
on:
pull_request:
paths:
- 'pyproject.toml'
- 'studio/**'
- 'unsloth/**'
- 'unsloth_cli/**'
- '.github/workflows/wheel-smoke.yml'
push:
branches: [main, pip]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
wheel:
name: Wheel build + content sanity + import smoke
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: studio/frontend/package-lock.json
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'
- name: Build frontend
run: |
cd studio/frontend
npm ci --no-fund --no-audit
npm run build
- name: Build wheel + sdist
run: |
python -m pip install --upgrade pip build
rm -rf dist build ./*.egg-info
python -m build
- name: Wheel content sanity
run: |
python - <<'PY'
import zipfile, glob, sys
w = glob.glob("dist/unsloth-*.whl")
if not w:
print("FAIL: no wheel produced"); sys.exit(2)
w = w[0]
print(f"wheel: {w}")
with zipfile.ZipFile(w) as z:
n = z.namelist()
checks = {
"lockfile shipped": any(s.endswith("studio/frontend/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),
}
js = [s for s in n
if "studio/frontend/dist/assets/" in s
and s.endswith(".js")
and "/index-" in s]
if not js:
print("FAIL: no main bundle index-*.js in wheel"); sys.exit(2)
data = z.read(js[0]).decode("utf-8", "replace")
hits = data.count("unstable_Provider:")
print(f"main bundle: {js[0]}")
print(f"unstable_Provider hits: {hits} (>=4 indicates 2026.5.1 regression)")
checks["bundle has no Studio unstable_Provider call site"] = (hits < 4)
print()
for k, v in checks.items():
print(f" [{'PASS' if v else 'FAIL'}] {k}")
sys.exit(0 if all(checks.values()) else 1)
PY
- name: Studio backend import smoke
# Imports `studio.backend.main:app` from the freshly-installed wheel in
# a clean venv. This catches the class of bug that 2026.5.1 shipped with:
# frontend dist missing, package-lock.json missing, or the wheel's Python
# source tree broken in a way that surfaces only at app construction time.
run: |
python -m venv /tmp/v
/tmp/v/bin/pip install --upgrade pip
/tmp/v/bin/pip install -r studio/backend/requirements/studio.txt
/tmp/v/bin/pip install \
python-multipart aiofiles sqlalchemy cryptography \
pyyaml jinja2 mammoth unpdf requests \
'numpy<3'
/tmp/v/bin/pip install --no-deps dist/unsloth-*.whl
# Run from /tmp so Python imports the installed package, not the source tree.
cd /tmp
/tmp/v/bin/python -c "from studio.backend.main import app; print('Studio backend OK:', app.title)"
- name: Upload wheel on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: unsloth-wheel
path: dist/
retention-days: 7