snpmv2026.5.16

Security

Security features, defaults, and supply-chain hardening

snpm ships with security-first defaults: install scripts are blocked, registry credentials are scoped to the announcing registry origin, lockfiles record integrity hashes, and audit can both surface advisories and attempt to fix them.

Install scripts blocked by default

Install scripts (preinstall, install, postinstall) for dependencies are blocked unless explicitly allowed. This is the single biggest attack vector in the npm ecosystem and snpm closes it by default.

Allow specific packages via env var:

export SNPM_ALLOW_SCRIPTS="esbuild,sharp,@swc/core"

Or via workspace config:

snpm-workspace.yaml
onlyBuiltDependencies:
  - esbuild
  - sharp
ignoredBuiltDependencies:
  - fsevents

Root project and workspace-member lifecycle scripts (the ones you wrote) do run during install — preinstallinstallpostinstallprepare.

After changing the allow-list, run snpm rebuild to apply it to packages that already extracted.

Only allow packages you trust. puppeteer, playwright, sharp, esbuild, @swc/core are common cases — they download platform binaries during install. When in doubt, look at the package's install script before allowing it.

Minimum package age

SNPM_MIN_PACKAGE_AGE_DAYS=N makes snpm ignore versions published within the last N days.

export SNPM_MIN_PACKAGE_AGE_DAYS=7

Why this matters:

  • Zero-day publishes — malicious versions often get yanked within hours; waiting a few days drastically reduces the chance of installing a compromised release.
  • Broken releases — hastily published versions with critical bugs get patched quickly; waiting steers you to the patched build.
  • Compromised maintainer accounts — gives the community time to detect and report unauthorized publishes.

Recommended values:

  • Development: unset.
  • CI/staging: 3.
  • Production: 7.

Trade-off: brand-new packages (and brand-new patches) won't be installable until they age. Combine with --frozen-lockfile so CI never silently picks up newer versions either.

Lockfile and integrity

snpm-lock.yaml records each package's tarball URL and integrity hash. The hash is verified against the bytes snpm downloads before the package is unpacked.

node_modules/.snpm-integrity holds a lockfile-derived hash of the install plan. On the next install, snpm reads this file first; if it still matches, the install completes in tens of milliseconds without touching the registry or unpacking anything.

In CI, use --frozen-lockfile (or SNPM_FROZEN_LOCKFILE=1):

snpm install --frozen-lockfile

A drifted lockfile becomes a hard failure, not a silent upgrade.

Registry authentication

snpm login runs a web flow by default and stores the resulting Bearer token in ~/.snpmrc. Tokens never end up in package.json or snpm-lock.yaml.

snpm login
snpm login --registry https://npm.mycompany.com
snpm login --scope @myorg --registry https://npm.mycompany.com

For CI, set a token via environment variable:

export NODE_AUTH_TOKEN=...
# or
export NPM_TOKEN=...
# or
export SNPM_AUTH_TOKEN=...

For Basic auth, set NPM_CONFIG__AUTH=<base64-of-user:pass>.

To force auth on every request (even for public scopes), enable always-auth:

.snpmrc
always-auth=true
export SNPM_ALWAYS_AUTH=1

Scoped registries

.snpmrc
@myorg:registry=https://npm.myorg.com/
//npm.myorg.com/:_authToken=${MYORG_TOKEN}

Tarball origin scoping

When a registry response includes a tarball URL on a different host, snpm does not send the registry's credentials to that other host. Credentials are scoped to the announcing registry origin. This prevents a compromised or malicious registry from steering snpm into leaking your auth token to an attacker-controlled host.

Audit

snpm audit                       # report vulnerabilities
snpm audit --audit-level high    # critical/high only
snpm audit --fix                 # try to upgrade to a fixed version
snpm audit --format sarif > a.sarif
snpm audit -P                    # production only
snpm audit --ignore-cve CVE-2024-12345 --ignore-unfixable

--format sarif produces SARIF that GitHub and GitLab security dashboards consume directly — see CI/CD for a workflow example.

When the registry's audit endpoint itself fails, add --ignore-registry-errors to keep CI green and rely on other security scanners.

Publishing safely

snpm pack (and snpm publish before uploading) runs an inspection that surfaces blocking findings — files outside the package root, missing README, oversized files, suspicious binaries. Re-run with --dry-run and --list to see the full tarball:

snpm pack --dry-run --list

Override a specific blocking finding for one publish:

snpm publish --allow-risk OVERSIZED_FILE

snpm publish --otp 123456 carries through OTP codes for accounts with two-factor auth.

Best practices

  • Commit snpm-lock.yaml and use --frozen-lockfile in CI.
  • Set SNPM_MIN_PACKAGE_AGE_DAYS=7 for production-bound CI.
  • Keep the script allow-list minimal — only the dependencies that actually need to compile native bits.
  • Run snpm audit in CI with --format sarif, gated on --audit-level high for hard failures.
  • Use scoped registries with always-auth for private packages.
  • Rotate registry tokens periodically and prefer short-lived CI tokens over long-lived ones.
  • Use snpm why <pkg> to investigate suspicious transitive dependencies before allowing them.

Reporting vulnerabilities

If you find a security vulnerability in snpm itself, please report it via GitHub Security Advisories.

On this page