chore: harden npm supply chain — pin overrides, enforce npm ci, add ignore-scripts (#24838)

* chore: harden npm supply chain — pin overrides, enforce npm ci, add ignore-scripts

Replace open-ended >= version overrides with exact pins matching lockfile
versions across all 6 package.json files. Remove dead overrides for packages
not present in lockfiles. Switch CI and devcontainer from npm install to
npm ci for deterministic lockfile-based installs.

Add .npmrc to all 7 JS project directories with ignore-scripts=true (blocks
postinstall RAT vectors like the axios@1.14.1 supply chain attack) and
min-release-age=3d (refuses packages published <3 days ago, requires npm
>=11.10). Remove Yarn-only resolutions field from docs/my-website.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: bump sharp to 0.33.5 in docs, add docs .npmrc

sharp 0.32.x uses postinstall to download native binaries, which breaks
with ignore-scripts=true. sharp 0.33+ distributes via optionalDependencies
instead, making it compatible with the new .npmrc hardening.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove docs .npmrc to fix Vercel deploy

Vercel's build for docs/my-website uses npm install which needs
sharp 0.32.6's postinstall script. Since we don't control Vercel's
build process, remove the .npmrc from docs rather than fight it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: Dockerfile npm ci + nvm checksum verification

- Replace npm install with npm ci in Dockerfile.non_root,
  Dockerfile.custom_ui, and spend-logs/Dockerfile for deterministic
  lockfile-based installs
- Replace curl-pipe-bash nvm install with download-then-verify pattern
  in build_admin_ui.sh, build_ui.sh, and build_ui_custom_path.sh
- Update nvm from v0.38.0 (2021) to v0.40.4 (Jan 2026) with SHA256
  checksum verification before execution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: macOS sha256sum compat + clarify min-release-age scope

- Use shasum -a 256 fallback on macOS where sha256sum is unavailable
- Clarify in .npmrc comments that min-release-age only protects local
  npm install, not npm ci (used in CI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
stuxf 2026-03-31 13:41:37 -07:00 committed by GitHub
parent 134b01d530
commit 7066c895f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 117 additions and 132 deletions

View File

@ -12,6 +12,6 @@ echo "[post-create] Generating Prisma client"
poetry run prisma generate poetry run prisma generate
echo "[post-create] Installing npm dependencies" echo "[post-create] Installing npm dependencies"
cd ui/litellm-dashboard && npm install --no-audit --no-fund cd ui/litellm-dashboard && npm ci
echo "[post-create] Done" echo "[post-create] Done"

View File

@ -28,7 +28,7 @@ jobs:
cache-dependency-path: ui/litellm-dashboard/package-lock.json cache-dependency-path: ui/litellm-dashboard/package-lock.json
- name: Install dependencies - name: Install dependencies
run: npm install run: npm ci
- name: Build - name: Build
run: npm run build run: npm run build

5
.npmrc Normal file
View File

@ -0,0 +1,5 @@
# Supply-chain hardening
# Packages needing lifecycle scripts: npm rebuild <pkg>
ignore-scripts=true
# Protects local npm install only — npm ci (used in CI) ignores this
min-release-age=3d

View File

@ -160,7 +160,7 @@ run_grype_scans() {
"CVE-2026-0775" # npm cli incorrect permission assignment - no fix available yet, npm is only used at build/prisma-generate time "CVE-2026-0775" # npm cli incorrect permission assignment - no fix available yet, npm is only used at build/prisma-generate time
"GHSA-3ppc-4f35-3m26" # minimatch ReDoS via repeated wildcards - from nodejs_wheel bundled npm, not used in application runtime code "GHSA-3ppc-4f35-3m26" # minimatch ReDoS via repeated wildcards - from nodejs_wheel bundled npm, not used in application runtime code
"GHSA-83g3-92jg-28cx" # tar arbitrary file read/write via hardlink - from nodejs_wheel bundled npm, not used in application runtime code "GHSA-83g3-92jg-28cx" # tar arbitrary file read/write via hardlink - from nodejs_wheel bundled npm, not used in application runtime code
"CVE-2026-25639" # axios - full fix requires 1.x major version bump; pinned to >=0.30.2 to clear other axios CVEs, upgrade to 1.x in follow-up "CVE-2026-25639" # axios DoS via __proto__ in mergeConfig - transitive dev dep via @neondatabase/api-client, not imported in application code
"CVE-2026-2297" # Python 3.13 SourcelessFileLoader audit hook bypass - no fix available in base image "CVE-2026-2297" # Python 3.13 SourcelessFileLoader audit hook bypass - no fix available in base image
"GHSA-qffp-2rhf-9h96" # tar hardlink path traversal - from nodejs_wheel bundled npm, not used in application runtime code "GHSA-qffp-2rhf-9h96" # tar hardlink path traversal - from nodejs_wheel bundled npm, not used in application runtime code
"CVE-2026-2673" # OpenSSL 3.6.1 TLS 1.3 key exchange group negotiation issue - no fix available yet "CVE-2026-2673" # OpenSSL 3.6.1 TLS 1.3 key exchange group negotiation issue - no fix available yet

View File

@ -51,7 +51,7 @@ ENV UI_BASE_PATH="/prod/ui"
# Build the UI with the specified UI_BASE_PATH # Build the UI with the specified UI_BASE_PATH
WORKDIR /app/ui/litellm-dashboard WORKDIR /app/ui/litellm-dashboard
RUN npm install RUN npm ci
RUN UI_BASE_PATH=$UI_BASE_PATH npm run build RUN UI_BASE_PATH=$UI_BASE_PATH npm run build
# Create the destination directory # Create the destination directory

View File

@ -47,7 +47,7 @@ RUN mkdir -p /var/lib/litellm/ui && \
if [ -f "/app/enterprise/enterprise_ui/enterprise_colors.json" ]; then \ if [ -f "/app/enterprise/enterprise_ui/enterprise_colors.json" ]; then \
cp /app/enterprise/enterprise_ui/enterprise_colors.json ./ui_colors.json; \ cp /app/enterprise/enterprise_ui/enterprise_colors.json ./ui_colors.json; \
fi && \ fi && \
npm install --legacy-peer-deps && \ npm ci && \
npm run build && \ npm run build && \
cp -r /app/ui/litellm-dashboard/out/* /var/lib/litellm/ui/ && \ cp -r /app/ui/litellm-dashboard/out/* /var/lib/litellm/ui/ && \
mkdir -p /var/lib/litellm/assets && \ mkdir -p /var/lib/litellm/assets && \

View File

@ -40,11 +40,22 @@ else
exit 1 exit 1
fi fi
fi fi
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash NVM_VERSION="v0.40.4"
NVM_CHECKSUM="4b7412c49960c7d31e8df72da90c1fb5b8cccb419ac99537b737028d497aba4f"
NVM_SCRIPT=$(mktemp)
trap 'rm -f "$NVM_SCRIPT"' EXIT
curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" -o "$NVM_SCRIPT"
if command -v sha256sum &>/dev/null; then
echo "${NVM_CHECKSUM} ${NVM_SCRIPT}" | sha256sum -c -
elif command -v shasum &>/dev/null; then
echo "${NVM_CHECKSUM} ${NVM_SCRIPT}" | shasum -a 256 -c -
else
echo "No sha256 tool found; cannot verify nvm checksum"; exit 1
fi || { echo "nvm checksum verification failed"; exit 1; }
bash "$NVM_SCRIPT"
source ~/.nvm/nvm.sh source ~/.nvm/nvm.sh
nvm install v18.17.0 nvm install v18.17.0
nvm use v18.17.0 nvm use v18.17.0
npm install -g npm
# copy _enterprise.json from this directory to /ui/litellm-dashboard, and rename it to ui_colors.json # copy _enterprise.json from this directory to /ui/litellm-dashboard, and rename it to ui_colors.json
cp enterprise/enterprise_ui/enterprise_colors.json ui/litellm-dashboard/ui_colors.json cp enterprise/enterprise_ui/enterprise_colors.json ui/litellm-dashboard/ui_colors.json

View File

@ -48,27 +48,26 @@
"node": ">=16.14", "node": ">=16.14",
"npm": ">=8.3.0" "npm": ">=8.3.0"
}, },
"resolutions": {
"webpack-dev-server": ">=5.2.1",
"form-data": ">=4.0.4",
"mermaid": ">=11.10.0",
"gray-matter": "4.0.3",
"node-forge": ">=1.3.2"
},
"overrides": { "overrides": {
"webpack-dev-server": ">=5.2.1",
"form-data": ">=4.0.4",
"mermaid": ">=11.10.0",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
"glob": ">=11.1.0", "webpack-dev-server": "5.2.3",
"tar": ">=7.5.10", "form-data": "4.0.5",
"minimatch": ">=10.2.4", "mermaid": "11.12.1",
"diff": ">=8.0.3", "minimatch": "10.2.4",
"@isaacs/brace-expansion": ">=5.0.1", "serialize-javascript": "7.0.3",
"serialize-javascript": ">=7.0.3", "mdast-util-to-hast": "13.2.1",
"node-forge": ">=1.3.2", "lodash-es": "4.17.23",
"mdast-util-to-hast": ">=13.2.1", "@babel/traverse": "7.28.5",
"lodash-es": ">=4.17.23", "ws": "8.19.0",
"http-proxy-middleware": "3.0.5",
"tar-fs": "3.1.1",
"webpack-dev-middleware": "5.3.4",
"braces": "3.0.3",
"webpack": "5.105.3",
"serve-static": "2.2.1",
"path-to-regexp": "1.9.0",
"dompurify": "3.3.2",
"svgo": "4.0.1",
"schema-utils@3": { "schema-utils@3": {
"ajv": "6.14.0" "ajv": "6.14.0"
}, },
@ -83,18 +82,6 @@
}, },
"url-loader": { "url-loader": {
"ajv": "6.14.0" "ajv": "6.14.0"
}, }
"@babel/traverse": ">=7.23.2",
"ws": ">=7.5.10",
"http-proxy-middleware": ">=2.0.9",
"tar-fs": ">=2.1.4",
"webpack-dev-middleware": ">=5.3.4",
"braces": ">=3.0.3",
"axios": "1.13.6",
"webpack": ">=5.94.0",
"serve-static": ">=1.16.0",
"path-to-regexp": ">=0.1.12",
"dompurify": ">=3.3.2",
"svgo": ">=3.3.3"
} }
} }

5
litellm-js/proxy/.npmrc Normal file
View File

@ -0,0 +1,5 @@
# Supply-chain hardening
# Packages needing lifecycle scripts: npm rebuild <pkg>
ignore-scripts=true
# Protects local npm install only — npm ci (used in CI) ignores this
min-release-age=3d

View File

@ -0,0 +1,5 @@
# Supply-chain hardening
# Packages needing lifecycle scripts: npm rebuild <pkg>
ignore-scripts=true
# Protects local npm install only — npm ci (used in CI) ignores this
min-release-age=3d

View File

@ -8,7 +8,7 @@ WORKDIR /app
COPY ./litellm-js/spend-logs/package*.json ./ COPY ./litellm-js/spend-logs/package*.json ./
# Install dependencies # Install dependencies
RUN npm install RUN npm ci
# Install Prisma globally # Install Prisma globally
RUN npm install -g prisma RUN npm install -g prisma

View File

@ -9,22 +9,5 @@
"devDependencies": { "devDependencies": {
"@types/node": "^20.11.17", "@types/node": "^20.11.17",
"tsx": "^4.7.1" "tsx": "^4.7.1"
},
"overrides": {
"glob": ">=11.1.0",
"tar": ">=7.5.10",
"minimatch": ">=10.2.4",
"diff": ">=8.0.3",
"@isaacs/brace-expansion": ">=5.0.1",
"@babel/traverse": ">=7.23.2",
"ws": ">=7.5.10",
"http-proxy-middleware": ">=2.0.9",
"tar-fs": ">=2.1.4",
"webpack-dev-middleware": ">=5.3.4",
"braces": ">=3.0.3",
"axios": "1.13.6",
"webpack": ">=5.94.0",
"serve-static": ">=1.16.0",
"path-to-regexp": ">=0.1.12"
} }
} }

View File

@ -11,20 +11,10 @@
"jest": "^29.7.0" "jest": "^29.7.0"
}, },
"overrides": { "overrides": {
"glob": ">=11.1.0", "glob": "13.0.0",
"tar": ">=7.5.11", "minimatch": "10.1.1",
"minimatch": ">=10.2.4", "@isaacs/brace-expansion": "5.0.0",
"diff": ">=8.0.3", "@babel/traverse": "7.28.5",
"@isaacs/brace-expansion": ">=5.0.1", "braces": "3.0.3"
"@babel/traverse": ">=7.23.2",
"ws": ">=7.5.10",
"http-proxy-middleware": ">=2.0.9",
"tar-fs": ">=2.1.4",
"webpack-dev-middleware": ">=5.3.4",
"braces": ">=3.0.3",
"axios": "1.13.6",
"webpack": ">=5.94.0",
"serve-static": ">=1.16.0",
"path-to-regexp": ">=0.1.12"
} }
} }

View File

@ -0,0 +1,5 @@
# Supply-chain hardening
# Packages needing lifecycle scripts: npm rebuild <pkg>
ignore-scripts=true
# Protects local npm install only — npm ci (used in CI) ignores this
min-release-age=3d

View File

@ -10,22 +10,5 @@
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.47.2", "@playwright/test": "^1.47.2",
"@types/node": "^22.5.5" "@types/node": "^22.5.5"
},
"overrides": {
"glob": ">=11.1.0",
"tar": ">=7.5.10",
"minimatch": ">=10.2.4",
"diff": ">=8.0.3",
"@isaacs/brace-expansion": ">=5.0.1",
"@babel/traverse": ">=7.23.2",
"ws": ">=7.5.10",
"http-proxy-middleware": ">=2.0.9",
"tar-fs": ">=2.1.4",
"webpack-dev-middleware": ">=5.3.4",
"braces": ">=3.0.3",
"axios": "1.13.6",
"webpack": ">=5.94.0",
"serve-static": ">=1.16.0",
"path-to-regexp": ">=0.1.12"
} }
} }

View File

@ -0,0 +1,5 @@
# Supply-chain hardening
# Packages needing lifecycle scripts: npm rebuild <pkg>
ignore-scripts=true
# Protects local npm install only — npm ci (used in CI) ignores this
min-release-age=3d

View File

@ -24,20 +24,11 @@
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
"overrides": { "overrides": {
"glob": ">=11.1.0", "glob": "13.0.0",
"tar": ">=7.5.10", "minimatch": "10.1.1",
"minimatch": ">=10.2.4", "@isaacs/brace-expansion": "5.0.0",
"diff": ">=8.0.3", "@babel/traverse": "7.28.5",
"@isaacs/brace-expansion": ">=5.0.1", "ws": "8.18.3",
"@babel/traverse": ">=7.23.2", "braces": "3.0.3"
"ws": ">=7.5.10",
"http-proxy-middleware": ">=2.0.9",
"tar-fs": ">=2.1.4",
"webpack-dev-middleware": ">=5.3.4",
"braces": ">=3.0.3",
"axios": "1.13.6",
"webpack": ">=5.94.0",
"serve-static": ">=1.16.0",
"path-to-regexp": ">=0.1.12"
} }
} }

View File

@ -0,0 +1,5 @@
# Supply-chain hardening
# Packages needing lifecycle scripts: npm rebuild <pkg>
ignore-scripts=true
# Protects local npm install only — npm ci (used in CI) ignores this
min-release-age=3d

View File

@ -2,8 +2,20 @@
# Check if nvm is not installed # Check if nvm is not installed
if ! command -v nvm &> /dev/null; then if ! command -v nvm &> /dev/null; then
# Install nvm # Install nvm with checksum verification
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash NVM_VERSION="v0.40.4"
NVM_CHECKSUM="4b7412c49960c7d31e8df72da90c1fb5b8cccb419ac99537b737028d497aba4f"
NVM_SCRIPT=$(mktemp)
trap 'rm -f "$NVM_SCRIPT"' EXIT
curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" -o "$NVM_SCRIPT"
if command -v sha256sum &>/dev/null; then
echo "${NVM_CHECKSUM} ${NVM_SCRIPT}" | sha256sum -c -
elif command -v shasum &>/dev/null; then
echo "${NVM_CHECKSUM} ${NVM_SCRIPT}" | shasum -a 256 -c -
else
echo "No sha256 tool found; cannot verify nvm checksum"; exit 1
fi || { echo "nvm checksum verification failed"; exit 1; }
bash "$NVM_SCRIPT"
# Source nvm script in the current session # Source nvm script in the current session
export NVM_DIR="$HOME/.nvm" export NVM_DIR="$HOME/.nvm"

View File

@ -12,8 +12,20 @@ UI_BASE_PATH="$1"
# Check if nvm is not installed # Check if nvm is not installed
if ! command -v nvm &> /dev/null; then if ! command -v nvm &> /dev/null; then
# Install nvm # Install nvm with checksum verification
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash NVM_VERSION="v0.40.4"
NVM_CHECKSUM="4b7412c49960c7d31e8df72da90c1fb5b8cccb419ac99537b737028d497aba4f"
NVM_SCRIPT=$(mktemp)
trap 'rm -f "$NVM_SCRIPT"' EXIT
curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" -o "$NVM_SCRIPT"
if command -v sha256sum &>/dev/null; then
echo "${NVM_CHECKSUM} ${NVM_SCRIPT}" | sha256sum -c -
elif command -v shasum &>/dev/null; then
echo "${NVM_CHECKSUM} ${NVM_SCRIPT}" | shasum -a 256 -c -
else
echo "No sha256 tool found; cannot verify nvm checksum"; exit 1
fi || { echo "nvm checksum verification failed"; exit 1; }
bash "$NVM_SCRIPT"
# Source nvm script in the current session # Source nvm script in the current session
export NVM_DIR="$HOME/.nvm" export NVM_DIR="$HOME/.nvm"

View File

@ -82,28 +82,14 @@
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"overrides": { "overrides": {
"diff": ">=8.0.3", "prismjs": "1.30.0",
"prismjs": ">=1.30.0", "js-yaml": "4.1.1",
"webpack-dev-server": ">=5.2.1", "glob": "13.0.0",
"mermaid": ">=11.10.0", "minimatch": "10.2.4",
"js-yaml": ">=4.1.1", "lodash": "4.17.23",
"glob": ">=11.1.0", "ws": "8.19.0",
"tar": ">=7.5.11", "braces": "3.0.3",
"minimatch": ">=10.2.4", "axios": "1.13.6"
"@isaacs/brace-expansion": ">=5.0.1",
"node-forge": ">=1.3.2",
"lodash-es": ">=4.17.23",
"lodash": ">=4.17.23",
"@babel/traverse": ">=7.23.2",
"ws": ">=7.5.10",
"http-proxy-middleware": ">=2.0.9",
"tar-fs": ">=2.1.4",
"webpack-dev-middleware": ">=5.3.4",
"braces": ">=3.0.3",
"axios": "1.13.6",
"webpack": ">=5.94.0",
"serve-static": ">=1.16.0",
"path-to-regexp": ">=0.1.12"
}, },
"engines": { "engines": {
"node": ">=18.17.0", "node": ">=18.17.0",