npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@happyberg/pkg-quarantine

v0.2.4

Published

Unified quarantine policy for package managers — block recently-published packages to prevent supply-chain attacks

Readme

pkg-quarantine

[ pkg ] —— wait 4 days ——> [ install ] ← supply-chain blocker

npm version license node

Block freshly-published packages before they reach your machine.

One command configures a release-age cooldown across every supported package manager on your machine. Malicious versions of hijacked packages are typically detected and pulled within hours to a few days, so a 4-day hold sits comfortably outside that window.

npm install -g @happyberg/pkg-quarantine
quarantine init
quarantine --version   # 0.2.4

That's it. For managers with native release-age support (npm, pnpm, bun, uv, yarn, deno), every install now silently rejects anything published in the last 4 days. For managers without it (pip, gem, composer, cargo, hex), quarantine update enforces the same policy at update time.

Deep dive: TanStack and the day provenance attestation stopped being a defense. Full breakdown of the May 11, 2026 attack chain, the defenses that failed, and the cooldown that held.


Why this exists

Supply-chain attacks are arriving in waves, the defenses we built for them are being defeated one by one, and a release-age cooldown is the layer that still holds.

The May 2026 wave

On May 11, 2026 between 19:20 and 19:26 UTC, 84 malicious npm artifacts across 42 @tanstack packages were published. Within 48 hours the same campaign covered 172 packages across npm and PyPI (Wiz, Snyk, Socket, Endor Labs). @tanstack/react-router alone has ~12M weekly downloads.

The detail that matters: the malicious versions were signed. Attackers chained a pull_request_target Pwn Request, GitHub Actions cache poisoning, and OIDC token extraction from runner process memory to publish through TanStack's legitimate trusted-publishing pipeline. Sigstore verified the artifacts. Provenance attestation showed them as authentic. They were indistinguishable from legitimate by every signature check the ecosystem built for this case.

This is wave 4 of the Shai-Hulud campaign:

  • Sept 15, 2025: Shai-Hulud (original), self-replicating worm, hundreds of packages.
  • Nov 2025: Shai-Hulud 2.0, 25,000+ malicious GitHub repos, Zapier / PostHog / Postman hit.
  • March 2026: Trivy scanner npm packages, attributed to TeamPCP.
  • March 31, 2026: axios (attributed to UNC1069), plain-crypto-js trojan, ~100M weekly downloads.
  • April 1-8, 2026: Asurion impersonation campaign (sbxapps, asurion-hub-web, soluto-home-web, asurion-core), multi-stage credential harvester.
  • April 21, 2026: pgserve worm, cross-registry npm + PyPI, self-propagating.
  • April 22, 2026: @bitwarden/cli malicious for 93 minutes, attributed to TeamPCP.
  • May 11-13, 2026: TanStack / Mini Shai-Hulud wave 4, 172 packages, signed by the legitimate pipeline.

What still works

Detection time. Socket flagged the TanStack artifacts within 6 minutes of publication. By the time anyone ran a fresh install long enough for the malicious versions to enter a dependency tree the second time, the security community had already pulled them.

A 4-day release-age cooldown closes the rest of the gap. Fresh installs of a brand-new version are held back until detection has caught up. It is one defense the May 2026 wave did not subvert, because it does not depend on trusting any party, signature, or attestation. It just waits.

The native settings exist:

| Manager | Setting | |---------|---------| | npm 11.10+ | min-release-age | | pnpm | minimum-release-age | | bun | install.minimumReleaseAge | | uv | exclude-newer | | yarn (per project) | npmMinimalAgeGate | | deno (per project) | minimumDependencyAge |

They live in six config files with six different keys and six different units. pip, gem, composer, cargo, and hex still have no native install-time gate. And npm < 11.10 silently accepts min-release-age while ignoring it. Green config, zero protection.

pkg-quarantine configures all of them at once and verifies they are actually in effect.


Commands

quarantine init [managers...]        # Write/merge quarantine configs
quarantine audit [managers...]       # Traffic-light config report
quarantine status                    # Quick policy summary
quarantine update [managers...]      # Quarantine-aware global updater

quarantine init

Writes quarantine settings to each manager's global config file. Existing settings and auth tokens are preserved; it merges, never clobbers.

quarantine init                  # All detected managers
quarantine init npm pnpm uv      # Specific managers
quarantine init --dry-run        # Preview without writing
quarantine init --days 7         # Custom quarantine period

quarantine audit

Checks current config against desired quarantine state. Traffic-light output:

  npm     ✓ configured    min-release-age=4 days
  pnpm    ✓ configured    minimum-release-age=5760 minutes
  uv      ✗ missing       add exclude-newer to ~/.config/uv/uv.toml
  pip     ~ wrong value   only-binary not set

quarantine status

One-line-per-manager summary of quarantine posture.

quarantine update

Quarantine-aware global package updater. Checks each outdated package's publish date against the registry API before upgrading. Refuses to install anything that's too fresh unless you pass --force.

quarantine update                    # All managers
quarantine update npm                # Just npm
quarantine update --dry-run          # Preview without installing
quarantine update --force            # Bypass quarantine (prints a warning)

For AI agents

If you use Claude Code, Codex, Cursor, or any AI coding assistant that can install packages, this is especially for you.

AI agents install dependencies automatically, often without a human reviewing the exact version or publish date. That's fine for productivity. It's a supply-chain risk if the agent happens to install a freshly-hijacked package.

quarantine init enforces the policy at the package manager level, so it applies to every install, whether a human typed it or an agent did.

Setting it up once

# Install and configure everything:
npm install -g @happyberg/pkg-quarantine
quarantine init

After that, npm install, pip install, uv add, etc. will all silently enforce the quarantine with no further action needed.

Instructing your agent

Add this to your CLAUDE.md (or equivalent agent instructions file):

## Package Security

A 4-day quarantine policy is enforced via pkg-quarantine.
Versions published less than 4 days ago will not install.

Rules:
- Never run bare `npm install -g <pkg>@latest`.
  Use `quarantine update` instead.
- Before installing an unfamiliar package, check its
  publish date.
- If an install fails with a quarantine error, report
  it rather than bypassing it.
- The quarantine is a safety net, not an obstacle.

Verifying your agent respects it

quarantine audit        # Check all manager configs are set
quarantine status       # One-line status summary

If an agent tries to bypass quarantine with --ignore-scripts=false or --force, treat that as a signal to review what it's installing.

For CI pipelines

Add quarantine verification to your CI setup step:

- name: Verify quarantine policy
  run: |
    npm install -g @happyberg/pkg-quarantine
    quarantine audit --exit-code   # exits 1 if any manager is misconfigured

Supported managers

pkg-quarantine covers 13 package managers, but they fall into three honest tiers depending on what the underlying tool supports.

Tier 1: Native install-time quarantine

These managers ship a built-in release-age gate. init writes the setting and every install (manual or via an AI agent) is automatically protected.

| Manager | Mechanism | Notes | |---------|-----------|-------| | npm | min-release-age in ~/.npmrc | Requires npm ≥ 11.10.0 (Feb 2026). Earlier versions silently ignore the setting; quarantine audit warns. | | pnpm | minimum-release-age (minutes) in pnpm rc | Config lives at ~/Library/Preferences/pnpm/rc on macOS, ~/.config/pnpm/rc on Linux (or $XDG_CONFIG_HOME/pnpm/rc). | | bun | install.minimumReleaseAge (seconds) in bunfig.toml | — | | uv | exclude-newer = "N days" in uv.toml | — | | yarn | npmMinimalAgeGate in .yarnrc.yml | Per-project only. init prints the snippet to add. | | deno | minimumDependencyAge in deno.json | Per-project only. init prints the snippet to add. |

Tier 2: Update-time quarantine via quarantine update

These managers have no native release-age config, so install-time enforcement isn't possible. quarantine update checks the registry API before upgrading and refuses fresh versions. Bare pip install foo / gem install foo / etc. are not gated; you must use quarantine update.

| Manager | Registry checked | Hardening init configures | |---------|------------------|------------------------------| | pip | PyPI JSON API | only-binary = :all: (blocks source-build attacks; not a quarantine itself) | | gem | rubygems.org API | BUNDLE_TRUST___POLICY: MediumSecurity | | composer | packagist API | no-scripts, allow-plugins={}, secure-http | | cargo | crates.io API | Recommends cargo-audit | | hex | hex.pm API | Recommends mix_audit |

Composer 2.9+ already enables audit.block-insecure by default, so init no longer writes that setting.

Tier 3: Audit and recommendation only

These managers don't have a release-age model at all. init prints best-practice recommendations and audit reports posture; there is no enforcement.

| Manager | What init does | |---------|------------------| | go | Verifies sumdb is active, recommends govulncheck | | brew | Lists third-party taps as a posture warning |


Configuration

Global config at ~/.config/quarantine/config.toml:

quarantine_days = 4
managers = ["npm", "pnpm", "bun", "uv", "pip", "gem", "composer", "go", "brew", "cargo", "hex"]

Defaults apply if the file doesn't exist: 4-day hold, all managers.

The default managers array lists 11 entries because yarn and deno are gated per-project (their release-age settings live in .yarnrc.yml and deno.json, not a global rc). pkg-quarantine still counts them in its "13 supported managers" headline, but quarantine init for those prints the snippet you add to the project, instead of writing global config.


Security and contributing


Design

  • 2 runtime dependencies: commander + smol-toml. Zero transitive deps.
  • Dependency injection: All commands receive FileSystem and Shell interfaces. Tests use in-memory mocks; no disk or network in tests.
  • Auth-token safe: The custom .npmrc parser treats // lines as scoped registry entries (not comments), preserving all auth tokens intact.
  • Native fetch(): Registry API calls use Node's built-in fetch. No HTTP library dependency.
  • Merge, never clobber: init reads existing config before writing, preserving all unrelated settings.

Development

npm test              # Run tests
npm run build         # Build ESM + CJS
npm run lint          # Type-check
npm run test:watch    # Watch mode

License

MIT.