Protection against npm supply-chain attacks.
One command, three layers, no account.
curl -fsSL https://raw.githubusercontent.com/orlyjamie/internetcondom/main/install.sh | shOr read it first:
curl -fsSL -o install.sh https://raw.githubusercontent.com/orlyjamie/internetcondom/main/install.sh
less install.sh && sh install.shOn 2026-08-04 the keyv and cacheable namespaces were compromised by pushing
straight to main and cutting an immediate release, so the poisoned versions
published with valid GitHub Actions provenance. Shai-Hulud spread to 868
packages across 1381 versions within hours, carrying a credential-stealing worm.
No single control would have stopped that. Each of these covers a different blind spot:
| Layer | Kind | Stops | Blind to |
|---|---|---|---|
| Socket Firewall | reactive | known-malicious packages, at the network | anything newer than the feed |
| Minimum release age | proactive | everything published in the last N days | a patient attacker |
ignore-scripts |
structural | the execution vector itself | nothing — but it is not a resolution control |
Socket flagged keyv@6.0.0 about six minutes after publication. Layer 2 covers
that window. Layer 3 covers both: Shai-Hulud propagates through a preinstall
hook, and a compromised package is inert until something runs on install.
-
Installs
sfw(Socket Firewall Free) to~/.local/bin— no sudo, no account, no API key. Falls back tonpm i -g sfwif the binary download fails. -
Wraps your package managers with shell functions so
sfwapplies without anyone remembering to type it:npm npx pnpm yarn pip pip3 uv cargo. -
Sets a minimum release age of 14 days, per manager, version-gated:
Manager Key File Unit Needs npm min-release-age~/.npmrcdays npm 11.10.0+ pnpm minimumReleaseAge~/.config/pnpm/config.yamlminutes pnpm 10.16+ yarn npmMinimalAgeGatevia yarn config --homeminutes Yarn 4.10.0+ uv exclude-newer~/.config/uv/uv.tomlduration recent uv Units differ per manager. pip and cargo have no native equivalent.
-
Sets
ignore-scripts=truein~/.npmrc.
Skips anything you do not have installed, and anything too old to support the setting, and says so. Skips are not failures.
| Ecosystem | Screened by sfw | Release age | ignore-scripts |
|---|---|---|---|
| npm / pnpm / yarn | ✅ | ✅ | ✅ |
| pip | ✅ | ✗ none exists | ✗ |
| uv | ✅ | ✅ | ✗ |
| cargo | ✅ | ✗ none exists | ✗ |
| bun / deno | ✗ not supported | ✗ | ✗ |
JavaScript gets all three layers. Python and Rust get network screening, plus a
cooldown on uv. ignore-scripts is npm-family only.
sfw's detection is server-side, so an older binary is not a stale feed. Update
it for fixes and new ecosystem support, not for detection coverage.
The installer pulls from releases/latest/download. To refresh in place:
sh install.sh --updateIt updates sfw the same way it arrived — if npm installed it, --update uses
npm i -g sfw@latest rather than dropping a binary that would silently shadow it.
Weekly, unattended:
# cron
0 9 * * 1 /path/to/install.sh --update >/dev/null 2>&1# macOS launchd — ~/Library/LaunchAgents/com.internetcondom.update.plist
launchctl load ~/Library/LaunchAgents/com.internetcondom.update.plistRe-running the whole script is always safe: it is idempotent, replaces its own
marked block rather than appending a second copy, and backs up every file it
touches once, to <file>.internetcondom.bak.
Package managers themselves are not auto-upgraded. The script reports which one is too old and what version it needs.
Machine-level config protects you. Committed config protects everyone who clones, plus CI. Both matter.
# pnpm-workspace.yaml
minimumReleaseAge: 20160 # 14 days, in minutes
minimumReleaseAgeExclude:
- '@yourorg/*'# .npmrc
ignore-scripts=true
min-release-age=14# .yarnrc.yml
npmMinimalAgeGate: 20160In CI and Docker, use the lockfile-respecting install and keep the flag explicit:
COPY .npmrc package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfileIf a dependency genuinely needs its install hook — ffmpeg-static, puppeteer
and friends fetch binaries that way — allow it by name rather than switching the
protection off wholesale:
npm rebuild ffmpeg-staticAdd to AGENTS.md / CLAUDE.md:
## Dependencies
- Use pnpm. Never bypass the minimumReleaseAge setting in pnpm-workspace.yaml.
- Never pass --ignore-scripts=false, --no-strict-peer-dependencies, or edit
.npmrc to weaken it.
- Adding a dependency requires asking first. Prefer node built-ins and packages
already in package.json.
- `npm rebuild <pkg>` is the sanctioned way to run a needed install hook.Instructions in a file are guidance an agent can miss. The shell wrapper is what enforces it.
- Shell functions are interactive-only. A
#!/bin/shscript, a Makefile, or a Docker build invoking npm bypasses them. Commit the per-repo config for those. bunanddenoare not wrapped. Socket Firewall Free covers npm, yarn, pnpm, pip, uv and cargo.sfwpasses unknown commands straight through, so wrapping bun would look like protection while providing none.ignore-scriptsbreaks packages that need install hooks. By design. Usenpm rebuild <pkg>.- A 14-day window is not a proof. It raises the cost and buys detection time. A sufficiently patient attacker waits it out.
command npm ...bypasses everything. Deliberate. A guard that breaks real work gets deleted rather than fixed.sfwis a closed-source binary under PolyForm Shield. Skip layer 1 if that is not an acceptable trade — the layers are independent.
sh install.sh --uninstallRemoves the shell block and the ~/.npmrc keys it added, leaving everything else
in those files untouched. sfw itself and the pnpm/yarn settings are left alone —
delete them by hand if you want them gone.
GPL-3.0-or-later. Copyright (C) 2026 Jamieson O'Reilly. See LICENSE.
