CI: add codespell + shellcheck to Lint CI; add Security audit workflow
Three Priority-1 follow-ups from the lint review.
Lint CI gains two non-blocking gates that surface drift without
blocking merges (the same shape as the existing format-drift step):
- codespell: typo catcher across source / comments / docs. Skips
lockfiles, generated assets, binary artefacts, LICENSE files.
ignore-words-list pulls out short identifiers and PyTorch
idioms (parm/parms, ans, hist, etc.) the default dictionary
would flag. Local run finds 16 real typos to fix in a follow-up.
- shellcheck: catches subtle shell bugs `bash -n` doesn't see --
unquoted expansions, useless cat, `[[ ]]` command substitution,
etc. SC1090 + SC2034 muted because install/setup scripts
legitimately source runtime paths and use export-only
assignments. Critical-path coverage: install.sh, setup.sh,
tests/sh/.
Both pinned for reproducibility (codespell>=2.3,<3 in pip,
shellcheck via apt-get). Both surface findings in PR annotations
without failing the run; drop continue-on-error after the cleanup
PRs land.
New workflow: Security audit. Runs `pip-audit` against the same
dep set Studio's backend pytest matrix installs, so we audit what
the runtime actually loads (not what pyproject.toml's transitive
resolution might pull in differently). Triggers:
- PRs touching requirements / pyproject.toml,
- push to main / pip,
- nightly @ 04:13 UTC (off-the-hour to dodge cron rush),
- workflow_dispatch.
The default branch already carries 17 known vulnerabilities per
the dependabot banner, so a hard gate today would block every PR
on a baseline we have not triaged. Non-blocking; full table goes
to GITHUB_STEP_SUMMARY for grep-ability and a 30-day artefact for
historical comparison.
The custom AST anti-pattern scan I prototyped was dropped: every
class of CPU-import-time bug we hit in this PR (bitsandbytes,
torchvision, _cuda_getCurrentRawStream, DEVICE_COUNT==0 stream
init) is already caught by the Repo tests (CPU) job exercising
the actual import on a CPU torch wheel. Restating the rule
in AST form would only add noise.
This commit is contained in:
parent
467e01af64
commit
de7fd062ab
2 changed files with 140 additions and 3 deletions
47
.github/workflows/lint-ci.yml
vendored
47
.github/workflows/lint-ci.yml
vendored
|
|
@ -50,9 +50,15 @@ jobs:
|
|||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
|
||||
# Pin to match .pre-commit-config.yaml so a CI-only ruff bump
|
||||
# cannot disagree with what pre-commit accepted.
|
||||
- run: pip install 'ruff==0.15.12' 'pyyaml>=6'
|
||||
# Pin ruff to match .pre-commit-config.yaml so a CI-only ruff
|
||||
# bump cannot disagree with what pre-commit accepted.
|
||||
# codespell is pinned for the same reason: a reviewer should
|
||||
# never see a typo report appear and disappear depending on
|
||||
# which codespell version the runner happened to install.
|
||||
- run: pip install 'ruff==0.15.12' 'pyyaml>=6' 'codespell>=2.3,<3'
|
||||
|
||||
- name: Linux deps for shellcheck
|
||||
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends shellcheck
|
||||
|
||||
- name: Python AST/syntax check (every committed .py must compile)
|
||||
# python -m compileall uses the same parser the interpreter
|
||||
|
|
@ -267,6 +273,41 @@ jobs:
|
|||
print(f"{scanned} JSON files parse cleanly")
|
||||
PY
|
||||
|
||||
- name: codespell typo check (informational)
|
||||
# Catches typos in code, comments, and docs across the repo.
|
||||
# Skips lockfiles, generated assets, binary artefacts, and
|
||||
# the LICENSE files (US/UK spelling drift in legal text is
|
||||
# not ours to second-guess). The ignore-words-list pulls
|
||||
# out short identifiers + valid technical terms that
|
||||
# codespell's default dictionary would otherwise flag
|
||||
# (e.g. `ans` as a math-quiz variable name in
|
||||
# tests/utils/aime_eval.py, `parm`/`parms` in PyTorch
|
||||
# nn.Module idioms). Non-blocking until the surfaced typos
|
||||
# are fixed; drop continue-on-error after the cleanup.
|
||||
continue-on-error: true
|
||||
run: |
|
||||
codespell \
|
||||
--skip='*.lock,*.lockb,*.json,*.svg,*.png,*.jpg,*.jpeg,*.gif,*.ico,*.woff*,*.ttf,*.eot,*.zip,*.gz,*.gguf,*.safetensors,*.bin,node_modules,.git,build,dist,unsloth_compiled_cache,unsloth.egg-info,target,studio/frontend/dist,*.pyc,*-licenses.txt,LICENSE*' \
|
||||
--ignore-words-list='ans,bu,hel,fo,te,ot,hist,ned,sav,recurser,datas,nin,parm,parms,checkin,nd,fr,inout,donot,uint' \
|
||||
--quiet-level=2
|
||||
|
||||
- name: shellcheck on committed *.sh (informational)
|
||||
# Goes beyond `bash -n` (which only parses): catches subtle
|
||||
# shell bugs like unquoted variable expansions, useless
|
||||
# `cat`, command substitutions inside `[[`, etc. The
|
||||
# install/setup scripts are critical-path so the signal is
|
||||
# worth surfacing. Non-blocking until install.sh's
|
||||
# hand-rolled patterns get cleaned up; drop continue-on-error
|
||||
# afterwards.
|
||||
continue-on-error: true
|
||||
run: |
|
||||
# Exclude SC1090 ("source not followable") -- legitimate
|
||||
# for installer scripts that source files at runtime
|
||||
# paths shellcheck cannot resolve statically.
|
||||
# SC2034 ("variable assigned but never used") fires on
|
||||
# the export-only assignment idiom we use in install.sh.
|
||||
shellcheck -e SC1090,SC2034 $(git ls-files '*.sh')
|
||||
|
||||
- name: ruff format drift (informational)
|
||||
# The canonical formatter is scripts/run_ruff_format.py
|
||||
# = ruff format + scripts/enforce_kwargs_spacing.py, so plain
|
||||
|
|
|
|||
96
.github/workflows/security-audit.yml
vendored
Normal file
96
.github/workflows/security-audit.yml
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
||||
|
||||
# pip-audit on the Studio backend's resolved dep tree. Runs:
|
||||
# - on PRs touching any requirements file or pyproject.toml,
|
||||
# - nightly on a schedule against main, so newly-published CVEs
|
||||
# surface even when no PR opens,
|
||||
# - on workflow_dispatch for ad-hoc invocations.
|
||||
#
|
||||
# pip-audit is non-blocking initially. The default branch already
|
||||
# carries 17 known vulnerabilities (per the dependabot banner on
|
||||
# every push), so a hard gate today would block every PR on a
|
||||
# baseline we haven't triaged. As the baseline closes, drop
|
||||
# continue-on-error and the next PR that introduces a NEW vuln
|
||||
# will be blocked instead.
|
||||
|
||||
name: Security audit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'studio/backend/requirements/**'
|
||||
- 'pyproject.toml'
|
||||
- '.github/workflows/security-audit.yml'
|
||||
push:
|
||||
branches: [main, pip]
|
||||
schedule:
|
||||
# 04:13 UTC every day. Off-the-hour so we don't pile onto the
|
||||
# GitHub-Actions cron rush at :00.
|
||||
- cron: '13 4 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pip-audit:
|
||||
name: pip-audit (Studio backend deps)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install pip-audit + the dep set under test
|
||||
# We resolve the same dep set Studio's backend pytest matrix
|
||||
# installs, then pip-audit it. This audits what Studio
|
||||
# actually loads at runtime, not what pyproject.toml's
|
||||
# transitive resolution might pull in differently on a
|
||||
# different host.
|
||||
run: |
|
||||
python -m pip install --upgrade pip 'pip-audit>=2.7'
|
||||
pip install -r studio/backend/requirements/studio.txt
|
||||
pip install \
|
||||
python-multipart aiofiles sqlalchemy cryptography \
|
||||
pyyaml jinja2 mammoth unpdf requests typer \
|
||||
'numpy<3' pytest pytest-asyncio httpx
|
||||
pip install --index-url https://download.pytorch.org/whl/cpu \
|
||||
'torch>=2.4,<2.11' 'torchvision<0.26'
|
||||
pip install 'transformers>=4.51,<5.5'
|
||||
pip install 'bitsandbytes>=0.45'
|
||||
pip install 'unsloth_zoo>=2026.5.1'
|
||||
pip install -e . --no-deps
|
||||
|
||||
- name: pip-audit (informational, drives baseline triage)
|
||||
# `--strict`: fail on any finding (we'll use it once the
|
||||
# baseline is clean).
|
||||
# For now, run without --strict and let the runner surface
|
||||
# the count + which packages are affected. Output goes to
|
||||
# the workflow summary so it's grep-able.
|
||||
continue-on-error: true
|
||||
run: |
|
||||
pip-audit --format=columns | tee logs-audit.txt
|
||||
{
|
||||
echo "## pip-audit results"
|
||||
echo
|
||||
echo "\`\`\`"
|
||||
cat logs-audit.txt
|
||||
echo "\`\`\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload audit log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pip-audit-log
|
||||
path: logs-audit.txt
|
||||
retention-days: 30
|
||||
Loading…
Add table
Add a link
Reference in a new issue