Harden npm and yarn installs against supply-chain attacks (#12660)

* Harden npm and yarn installs against supply-chain attacks

- studio/frontend/.npmrc: allow-git=none. Git dependencies bypass the
  registry, the 7-day release-age gate and the integrity pin, and run
  prepare scripts on install. The lockfile has none.
- oxc-validator/.npmrc (new): the installers run npm ci here on user
  machines, so mirror the frontend protections (min-release-age=7,
  registry pin, save-exact, allow-git=none). npm ci still installs the
  locked versions; UNSLOTH_NPM_REGISTRY keeps working via --registry.
- Docker JupyterLab extension: commit yarn.lock (generated with the jlpm
  bundled in jupyterlab 4.6.0), install with jlpm install --immutable,
  set enableScripts: false, and include yarn.lock in the docker build
  context. Image builds no longer resolve caret ranges fresh or run
  dependency install scripts.

* oxc-validator .npmrc: do not pin the registry

A project registry= pin overrides a corporate mirror set in the user's
~/.npmrc, and this npm ci runs on every Studio install and update, so
users behind a blocked npmjs.org would fail setup. Integrity hashes in
the lockfile already reject tampered tarballs from any registry.

* npm: harden the Tauri CLI install; run CI when an .npmrc changes

studio/.npmrc applies the same settings as the frontend (7-day release
age, exact saves, no git dependencies) plus ignore-scripts, to the
npm ci --prefix studio the Desktop release runs to fetch the Tauri CLI.
No locked package there has install scripts. No registry pin, as for
the OXC validator.

The workflows that already trigger on the OXC validator lockfile and the
studio/ Tauri lockfile now also trigger on the .npmrc next to them.

* Desktop clean-machine CI: also run on studio/.npmrc changes
This commit is contained in:
Daniel Han 2026-10-04 02:21:52 -07:00 • committed by GitHub
parent 5971d280d4
commit 4d16e94d5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 4170 additions and 3 deletions

View file

@ -89,6 +89,7 @@ on:
- 'studio/frontend/.npmrc'
- 'studio/backend/core/data_recipe/oxc-validator/package.json'
- 'studio/backend/core/data_recipe/oxc-validator/package-lock.json'
- 'studio/backend/core/data_recipe/oxc-validator/.npmrc'
- 'studio/nvidia_probe.py'
- 'studio/backend/__init__.py'
- 'studio/backend/utils/__init__.py'
@ -133,6 +134,7 @@ on:
- 'studio/frontend/.npmrc'
- 'studio/backend/core/data_recipe/oxc-validator/package.json'
- 'studio/backend/core/data_recipe/oxc-validator/package-lock.json'
- 'studio/backend/core/data_recipe/oxc-validator/.npmrc'
- 'studio/nvidia_probe.py'
- 'studio/backend/__init__.py'
- 'studio/backend/utils/__init__.py'

View file

@ -48,8 +48,10 @@ on:
- 'studio/package-lock.json'
- 'studio/frontend/package.json'
- 'studio/frontend/package-lock.json'
- 'studio/.npmrc'
- 'studio/frontend/.npmrc'
- 'studio/backend/core/data_recipe/oxc-validator/package-lock.json'
- 'studio/backend/core/data_recipe/oxc-validator/.npmrc'
- 'studio/src-tauri/Cargo.toml'
- 'studio/src-tauri/Cargo.lock'
- 'studio/src-tauri/build.rs'

View file

@ -83,6 +83,7 @@ on:
- 'scripts/lockfile_supply_chain_audit.py'
- 'studio/package-lock.json'
- 'studio/backend/core/data_recipe/oxc-validator/package-lock.json'
- 'studio/backend/core/data_recipe/oxc-validator/.npmrc'
- 'scripts/scan_npm_packages.py'
- 'scripts/scan_npm_packages_baseline.json'
- 'scripts/check_new_install_scripts.py'

View file

@ -31,6 +31,7 @@ on:
# manifests decide what it builds with.
- 'studio/package.json'
- 'studio/package-lock.json'
- 'studio/.npmrc'
# Run as a step of the Linux job.
- 'scripts/lockfile_supply_chain_audit.py'
# The Rust unit test every_shared_case_agrees_with_the_desktop_filter
@ -59,6 +60,7 @@ on:
# manifests decide what it builds with.
- 'studio/package.json'
- 'studio/package-lock.json'
- 'studio/.npmrc'
# Run as a step of the Linux job.
- 'scripts/lockfile_supply_chain_audit.py'
# The Rust unit test every_shared_case_agrees_with_the_desktop_filter

View file

@ -40,6 +40,7 @@ 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/backend/core/data_recipe/oxc-validator/.npmrc'
# The harness below and the proxy it measures with.
- 'tests/studio/install/test_update_idempotency.py'
- 'tests/studio/install/idempotency_proxy.py'
@ -113,6 +114,7 @@ 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/backend/core/data_recipe/oxc-validator/.npmrc'
# The harness below and the proxy it measures with.
- 'tests/studio/install/test_update_idempotency.py'
- 'tests/studio/install/idempotency_proxy.py'

View file

@ -39,6 +39,7 @@
!jupyter/unsloth_labext/package.json
!jupyter/unsloth_labext/tsconfig.json
!jupyter/unsloth_labext/.yarnrc.yml
!jupyter/unsloth_labext/yarn.lock
!jupyter/unsloth_labext/src
!jupyter/unsloth_labext/src/**
!jupyter/unsloth_labext/style

View file

@ -37,7 +37,7 @@ RUN apt-get update \
&& rm -rf /var/lib/apt/lists/*
COPY jupyter/unsloth_labext /opt/labext-src
RUN cd /opt/labext-src \
&& /opt/unsloth-venv/bin/jlpm install \
&& /opt/unsloth-venv/bin/jlpm install --immutable \
&& /opt/unsloth-venv/bin/jlpm build:prod
FROM ${BASE_IMAGE}

View file

@ -63,7 +63,7 @@ RUN apt-get update \
RUN /opt/unsloth-venv/bin/uv pip install --python /opt/unsloth-venv/bin/python "jupyterlab==4.6.0"
COPY jupyter/unsloth_labext /opt/labext-src
RUN cd /opt/labext-src \
&& /opt/unsloth-venv/bin/jlpm install \
&& /opt/unsloth-venv/bin/jlpm install --immutable \
&& /opt/unsloth-venv/bin/jlpm build:prod
FROM ${BASE_IMAGE}

View file

@ -4,4 +4,3 @@ lib/
unsloth-jupyterlab/
.yarn/
.pnp.*
yarn.lock

View file

@ -1 +1,5 @@
nodeLinker: node-modules
# Dependencies never run install scripts here: the build only needs tsc and
# jupyter-builder, and yarn.lock is committed so the image build installs with
# `jlpm install --immutable` (no fresh resolution of caret ranges).
enableScripts: false

File diff suppressed because it is too large Load diff

14
studio/.npmrc Normal file
View file

@ -0,0 +1,14 @@
# Unsloth Desktop release npm configuration. `npm ci --prefix studio` in
# release-desktop.yml installs the pinned Tauri CLI from package-lock.json here,
# and that CLI builds the app every Desktop user installs.
#
# Refuse tarballs published less than 7 days ago (npm >=11.10; bare integer
# means DAYS). `npm ci` still installs the locked versions.
min-release-age=7
save-exact=true
# No git/GitHub dependencies (they bypass the registry, the cooldown and the
# integrity pin). No locked package has install scripts.
allow-git=none
ignore-scripts=true
audit-level=high
fund=false

View file

@ -0,0 +1,18 @@
# Unsloth OXC validator npm configuration. Same supply-chain defences as
# studio/frontend/.npmrc; the installers run `npm ci` here on user machines.
#
# Refuse tarballs published less than 7 days ago (npm >=11.10; bare integer
# means DAYS). `npm ci` still installs the locked versions, so this only
# gates new resolutions.
min-release-age=7
# Keep exact pins when a maintainer runs `npm install <pkg>` here.
save-exact=true
# No registry= pin here: this install runs on every Studio setup and update,
# and a project pin would override a corporate mirror in the user's ~/.npmrc.
# Every lock entry carries an integrity hash, so `npm ci` rejects tampered
# tarballs whichever registry serves them.
# No git/GitHub dependencies: they bypass the registry, the cooldown and the
# integrity pin, and run `prepare` on install. The lockfile has none.
allow-git=none
audit-level=high
fund=false

View file

@ -14,6 +14,11 @@ min-release-age=7
# `npm install <name>@<version> --save-exact` pass) but it stops new
# carets from creeping into the manifest as patch-version footguns.
save-exact=true
# Refuse git/GitHub dependencies outright. A git dep is fetched outside the
# registry (no min-release-age, no integrity pin) and runs its `prepare`
# script during install. The lockfile has none; this keeps a tampered lock
# or a hand-added `github:` spec from slipping one in. npm >=11.10.
allow-git=none
# Pin the default registry so a stale or hostile *lower-precedence* ~/.npmrc
# can't silently redirect our installs to an attacker registry. Note this does
# NOT block an ambient NPM_CONFIG_REGISTRY env var: npm and bun honor that at a