TL;DR

On July 11, 2026, the jscrambler npm package was compromised and shipped "IronWorm" — a cross-platform Rust infostealer. The detail that should get your attention isn't the cloud credentials or the crypto wallets. It's that IronWorm steals browser cookies and sessions, plus API keys and MCP server credentials from Claude Desktop, Cursor, Windsurf, VS Code, and Zed. And in versions 8.18.0 and 8.20.0 the dropper moved out of a preinstall hook and into main code/CLI, which means npm install --ignore-scripts does not stop it. Bad versions: 8.14.0, 8.16.0, 8.17.0, 8.18.0, 8.20.0. Clean: 8.22.0.

The honest lesson here is about attack surface, and it cuts at us too — Convert: Web to PDF has a dependency tree like everything else. The narrower, defensible claim: a tool that makes zero network requests during conversion has nowhere to send what it steals, and a tool with no server has no breach to have. That's not a promise of perfection. It's a smaller blast radius.


What happened, precisely

A stolen publishing credential was used to push malicious jscrambler releases to npm over roughly three hours on July 11, 2026. The payload was IronWorm, a cross-platform infostealer written in Rust.

The affected versions:

VersionStatusDelivery mechanism
8.14.0Maliciouspreinstall hook
8.16.0Maliciouspreinstall hook
8.17.0Maliciouspreinstall hook
8.18.0MaliciousMain code / CLI
8.20.0MaliciousMain code / CLI
8.22.0Clean

Socket flagged it six minutes after publish. JFrog identified self-propagation behaviour — the worm hunts for npm tokens, which is how a single compromised credential becomes a spreading problem rather than a contained one.

The --ignore-scripts detail is the important one

For years, the standard advice for npm supply-chain hygiene has been: run npm install --ignore-scripts. It disables lifecycle hooks like preinstall and postinstall, which is where the overwhelming majority of npm malware has historically lived. It's good advice and it works — right up until it doesn't.

The early IronWorm releases (8.14.0, 8.16.0, 8.17.0) used a preinstall hook. --ignore-scripts would have stopped them.

In 8.18.0 and 8.20.0, the dropper moved into main code and the CLI. At that point it isn't running at install time — it's running when you use the package. Your build runs it. Your CI runs it. --ignore-scripts is irrelevant, because nothing is being ignored; you're just executing the library you asked for.

That's the part worth internalizing. A mitigation that covers one delivery path doesn't cover the category. Attackers move.

What IronWorm takes

The list is long: AWS/Azure/GCP credentials including CI metadata endpoints, crypto wallets, Bitwarden vaults. But the two that reframe the threat model:

  • Browser cookies and sessions.
  • API keys and MCP server credentials from Claude Desktop, Cursor, Windsurf, VS Code, and Zed.

Cloud keys get rotated. Wallets get emptied and that's terrible but bounded. Your browser session is different — it's a live, authenticated bearer of everything you're logged into. Email, source control, HR portal, banking dashboard, cloud console. A stolen session cookie doesn't need your password and often doesn't need your MFA. It just needs to be replayed before it expires.

The MCP credential theft is the 2026-specific twist. Developers now routinely have local agent tooling holding long-lived tokens to production systems, sitting in config files on the same machine as node_modules. That's a new, rich, under-defended target, and IronWorm went straight for it.


The same shape of problem: GitHub.dev, June 2026

This isn't an isolated incident type. On June 3, 2026, a one-click OAuth token theft in GitHub.dev was disclosed — a malicious link combined with VS Code local workspace extension and message-passing abuse could steal a token with read/write access to private repos. It was found by Ammar Askar, Microsoft mitigated it service-side, and there's no CVE.

Different vector entirely. Same shape: the browser session is the crown jewel. One attack goes through your package manager, one goes through a link you click. Both converge on the same prize, because that's where the value is concentrated.

We made a related argument when ClaudeBleed showed how a browser agent's permissions become someone else's tooling. The through-line across all three: capability is the risk. Not maliciousness, not bad code — capability. The more a piece of software can do, the more it can be made to do.


Now the uncomfortable part: our attack surface

Here's where most vendor security posts pivot to "…and that's why our product is safe." We're not going to do that, because the argument doesn't survive contact with the facts.

We have a dependency tree. Every extension does. We bundle jsPDF. We rely on Chrome's DevTools Protocol. If a package we depend on got the jscrambler treatment, our build could ship something we didn't write. That is a real risk and we're not going to pretend we've transcended it. Nobody has. Anyone telling you their JavaScript project is immune to supply-chain compromise is either lying or hasn't looked.

So the honest version of our pitch is narrower than the marketing version. It's this:

Zero network requests during conversion means there's no exfiltration path in the conversion flow. Convert: Web to PDF renders via Chrome's DevTools Protocol, entirely on your machine. When you convert a page, nothing goes out. A stealer needs a way out — a hijacked converter that never opens a socket during conversion has nothing to write to.

No server means no breach. We don't operate infrastructure that holds your documents, because your documents never reach us. There is no bucket to misconfigure, no database to leak, no vendor breach that spills your files. You cannot lose what you never collected. This is the same argument we made about why a PDF converter shouldn't upload your files and after the Vercel breach put online PDF tools in the blast radius.

Neither claim is "we can't be compromised." Both are claims about what a compromise could accomplish. That's a lower bar, and it's the only one we can honestly clear.

Our own data collection, stated plainly

One anonymous ping fires after a conversion completes. It carries a random install token. No URL. No page content. No IP.

That's it. We don't know what you converted, what site you were on, or who you are. We mention it here rather than burying it because a post about disclosure that hides its own disclosure would be embarrassing.


The upload-based model: a design choice, not a bug

Let's be fair to the competition, because the lazy version of this argument is wrong.

Smallpdf, iLovePDF, PDFCrowd, and Adobe Acrobat's cloud services are not badly engineered. Several of them are excellent, with real security teams and real compliance programs. The issue isn't their code quality.

The issue is that their model requires your file to leave your machine. That's inherent to server-side processing — you cannot process a file on a server without sending it to the server. Once it's there, it exists in a place you don't control, governed by a retention policy you have to trust, on infrastructure with its own vendors and its own dependency trees and its own compromised-npm-package Tuesdays.

That is a design choice, not a flaw. It buys real things: no install, works on any OS, works on a phone, handles formats a browser can't. If those matter more to you than locality, that's a legitimate trade and you should make it deliberately.

Upload-based (Smallpdf, iLovePDF, PDFCrowd, Acrobat cloud)Local extension (Convert: Web to PDF)
File leaves your machineYes — required by the modelNo
Server holds your documentTemporarily, per their retention policyNo server exists
Breach could expose your filesYesNo files to expose
Reaches login-protected pagesNoYes — uses your live session
Their dependency tree affects youYes, plus their vendors'Yes, but ours only
Network requests during conversionRequiredZero
Works without an installYesNo
Works on iOS / AndroidYesNo — Chromium desktop only

The row that decides it for most people isn't the security row. It's the login row. If the page is behind your SSO — your HR portal, your bank dashboard, your internal wiki — a server-based tool physically cannot fetch it. That's covered properly in saving webpages behind logins as PDF.


How to reason about any extension's attack surface

Forget PDF tools for a second. Here's the general method, useful on any extension you're about to install.

1. Read the permissions, and ask what they enable

Not what the extension does — what its permissions permit. An extension with <all_urls> and host permissions can read every page you visit, forever, including the authenticated ones. Whether it currently does is a question about today's code. What it could do after an update, an acquisition, or a compromised maintainer account is a question about permissions.

The question to ask: if this extension were taken over tomorrow, what would the new owner have?

2. Does it phone home?

Open DevTools, hit the Network tab, use the extension, watch. An extension that transmits during its core operation has an exfiltration channel by design. One that doesn't, has to build one — which means shipping an update, which is at least visible.

This is a test you can run in ninety seconds and almost nobody does.

3. Single purpose

Scope creep is a security property. A tool that converts pages to PDF, syncs your bookmarks, blocks ads, and offers an AI sidebar has four times the surface and four times the reasons to need permissions it can abuse. Small is a security feature. It's the first line of our manifesto for reasons that predate this npm compromise: four extensions, not one suite, each doing one thing.

4. Does the Privacy practices tab match the behavior?

Every Chrome Web Store listing has a "Privacy practices" tab where the developer declares what they collect. Read it. Then compare it to what you observed in step 2. A mismatch in either direction is informative: collecting more than declared is a policy violation, and declaring more than they collect suggests they aren't paying attention.

We wrote about spotting the tells when 108 malicious extensions were pulled — the recurring pattern is always permissions that outrun purpose.

5. Know what the store now requires of them

This gets easier on August 1, 2026, when enforcement begins on Google's updated Chrome Web Store Developer Program Policies, announced July 1, 2026. The two changes that matter here:

  • Collected data must be strictly necessary to the extension's disclosed single purpose. Not "useful." Not "for analytics we might want later." Strictly necessary, tied to a purpose the developer declared.
  • All data collection must be prominently disclosed, even when it relates to that purpose — plus proactive notice to users if practices change after install.

(The same update bans prediction-market and real-money-on-outcomes extensions, and prohibits extensions designed to circumvent AI services' safety guardrails or usage restrictions.)

For once, the policy points the same direction as the security argument. "Strictly necessary to a disclosed single purpose" is, functionally, a rule against carrying attack surface you don't need. An extension that collects nothing has nothing to justify. Ours collects one anonymous install token, post-conversion, and that's the entire disclosure.


Practical steps if you touched jscrambler this week

Not security advice from a security vendor — we make PDF tools. But the basics:

  1. Check your lockfile for jscrambler at 8.14.0, 8.16.0, 8.17.0, 8.18.0, or 8.20.0. Pin to 8.22.0.
  2. Don't rely on --ignore-scripts as your answer. For 8.18.0 and 8.20.0 the dropper ran from main code, so a hook-blocking flag never entered the picture.
  3. Assume browser sessions on affected machines are compromised. Cookie and session theft is IronWorm's whole point. Sign out everywhere, invalidate sessions server-side where you can, rotate what you can rotate.
  4. Rotate MCP server credentials for Claude Desktop, Cursor, Windsurf, VS Code, and Zed. This is the one most people will skip because it's new. Those tokens often reach production.
  5. Rotate npm tokens. JFrog found self-propagation hunting exactly those.
  6. Check CI. IronWorm targeted cloud metadata endpoints, and CI runners are where the good credentials live.

Frequently asked questions

Does npm install --ignore-scripts protect me from IronWorm?

Only from the early versions. 8.14.0, 8.16.0, and 8.17.0 used a preinstall hook, which --ignore-scripts blocks. 8.18.0 and 8.20.0 moved the dropper into main code and the CLI, so it executes when the package runs, not when it installs. The flag is still worth using — it just isn't a strategy.

Why is stealing browser cookies worse than stealing a password?

A session cookie is proof you already authenticated. Replaying it typically skips the password and the MFA prompt, because from the server's perspective you're an existing logged-in session. Passwords can be changed and MFA re-enrolled; sessions have to be explicitly invalidated, which most people never do. It's the difference between a key and an unlocked door.

Is Convert: Web to PDF immune to a supply-chain attack?

No, and we're not going to claim it. We have a dependency tree, we bundle jsPDF, and we rely on Chrome's DevTools Protocol. What we can say is narrower: conversion makes zero network requests, so there's no outbound channel in that flow, and there's no server holding your documents to breach. Smaller blast radius, not zero risk. Anyone claiming zero risk in a JavaScript project is selling something.

If you make zero network requests, what's the anonymous ping?

It fires after a conversion finishes and carries a random install tokenno URL, no page content, no IP. It tells us roughly how many conversions happen. It cannot tell us what you converted, because it doesn't carry it.

Was this a Chrome extension attack?

No — jscrambler is an npm package, so this hit build pipelines and developer machines, not the Chrome Web Store. We're writing about it because the payload's targets (browser cookies, sessions, local AI tooling credentials) are exactly what a compromised extension would also be after. Same prize, different door.

What does the August 1, 2026 CWS policy actually change for users?

Enforcement begins on rules requiring that collected data be strictly necessary to a disclosed single purpose, and that all collection be prominently disclosed with proactive notice if practices change post-install. Practically: the "Privacy practices" tab becomes a more meaningful document, and "we collect it because it might be handy" stops being a defensible position.

What's the fastest way to check if an extension phones home?

DevTools, Network tab, use the extension, watch what fires. Ninety seconds. It won't catch everything — a patient attacker can wait — but it catches the routine cases, and it's more than almost anyone does before clicking Add to Chrome.


Bottom line

The July 11, 2026 jscrambler compromise shipped IronWorm, a Rust infostealer that went after browser cookies and sessions and MCP credentials from Claude Desktop, Cursor, Windsurf, VS Code, and Zed — and in 8.18.0/8.20.0 it moved into main code so --ignore-scripts didn't help. Pin 8.22.0, rotate your sessions and MCP tokens, and note that the June 3, 2026 GitHub.dev OAuth theft was the same idea through a different door: your browser session is the crown jewel.

There's no version of this where we tell you our extension is invulnerable. It has dependencies, it bundles jsPDF, it leans on Chrome's DevTools Protocol. What Convert: Web to PDF has instead is a smaller surface to attack: zero network requests during conversion — nowhere to send anything — and no server, so there's no breach to have. Our entire data collection is one anonymous post-conversion ping with a random install token, no URL, no content, no IP. Compare that against what you're installing. Check the permissions, watch the Network tab, read the Privacy practices tab, and after August 1, 2026 hold every extension to the standard Google is about to enforce: strictly necessary to a disclosed single purpose.

Small is a security property. That's not a slogan we adopted after a bad week — it's the first rule we won't break. If you want the mechanics rather than the argument, the 117-question FAQ has them.