# Security (/docs/security)



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-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:

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

Or via workspace config:

```yaml title="snpm-workspace.yaml"
onlyBuiltDependencies:
  - esbuild
  - sharp
ignoredBuiltDependencies:
  - fsevents
```

Root project and workspace-member lifecycle scripts (the ones you wrote) **do** run during install — `preinstall` → `install` → `postinstall` → `prepare`.

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

<Callout type="warn">
  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.
</Callout>

Minimum package age [#minimum-package-age]

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

```bash
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 [#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`):

```bash
snpm install --frozen-lockfile
```

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

Registry authentication [#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`.

```bash
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:

```bash
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`:

```ini title=".snpmrc"
always-auth=true
```

```bash
export SNPM_ALWAYS_AUTH=1
```

Scoped registries [#scoped-registries]

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

Tarball origin scoping [#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 [#audit]

```bash
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](/docs/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 [#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:

```bash
snpm pack --dry-run --list
```

Override a specific blocking finding for one publish:

```bash
snpm publish --allow-risk OVERSIZED_FILE
```

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

Best practices [#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 [#reporting-vulnerabilities]

If you find a security vulnerability in snpm itself, please report it via [GitHub Security Advisories](https://github.com/binbandit/snpm/security/advisories).
