npm vs JSR vs npmx: How the Registry Model Shapes Your Supply Chain Risk

If you have written JavaScript in the last decade, the question was never "which registry" but "how do I keep npm from eating my build." npm is the registry, the CLI, and the culture all at once, so alternatives spent years as hobby projects.

That has changed: JSR is the JavaScript Registry that Deno built, and it is a serious working counter-model; npmx keeps appearing next to both in the same sentence even though it is a package browser, not a registry. And in the last two years the stakes moved from "a typosquatted package is annoying" to "a compromised package lands malware in a hundred thousand builds within hours."

This post is the registry layer, not the package manager layer. We already covered npm vs pnpm as package managers: node_modules layout, workspaces, install-time defaults. Here I compare where packages live, who can claim a name, whether a published version can change, and what proof you can get. Then I map the attacks that are actually being used in 2025 and 2026, and the layered defenses that hold up against them.

The short version

  • npm is a registry plus a CLI. More than two million packages in one shared namespace, distributed as tarballs, lifecycle scripts executed at install time by default, and a 72-hour unpublish window. Registry signatures are applied to every published version automatically; verifying them, and checking provenance, is what is opt-in.
  • JSR is a registry only. ESM-only and TypeScript-first, every package lives under a scoped name tied to a GitHub-linked identity, versions are immutable, and publishing from GitHub Actions generates Sigstore provenance automatically. It is open source (MIT) and designed to complement npm, not replace it.
  • npmx is not a third registry. The npmx most people mean (npmx.dev) is an open-source browser interface over the npm registry. It hosts nothing and vouches for nothing. There is also an unrelated edge-runtime tool with the same name.
  • The other alternatives are CDNs and private registries. esm.sh, UNPKG, and jsDelivr serve npm's bytes; Verdaccio, GitHub Packages, Artifactory, Nexus, Cloudsmith, and CodeArtifact are where teams get policy, allowlists, and malware scanning.
  • The model decides your baseline attack surface: install-time execution, name-claiming, version mutability, identity binding, and provenance. JSR removes or shrinks several of these by design. No registry removes account takeover or a malicious release by a trusted publisher.
  • Defense is layered and mostly registry-agnostic: lockfiles plus npm ci, verified signatures and provenance, behavior scanners, private registry boundaries, 2FA and scoped tokens, and ruthless dependency hygiene.
Verified
  • npmcurrent CLI, bundled with Node LTS
  • JSRregistry docs and FAQ, fetched 2026-09-25
  • npmxalpha (npmx.dev, March 2026)

Registry claims checked against jsr.io/docs/trust, /docs/scopes, /docs/packages, /docs/npm-compatibility and the JSR FAQ, npm's audit signatures and access-token documentation, the axios incident timelines (GTIG, Socket, Datadog, GetSafety), OSV and Snyk node-ipc advisories, Sonatype's 2026 report, and OpenSSF's Mini Shai-Hulud analysis on this date.

What the three names actually are

npm: the registry and the default client

npm is a client/server pair that users have never been able to separate: the CLI talks to the npm registry over HTTPS, resolves the graph described by package.json ranges plus the lockfile, and installs tarballs. Two properties define its security posture.

First, the namespace is shared and claim-based. Any account can publish any name that is not taken, scoped names (@scope/name) are optional, and popularity signals (download counts, stars, weekly charts) are the de facto trust heuristic. That is the soil for typosquatting and dependency confusion.

Second, installing executes code. npm runs the preinstall, install, and postinstall scripts of every dependency by default, and it has shipped that way for over a decade. It is the single most exploited execution channel in the ecosystem, which is why newer package managers made "no scripts until approved" their headline feature.

npm has added real supply chain tooling in the last few years: registry signatures on every published version, with npm audit signatures left to you to run, publish provenance via GitHub Actions or trusted publishing (SLSA), verified publisher badges, and granular access tokens. Late 2025 tightened the account side hard: classic long-lived tokens were revoked in favor of short-lived login sessions and capped granular tokens, publishing requires 2FA or an explicit bypass-2FA token, and new packages ship with 2FA on by default. The honest summary: npm the registry has grown the right levers; the publisher-auth ones are now mandatory, while consumer-side verification is still opt-in.

JSR: the registry built to argue with npm

JSR is a registry, not a package manager. It stores packages and serves them; you consume with Deno jsr: imports, or with pnpm 10.9+, Yarn 4.9+, or vlt natively, or through npx jsr add with npm and older tools. It is open source under the MIT license. Its security model is a set of deliberate architectural answers to npm's weaknesses:

  • Identity-bound namespaces. Every JSR package lives in a scope, and JSR accounts sign in through GitHub. Scope names are globally unique; package names only need to be unique within their scope. A scope has no single owner: it is managed by one or more admins, and whoever created it is simply its first admin. There is no anonymous publishing, and name squatting is against policy.
  • Immutable versions. Published source generally cannot be deleted. You can yank a version (hidden from default views yet still installed for existing dependents), or delete a version within the first 24 hours, and only if it has fewer than ten downloads and no published package depends on it. npm-compat tarballs are served from URLs that never change, and lockfiles record those URLs, so your lockfile stays a byte-level contract.
  • No install-time execution channel. The publish manifest carries a limited field set, and the generated npm tarball contains the exports mapping, not a scripts block. jsr: imports load source modules directly with no install step at all. A malicious JSR package can still run code when your app imports it; it just cannot hand the package manager a preinstall script to run.
  • Provenance by default. Versions published from GitHub Actions automatically get a Sigstore provenance statement (SLSA framework, logged to the Rekor transparency log). Token-based publishing from other CI produces none. GitHub Actions publishing is already restricted to workflows triggered by scope members by default; scope admins can go further and require CI-only publishing.
  • npm compatibility as a bridge, with limits. The compatibility layer rewrites every JSR package into an npm tarball under the @jsr scope at npm.jsr.io, transpiling TypeScript to JavaScript plus generated .d.ts. That means a JSR package's transitive npm dependencies still flow through your package manager with your manager's script policy. JSR does not sandbox npm; it inherits it.

npmx: one short name, three different things

The name npmx means different things depending on where you saw it, and none of them is a registry:

  • npmx.dev is an open-source, community-driven browser for the npm registry: fast search, package pages, and comparison views over npm's own data. It launched its alpha in March 2026. It does not host, sign, or verify packages - and it is not purely read-only: an optional local connector drives your own npm CLI for package, team, and org administration, so using that flow puts your npm credentials behind a third-party browser UI.
  • dot-do/npmx is an unrelated project that runs npm install and npx-style commands on edge runtimes without Node installed.
  • Older npm lore uses npmx as shorthand for putting $(npm bin) on your PATH, from the original npx announcement.

And if you actually meant npx, that is npm's package runner, a different layer: it executes a package's binaries on demand without adding it to your project. So when someone frames a three-way "npm vs JSR vs npmx," the accurate unpacking is: npm is the incumbent registry, JSR is a real alternative registry, and npmx is a nicer window into npm.

Use it to research; reading it changes no trust boundary - though its npm-CLI-backed admin connector is a credentials operation, not reading. (The px tools are handy, but any package-search page is only as safe as the links you click, and malvertising on package-result pages is a real social-engineering channel.)

If npmx is a UI, what are the real alternatives?

Registry or tool

Type

Hosts packages?

What it changes for you

npm

public registry (default)

yes

The baseline: open namespace, tarballs, lifecycle scripts, optional provenance

JSR

public registry, open source (MIT)

yes

Identity-bound scopes, immutable versions, provenance by default, ESM-only

deno.land/x

legacy registry for Deno

yes (GitHub-backed)

Predecessor to JSR; fine to consume, not the place for new publishing

esm.sh / UNPKG / jsDelivr

CDNs

no, they serve npm bytes

Caching and availability only; no trust change, keep integrity checks

pkg.pr.new

preview publisher

temporary

Instant preview releases from GitHub for testing, not production supply

Verdaccio

self-hosted proxy registry

cache and proxy

Allowlists, caching, audit trail, offline resilience

GitHub Packages / GitLab

private registries

yes (private scopes)

Access control for internal packages, permissions tied to organizations

Artifactory / Nexus / Cloudsmith

enterprise proxies

cache and proxy

Malware scanning, policy enforcement, egress control at scale

npmmirror

public mirror

mirror

Faster downloads in some regions; you add a middle party, so verify integrity

The pattern worth noticing: the only names that host packages are npm, JSR, deno.land/x, and the private registry products. Everything else either mirrors npm's data (CDNs, npmmirror) or sits in front of it (proxies). If your goal is a fundamentally different supply chain, the lever is a private registry boundary, not a faster CDN.

What the model changes for supply chain security

Namespace and identity

On npm, claiming a name is the attack. An attacker publishes a lookalike (react-фlags, an extra hyphen, a dot instead of a dash) and waits for a typo. Scoped official namespaces shrink the surface, but any free unscoped name remains claimable. On JSR, names live under scopes that trace to a GitHub identity, and squatting is against policy. An attacker cannot quietly create a fresh @google or @axios scope on JSR; impersonation has to be discovered socially instead of created anonymously.

Version immutability

npm allows authors to unpublish a version within the first 72 hours, and beyond that if the package has no dependents, little download volume, and a single owner.

The earlier unrestricted mutability is what left-pad exposed in 2016: one removed 11-line package broke builds across the ecosystem, and npm's unpublish window is the response to left-pad, not the rule left-pad broke.

JSR makes versions immutable: yank hides a version from new installs without breaking existing dependents, deletion is limited to the first 24 hours and only for versions with fewer than ten downloads and no dependents, and npm-compat tarball URLs are permanent and recorded in lockfiles. What this changes for an attacker: they cannot silently republish over the same version number, and your lockfile keeps pointing at the bytes you reviewed. The honest caveat is that JSR may regenerate a new npm-compat tarball "revision" for the same version to fix compatibility, which means the artifact behind a version can change over time on the npm side; old tarball URLs stay live, and lockfile-pinned URLs preserve your copy.

Execution model

This is the biggest single lever. npm installs run lifecycle scripts by default, so a malicious package can execute code on every machine that pulls it. That channel has produced the worst incidents of the last two years: the axios compromise (March 2026) rode in through a plain-crypto-js dependency with a postinstall remote access trojan, and the Shai-Hulud campaigns of 2025 hid payloads in preinstall scripts.

JSR removes the channel from its own packages: there is no scripts field in the publish manifest, and jsr: imports have no install step at all. The npm-compat tarball contains only the exports mapping, so even when you install a JSR package into node_modules, there is no install script for the package manager to run. The limit is real though: JSR packages can depend on npm packages, and those dependencies arrive through your package manager with whatever script policy that manager enforces. Moving your code to JSR does not fix a hostile npm transitive dependency.

Provenance and verification

Provenance answers "who claims to have built this and from what." npm makes it opt-in: publishers who release from GitHub Actions with OIDC get SLSA provenance attached, and npm audit signatures verifies registry signatures and, where present, attestation bundles. JSR makes it default for its strongest publishing path: any version published from GitHub Actions gets a Sigstore provenance statement automatically, and scope admins can require CI-only publishing so a phished laptop cannot publish locally.

The 2026 TanStack incident (Mini Shai-Hulud) is the required caution: 84 artifacts across 42 @tanstack packages shipped with attestations that many reports described as SLSA Build Level 3 - cryptographically valid, because the attacker extracted the legitimate OIDC token from runner memory and signed through Sigstore after poisoning a shared cache. OpenSSF's post adds the correction that matters: the build platform did not meet L3's isolation requirements, a compliant L3 platform would have blocked this vector, and npm's built-in provenance reaches only Build L2. To quote OpenSSF: "a signed artifact is not necessarily a trustworthy one." An attestation is a record of what the build system observed from inside its own environment; let attacker code run in that environment and the record faithfully describes a compromised build. Provenance is evidence for a policy, never a substitute for one.

Popularity and trust signals

npm's de facto trust heuristic is popularity, and popularity is farmed: Check Point Research documented the Stargazers Ghost Network, over 3,000 GitHub accounts starring, forking, and subscribing to malicious repositories to make them look legitimate, with more than 2,200 malicious repositories observed. Treat download counts, stars, and even JSR's package score as friction, not proof. The only durable signals are identity you can verify (publisher, organization, provenance) and behavior you can scan.

What stays the same

Every registry design still ends at the human layer. The account-takeover playbook works on any platform: phishing a maintainer with a convincing npm-branded email (the qix compromise in 2025), reusing leaked credentials (eslint-scope in 2018), or quietly exploiting a dormant high-traffic package (the node-ipc compromise in May 2026, after a 21-month publishing gap). No namespace scheme, immutability rule, or provenance default fixes a phished author, which is why the defense section is the longest part of this post.

npm vs JSR at the registry layer
FeaturenpmJSR
Kind of thingRegistry plus a CLI clientRegistry only, consumed by existing package managers
Namespace and identityGlobal namespace; any free name, scoped names optionalEvery package under an @scope tied to a GitHub-linked identity
Module formatCommonJS and ESMESM only, TypeScript source welcomed
Install-time scriptspreinstall/install/postinstall run by defaultNo scripts field in the publish manifest; jsr: imports have no install step
Version mutability72-hour unpublish windowVersions immutable; yank or limited 24-hour deletion
ProvenanceOpt-in via GitHub Actions (SLSA)Automatic Sigstore provenance for GitHub Actions publishes
Publishing authLogin or publish token; OIDC for provenanceTokenless OIDC from GitHub Actions; tokens elsewhere; CI-only optional
Consuming from Node toolingDirectlynpm-compat layer at npm.jsr.io (@jsr scope)
Ecosystem sizeTwo million-plus packagesSmaller, modern TypeScript focused
  • Kind of thing

    npm
    Registry plus a CLI client
    JSR
    Registry only, consumed by existing package managers
  • Namespace and identity

    npm
    Global namespace; any free name, scoped names optional
    JSR
    Every package under an @scope tied to a GitHub-linked identity
  • Module format

    npm
    CommonJS and ESM
    JSR
    ESM only, TypeScript source welcomed
  • Install-time scripts

    npm
    preinstall/install/postinstall run by default
    JSR
    No scripts field in the publish manifest; jsr: imports have no install step
  • Version mutability

    npm
    72-hour unpublish window
    JSR
    Versions immutable; yank or limited 24-hour deletion
  • Provenance

    npm
    Opt-in via GitHub Actions (SLSA)
    JSR
    Automatic Sigstore provenance for GitHub Actions publishes
  • Publishing auth

    npm
    Login or publish token; OIDC for provenance
    JSR
    Tokenless OIDC from GitHub Actions; tokens elsewhere; CI-only optional
  • Consuming from Node tooling

    npm
    Directly
    JSR
    npm-compat layer at npm.jsr.io (@jsr scope)
  • Ecosystem size

    npm
    Two million-plus packages
    JSR
    Smaller, modern TypeScript focused

The attacks that actually happen today

Attack class

Registry weakness it exploits

Where you defend

Typosquatting and lookalikes

Open namespace, unbounded name squatting

Naming hygiene, scoped names, verified publisher

Dependency confusion

Same name can exist internally and publicly

Private registry pinning, scoped registries, allowlists

Account takeover (phishing, reused passwords)

Identity not enforced at publish

FIDO2 2FA, scoped short-lived tokens, dormant-account cleanup

Malicious release from a trusted publisher

Publish right of a compromised author

Release cooldowns, behavior scanning, signatures

Install-script payloads

Lifecycle scripts run by default

ignore-scripts, build allowlist, sandboxed CI

Version tampering and mutability

Unpublish and repackage window

Immutable registries (JSR), committed lockfiles

Provenance forgery

Build platform compromise

Hardened CI, OIDC hygiene, minimal release-job permissions

Reputation laundering

Popularity used as a trust heuristic

Ignore stars and download counts, verify maintainers

Metadata mismatch (manifest confusion)

Manifest and README diverge from installed artifact

Audit installed bytes, review lockfile diffs, scanners

AI-assisted targeting

Coding agents add packages by name and description

Same as typosquatting; verify before an agent installs a dep

Three incidents from 2026 show how these combine in practice.

axios, March 31, 2026. Between roughly 00:21 and 03:40 UTC in the early hours of the day, malicious axios versions (1.14.1 and 0.30.4) were published from a compromised maintainer account, each silently adding a dependency named plain-crypto-js@4.2.1 whose postinstall script was a cross-platform remote access trojan. npm administration removed the versions and revoked the compromised account's tokens by roughly 03:40 UTC, about three and a half hours after the first publish. Google's threat intelligence group (GTIG) later attributed the attack to UNC1069, a financially motivated cluster tied to North Korea. Anyone who installed in that window got code execution on the build. The lesson is the timeline: malware lives and dies in hours, which is why cooldowns and script blocking matter more than reactive scanning for this class.

node-ipc, May 2026. Three versions (9.1.6, 9.2.3, and 12.0.1) of a dormant-but-popular IPC library were published on May 14, 2026 with an obfuscated credential-stealing payload compiled into its CommonJS bundle, triggered on require("node-ipc"). OSV attributes the publishes to a compromised maintainer account (atiertant), reportedly seized by re-registering the email domain behind its npm recovery address, not to a pipeline attack. It is the recurring dormant-package pattern: attackers wait for a package with huge transitive reach and little maintenance, then slip a release in.

TanStack and Mini Shai-Hulud, May 2026. Attackers compromised 84 npm package artifacts across 42 @tanstack packages, and the worm spread to over 170 packages across @mistralai, @uipath, and other namespaces. The malicious releases carried cryptographically valid provenance attestations that many reports described as SLSA Build Level 3, because the attacker lifted the legitimate OIDC token from runner memory and signed through Sigstore after poisoning a shared build cache. OpenSSF's analysis corrects the framing: the attestations were genuine, but the build platform never met L3's isolation requirements, which npm's own built-in provenance (Build L2) does not reach either, and a compliant L3 platform would have blocked the cache-poisoning vector. The payload also rode in through an install-script side door: an optionalDependencies entry resolving to an orphaned git commit whose prepare lifecycle hook ran the malware. This is the hardest case for defenders: every cryptographic control worked exactly as designed, and the build platform itself was the weak link. In OpenSSF's words, "a signed artifact is not necessarily a trustworthy one."

The older classics still matter because the pattern is unchanged, only the delivery details: event-stream in 2018 (attacker gained maintainer access and shipped a backdoored dependency that targeted cryptocurrency wallets), eslint-scope in 2018 (compromised account, 3.7.2 harvested .npmrc tokens), ua-parser-js in 2021 (a hijacked maintainer account published malicious releases that dropped cryptominers and a password stealer, on a package pulled roughly 8 million times a week), colors and faker in 2022 (protestware from a burned-out maintainer: colors shipped an infinite loop that DoS-ed every process loading it, while faker was gutted into an empty release), and node-ipc's 2022 peacenotwar variant (protestware overwriting files). Sonatype counted more than 454,600 new malicious open-source packages in 2025, pushing the cumulative total it has blocked past 1.233 million, and reported that over 99 percent of 2025's open-source malware landed on npm. Volume like that is why the defense has to be systemic, not a per-incident response.

Defense measures, in layers

None of this is exotic. It is a stack of cheap defaults that compound.

Layer 0, dependencies: reduce the graph. Every dependency is a standing code-execution grant. Uninstall what you do not use, prefer direct dependencies you actually read over deep transitive trees, pin versions where it matters, and add a release cooldown so brand-new versions (the axios and node-ipc windows) age before your CI accepts them. pnpm's minimumReleaseAge (on by default since pnpm 11: 1440 minutes, one day; on pnpm 10 it defaulted to off), npm's min-release-age (opt-in, and measured in days, not minutes - 7 is a week, so copying pnpm's 10080 into .npmrc buys you about 27 years), and Bun's minimumReleaseAge (in seconds) are all the same idea; see the npm vs pnpm post for the exact settings.

Layer 1, install fidelity: lockfiles are security artifacts. Commit package-lock.json (or yarn.lock, pnpm-lock.yaml, bun.lock, Bun's default text lockfile since 1.2), install with npm ci in CI so the lockfile is authoritative, and review every lockfile diff in a pull request. A poisoned lockfile happens when a developer runs npm install inside an incident window, so combine lockfiles with the cooldowns above. lockfile-lint rejects lockfiles pointing at exotic hosts or plain HTTP.

Layer 2, execution: stop install-time code you did not approve. This is the highest-value single control. Default to no scripts, then approve the few packages that genuinely need a build (esbuild, sharp, and friends) after reading what they run. npm honors ignore-scripts, pnpm blocks dependency build scripts by default and walks you through an allowlist, and Bun uses trusted dependencies. If a package that never needed a build suddenly adds one, that is a red flag, not a convenience.

The TanStack payload used a side door of exactly this kind: an optionalDependencies entry resolving to an orphaned git commit whose prepare hook ran the dropper - which is why pnpm 11 blocks git, URL, and local subdependency resolutions by default via blockExoticSubdeps.

Also remember that editors (and their extensions), git hooks, and CI workflow files execute third-party code with your credentials; my Docker sandboxes for AI agents post makes the same point about package.json scripts as an execution channel review tools miss.

Hardened local baseline (npm)
$ npm config set ignore-scripts true
$ npm audit signatures
$ npx lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https
$ npm sbom --sbom-format cyclonedx > sbom.cdx.json
$ npm ci

Layer 3, verification: check what you actually got. npm audit signatures verifies registry signatures on downloaded packages and, where present, their provenance attestations. gh attestation verify and sigstore tooling check build attestations against policy. Generate an SBOM (npm sbom or syft) so you know, mechanically, what is in your dependency graph. A package with a verified publisher badge and provenance is worth more than a package with 50 million weekly downloads and none.

Layer 4, scanning: know what each tool catches. npm audit queries known CVEs; it does not detect malware and will happily pass a package published ten minutes ago. Run it for the CVE baseline, but pair it with behavior analysis: Socket (real-time, blocks malicious packages by runtime behavior), Snyk, OSV-Scanner, Trivy or Grype, Endor Labs. Dependabot or Renovate turn updates into reviewed pull requests instead of silent npm install drift. OpenSSF Scorecard and deps.dev give you maintainer and build signals for adoption decisions.

Layer 5, identity and publishing: make takeover expensive. Hardware-key 2FA (FIDO2) over TOTP, because modern phishing steals one-time codes in real time; my SMS 2FA post spells out why the weakest factor is the one attackers target. Use scoped, short-lived publish tokens, never commit .npmrc tokens, remove dormant maintainers, and publish with provenance (npm publish --provenance from GitHub Actions). On JSR, GitHub Actions publishing is already restricted by default to workflows triggered by scope members, and scope settings let you go further and require CI-only publishing; that combination means a phished account cannot publish from a laptop at all.

Layer 6, registry boundary: put a proxy in front of everything. Point your .npmrc at a private proxy or caching registry (Verdaccio, GitHub Packages, Artifactory, Nexus, Cloudsmith, CodeArtifact) that allowlists names and versions, caches known-good artifacts, scans on ingest, and logs every resolution. In production and CI, deny direct public-registry access. This single move neutralizes the whole typosquatting and dependency-confusion class, because the allowlist decides what may enter, and it gives you a central place to enforce cooldowns after incidents.

Layer 7, runtime: watch what the graph does. Egress filtering and C2 detection catch a RAT phoning home; EDR on developer workstations matters because those are the machines holding publish tokens and source; rotate everything after any incident; and keep a recovery story (clean lockfile, known-good tarball cache) so you can rebuild a poisoned tree without trusting the registry again.

Layer

Control

Blocks

Effort

0

Fewer, pinned, reviewed dependencies

Shrinks the graph and the blast radius

Small

1

Committed lockfile plus npm ci in CI

Floating ranges, lockfile drift, dirty lockfiles

Small

2

ignore-scripts, build allowlist, release cooldowns

Install-time payloads, the biggest channel

Small

3

npm audit signatures, provenance checks, SBOM

Tampered and misattributed artifacts

Small

4

npm audit for CVEs, Socket/Snyk for behavior

Known CVEs and brand-new malware

Medium

5

FIDO2 2FA, scoped tokens, dormant-account cleanup

Account takeover and unauthorized publishes

Small

6

Private proxy registry with allowlist and scanning

Typosquatting, dependency confusion, unvetted code

High

7

Egress filtering, EDR, token rotation

Post-install C2, persistence, lateral moves

Medium

Picking your stack

  • Stay on npm when you need maximum interop, your organization standardizes on it, or you ship a single small package. npm is fine; the leverage is in layers 0 through 5, not in abandoning the registry.
  • Add JSR when you publish or consume modern TypeScript packages, especially for Deno, Cloudflare Workers, or other edge runtimes, or when you want immutable versions and provenance by default. Consume it through npm-compat the way you consume npm: same lockfile, signatures, and scans.
  • Use npmx as a research UI only. It is a nicer window into npm's data, and its comparison views are useful for due diligence; treat it, like any package-search page, as untrusted input and verify before you install.
  • Stand up a private registry when your team treats dependencies as critical infrastructure, which it should. The proxy layer is where allowlists, scanning, cooldowns, and audit trails actually get enforced.

My own read after researching this: JSR is the most coherent attempt yet to design the attack surface out of the registry layer, and its immutability plus provenance-by-default are worth taking seriously; but the incidents of 2025 and 2026 keep proving that the registry is only the first gate. The stack that survives contact with reality is the one where the lockfile is committed, install scripts are opt-in, signatures and provenance are verified, behavior is scanned, publishing accounts are hardened, and a proxy registry decides what may enter. Nothing else on this list excuses the human layer, and no registry fixes a phished author.

Official sources

  • JSR provenance and trust: https://jsr.io/docs/trust
  • JSR scopes and CI publishing: https://jsr.io/docs/scopes
  • JSR npm compatibility: https://jsr.io/docs/npm-compatibility
  • JSR FAQ: https://jsr.io/docs/faq
  • Introducing JSR: https://deno.com/blog/jsropenbeta
  • npm audit signatures: https://docs.npmjs.com/cli/commands/npm-audit
  • Verifying registry signatures: https://docs.npmjs.com/verifying-registry-signatures/
  • Sonatype, state of the software supply chain, open source malware: https://www.sonatype.com/state-of-the-software-supply-chain/2026/open-source-malware
  • Snyk on the node-ipc compromise: https://snyk.io/blog/malicious-node-ipc-versions-published-npm/
  • StepSecurity on the axios compromise: https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan
  • OpenSSF on Mini Shai-Hulud and the limits of SLSA: https://openssf.org/blog/2026/06/10/mini-shai-hulud-where-slsas-boundaries-fall/
  • Check Point Research, Stargazers Ghost Network: https://research.checkpoint.com/2024/stargazers-ghost-network/
  • Our npm vs pnpm post: https://systhoughts.com/posts/npm-vs-pnpm-supply-chain-pnpm-12
  • Our Docker sandboxes for AI agents post: https://systhoughts.com/posts/docker-sandboxes-ai-agents

What is your registry posture today, npm, JSR, or a private proxy in between? And which defense layer is the one you keep skipping? Drop it in the comments.

Until next time, keep your systems thoughtful.

No comments yet