@mouadlouhichi/pnpms
v0.1.0
Published
A strict malware gate for pnpm installs, powered by OSV malicious-package advisories.
Maintainers
Readme
pnpms
A strict malware gate for
pnpminstalls.
pnpms is a small wrapper around pnpm. Before it installs dependencies, it resolves the requested dependency tree into pnpm-lock.yaml, checks every exact public npm package/version against OSV malicious-package advisories (MAL-*), and only then lets pnpm perform a frozen install.
It is designed to block known malicious or compromised npm releases, including a malicious transitive dependency introduced by an otherwise legitimate package.
What it does
pnpms add example-package
│
├─ 1. pnpm add example-package --lockfile-only --ignore-scripts
│ Resolves the exact tree; no node_modules installation stage.
│
├─ 2. Read every exact public npm name@version from pnpm-lock.yaml
│
├─ 3. Query OSV in batches and retain MAL-* advisories only
│
├─ malware found ──> restore package.json / pnpm-lock.yaml; exit 1
│
└─ clean ──────────> pnpm install --frozen-lockfileThe final frozen install prevents a second dependency-resolution decision after the malware check.
Install
pnpmsis not published yet in this repository. After you publish it, install a pinned/verified release rather than blindly executing an untrustednpxcommand.
npm install --global @mouadlouhichi/pnpms
# or, during development from this checkout:
npm linkThen use pnpms in place of pnpm for dependency-changing commands:
pnpms install
pnpms add zod
pnpms add -D typescript
pnpms update
pnpms fetchDefault security behavior
offlinePolicy is strict by default:
- A
MAL-*hit always blocks the operation. - An unreachable, malformed, or incomplete OSV response blocks the operation.
- A malicious hit remembered in the local cache also blocks while offline.
- Clean results are checked live by default (
cacheTtlHours: 0). Teams can opt into a bounded clean-result cache to reduce repeated API calls.
For a deliberate emergency override during an OSV outage only:
pnpms --pnpms-allow-offline installThat option never overrides a positive malware finding. It only allows an install when the intelligence service cannot answer. Use it sparingly and log its use in CI.
Force fresh checks rather than using cached clean results:
pnpms --pnpms-refresh installScan an existing lockfile
pnpms scan
pnpms --pnpms-refresh scanThis is useful in CI, after a newly disclosed supply-chain incident, or before a production deploy.
Configuration
Create a starter configuration file:
pnpms config initThis writes pnpms.config.json in the project/workspace root:
{
"offlinePolicy": "strict",
"cacheTtlHours": 0,
"requestTimeoutMs": 10000
}The same keys can be placed in the root package.json under pnpms:
{
"pnpms": {
"offlinePolicy": "strict",
"cacheTtlHours": 1,
"batchSize": 500
}
}Configuration precedence, from lowest to highest:
- built-in defaults;
package.json#pnpms;pnpms.config.json;PNPMS_*environment variables;--pnpms-*command-line options.
Useful environment variables:
PNPMS_OFFLINE_POLICY=strict # strict | warn
PNPMS_CACHE_TTL_HOURS=6
PNPMS_REQUEST_TIMEOUT_MS=10000
PNPMS_BATCH_SIZE=500
PNPMS_CACHE_DIR=/secure/cache/pnpmsUse pnpms config show to view the resolved configuration and pnpms cache clear to remove the local advisory cache.
Commands and compatibility
Guarded now
install,iadd,aupdate,upfetchscan(pnpms command)
Most other pnpm commands are delegated unchanged because they do not resolve/install a new registry dependency.
Deliberately refused in 0.x
These commands can download and execute a package without the project-lockfile transaction used by pnpms:
pnpm add -g/ other global install paths;pnpm dlx;pnpm create;pnpm exec --package ...;pnpm importandpnpm install-test;- guarded installs using
--no-lockfileor a custom--lockfile-dir.
pnpms fails rather than silently allowing those as bypasses. Future versions can add a separately designed, temporary-lockfile workflow for them.
Important limitations
pnpms is a known-malware gate, not a guarantee that a dependency is safe.
- A brand-new or undisclosed compromise is not in an advisory feed yet.
- OSV data can be delayed, corrected, or incomplete.
- A developer can bypass the check by running
pnpmdirectly. Enforcepnpmsin CI and team tooling. - Explicit git,
file:, local/workspace, and explicitly non-public registry tarballs are not sent to OSV. pnpms prints a warning for such entries rather than falsely identifying a private package with the same name as a public malicious package. - pnpms leaves normal pnpm lifecycle-script behavior unchanged during the final install. It prevents a known malicious version before that phase; it does not replace pnpm script-approval or sandboxing controls.
For stronger defense in depth, also commit lockfiles, use --frozen-lockfile in CI, limit registry access, pin trusted publishing, use pnpm’s build-script approval controls, and set a release-age cooldown for new dependencies.
CI example
- name: Install guarded dependencies
run: pnpms install --frozen-lockfile
- name: Re-scan lockfile before release
run: pnpms --pnpms-refresh scanBecause a frozen install already has an exact lockfile, pnpms scans it first and delegates to pnpm only if no known malicious version is found.
Development
Requirements: Node.js 20+ and pnpm 9+ for real projects.
npm install --ignore-scripts
npm test
npm run check
npm run pack:checkThe test suite covers lockfile parsing, OSV response validation, cache behavior, strict outage behavior, the malicious-package block, and rollback of staged files.
Publishing checklist
Confirm that you are signed in to the intended npm scope:
npm whoami.Update
package.jsonrepository metadata and version.Run
npm audit --omit=dev,npm test,npm run check, andnpm run pack:check.Inspect the resulting tarball with
npm pack.For the first interactive release, publish with your account’s security-key 2FA enabled:
npm publish --access publicFor later automated releases, configure an npm trusted publisher and publish from that protected CI workflow with provenance enabled.
License
MIT. See LICENSE.
