@itsl-solutions/npm-registry-shield
v0.2.0
Published
Registry proxy that quarantines recently published npm package versions
Maintainers
Readme
npm-registry-shield
Registry proxy that quarantines recently published npm package versions. Protects against supply chain attacks by ensuring you only install versions that have been on the registry for a configurable number of days.
How it works
npm-registry-shield sits between your package manager and the npm registry. When you run npm install, it intercepts the package metadata and strips out versions published within the quarantine window (default: 3 days). Your package manager never sees the risky versions - it resolves to the newest safe version automatically.
npm install express
|
v
localhost:4873 (npm-registry-shield)
|
|- fetch metadata from registry.npmjs.org
|- strip versions published < 3 days ago
|- return filtered metadata
|
v
npm resolves to newest safe versionAll transitive dependencies are also protected - every package at every depth resolves through the proxy.
What gets filtered, what passes through
| Request type | Behavior |
|--------------|----------|
| GET /<pkg> (full packument) | Versions inside the quarantine window are stripped. dist-tags are rewritten to point at the newest surviving version. If every version is quarantined, returns 404. |
| GET /<pkg>/<version> (single version metadata) | Passed through unchanged. If the version is quarantined, a warning is logged and counted in stats but the response is not blocked. |
| Tarball downloads (/<pkg>/-/<file>.tgz) | Forwarded upstream as-is. |
| Search, login, publish, anything else | Forwarded upstream as-is. |
Packuments are cached in memory for 10 minutes (configurable via cacheTtlMinutes). The cache is cleared automatically when you run allow or remove.
Requirements
- bun >= 1.0
bun is the only runtime. The proxy itself runs under bun, and the published package ships TypeScript sources executed directly by bun.
Install
bun add -g @itsl-solutions/npm-registry-shieldQuick start
npm-registry-shield startThat's it. On macOS this installs a launchd service and starts it. On Linux it installs a systemd user service. The proxy runs in the background, survives reboots, and rewrites your ~/.npmrc (and ~/.yarnrc.yml if present) to point at it.
Your npm, pnpm, yarn, and bun install commands are now protected.
To stop:
npm-registry-shield stopThis unloads the service and restores your original ~/.npmrc.
To run in the foreground (for debugging or one-off use):
npm-registry-shield start --foregroundLogs are written to ~/.npm-shield/daemon.log. Configuration is at ~/.npm-shield/config.json.
Manual daemon install
If you want to write the service file yourself (custom flags, custom path, etc.):
# macOS
npm-registry-shield daemon-template launchd > ~/Library/LaunchAgents/com.npm-registry-shield.plist
launchctl load ~/Library/LaunchAgents/com.npm-registry-shield.plist
# Linux
mkdir -p ~/.config/systemd/user
npm-registry-shield daemon-template systemd > ~/.config/systemd/user/npm-registry-shield.service
systemctl --user daemon-reload && systemctl --user enable --now npm-registry-shieldCommands
npm-registry-shield start [options] Install + start the background daemon (survives reboots)
npm-registry-shield stop Stop the daemon and restore package manager configs
npm-registry-shield status Show running state and config
npm-registry-shield allow <pkg> Skip quarantine for a package
npm-registry-shield allow <pkg>@<ver> Allow specific version (with warning)
npm-registry-shield remove <pattern> Remove from passthrough list
npm-registry-shield list Show passthrough entries
npm-registry-shield config get <key> Show config value
npm-registry-shield config set <k> <v> Update config value
npm-registry-shield stats Show statistics summary
npm-registry-shield daemon-template <launchd|systemd>
Print a service unit you can install for persistenceStart options
-f, --foreground Run in this terminal instead of installing the daemon
-q, --quarantine=<days> Override quarantine period (default: 3)
-p, --port=<port> Override proxy port (default: 4873)
-u, --upstream=<url> Override upstream registry URL
-d, --dashboard Enable the web dashboard (default)
--no-dashboard Disable the web dashboardValue flags accept long or short, with or without =: --port=4873, --port 4873, -p 4873, -p4873. A value flag with no value (or one immediately followed by another flag) is rejected with an error rather than parsed silently. Short flags cannot be bundled (-fd); pass them separately.
Package manager support
| Manager | Supported | How |
|---------|-----------|-----|
| npm | Yes | .npmrc registry setting |
| pnpm | Yes | .npmrc registry setting |
| yarn v1 | Yes | .npmrc registry setting |
| yarn v2+ | Yes | .yarnrc.yml npmRegistryServer |
| bun | Yes | .npmrc registry setting |
| deno | Manual | export DENO_NPM_REGISTRY=http://localhost:4873 |
Dashboard
When the proxy is running, open http://localhost:4873/ for a live stats dashboard showing total requests, hidden (quarantined) versions, blocked installs, and a per-package breakdown.
Each package row also shows:
- Tool that fetched it (
npm,pnpm,yarn,bun,deno,browser, ...) detected from the requestUser-Agent. - Origin path of the process that made the request (working directory) plus the launcher app that started the chain (e.g.
iTerm2,Claude,Code Helper), so you can tell apart an explicitnpm installfrom a tool that quietly spawnednpmin the background.
By default only packages with more than 10 requests are shown; the "Show all" button in the Packages header reveals the long tail.
Origin attribution uses lsof + ps and works on macOS today. On Linux and other platforms the proxy still serves stats - the origin/tool columns are simply empty.
Stats CLI
For a terminal-friendly snapshot:
npm-registry-shield statsPrints the totals, the top 20 packages by request count with aligned columns, and the legend explaining what each column means. Reads from ~/.npm-shield/stats.json, which the daemon flushes every 30 seconds.
Security & networking
The proxy listens on 127.0.0.1 only, so nothing on your LAN can reach it. Upstream traffic still goes out over HTTPS to whatever registry you configured (https://registry.npmjs.org by default).
Configuration
Config is stored at ~/.npm-shield/config.json:
{
"port": 4873,
"upstream": "https://registry.npmjs.org",
"quarantineDays": 3,
"cacheTtlMinutes": 10,
"dashboard": true,
"passthrough": []
}Passthrough list
Skip quarantine for trusted packages:
# Entire package
npm-registry-shield allow lodash
# Scoped packages
npm-registry-shield allow "@my-org/*"
# Specific version (allowed with a warning)
npm-registry-shield allow [email protected]What happens when...
npm install express - Resolves to the newest version that is at least 3 days old.
npm install [email protected] where 5.0.0 is too new - npm fetches the full packument first to resolve, sees 5.0.0 missing, and fails. Run npm-registry-shield allow [email protected] to permit it.
A brand-new package (all versions < 3 days old) - The packument response is a 404 with an explanatory error. Add the package to the passthrough list to allow it.
npm ci with a lockfile pointing at a quarantined version - The packument resolution step strips the version, so npm errors. Catches dependencies your teammates added that haven't aged past the quarantine window yet. Add the offending versions to passthrough or wait them out.
npm view [email protected] - Hits the single-version endpoint, which passes through with a warning logged in ~/.npm-shield/daemon.log and counted under "warnings" in stats. Useful for inspecting fresh versions without committing to install them.
The proxy is down - .npmrc points at a dead server, npm commands fail. Run npm-registry-shield stop to restore your config. If the CLI is unreachable, manually remove the registry= line from ~/.npmrc.
Recovery
If something goes wrong and npm commands fail:
# Restore configs from backup
npm-registry-shield stop
# If the CLI is unavailable, remove this line manually:
# registry=http://localhost:4873
nano ~/.npmrcDevelopment
If you just want to use the tool, see Install above (bun add -g @itsl-solutions/npm-registry-shield). This section is for hacking on the source.
Working from a local clone of this package:
cd npm-registry-shield
bun install
bun link
exec $SHELL # reload shell so the new global bin is on PATH
npm-registry-shield startbun link symlinks the package into bun's global bin dir pointing at your clone. Edit src/*.ts and the change is live on the next run - no rebuild step.
Note: after bun link, the npm-registry-shield command is not available in the same shell session that ran the link - shells cache PATH lookups. Open a new terminal or run exec $SHELL (or hash -r in bash/zsh) to refresh.
Run tests with bun test.
License
MIT
