Rok po Shai-Hulud: co se změnilo na npm a co musíte udělat do ledna
Před rokem začal registrem npm procházet samošířící červ Shai-Hulud. Od té doby se útoky na dodavatelský řetězec JavaScriptu staly pravidelnou záležitostí a npm postupně přepisuje pravidla publikování. Projdeme, co se za ten rok stalo, jak dnes bezpečně publikovat balíček, co musíte stihnout do ledna 2027 a jak se bránit jako uživatel balíčků.
Rok, kdy se z incidentů stala rutina
Útoky na npm nejsou nic nového. Už v roce 2018 předal autor balíčku event-stream správu dobrovolníkovi, který do něj tiše vložil kód na krádež bitcoinů. V roce 2021 následoval ua-parser-js s miliony stažení týdně. Každý takový případ se ale brál jako výjimka.
Zlom přišel v září 2025. Nejdřív útočníci phishingem převzali účet správce balíčků chalk a debug, které mají dohromady přes miliardu stažení týdně. Krátce nato, 23. září 2025, americká agentura CISA varovala před červem Shai-Hulud, který kompromitoval více než 500 balíčků. Novinkou nebyl samotný útok, ale automatizace: červ kradl tokeny z nakažených strojů a s jejich pomocí sám publikoval další infikované verze.
Rok 2026 ukázal, že nejde o jednorázovou krizi. Na jaře přišla vlna kampaní zasahujících balíčky Bitwardenu, SAP, TanStacku, AntV nebo Red Hatu. U Red Hatu stojí za pozornost vstupní bod: GitHub účet vývojáře byl kompromitován přes škodlivé rozšíření pro VS Code a přes něj se škodlivý kód dostal do balíčků pod @redhat-cloud-services. Útok na AI framework Mastra, který analytici připisují severokorejské skupině, pak přidal státní sponzoring.
Zatím poslední velký případ přišel 4. srpna 2026. Útočník převzal GitHub účet správce balíčku keyv a souvisejících projektů a červ, kterému komunita říká ChainDrop, se odtud rozšířil do stovek balíčků. Některé z nich mají přes 150 milionů stažení týdně. Ukradená data červ zašifrovaně ukládal do veřejných GitHub repozitářů, kterých podle Aikido Security vzniklo zhruba 1 300.
Proč staré rady nestačí
Po prvním Shai-Hulud zněla nejčastější rada jednoduše: instalujte s --ignore-scripts. Červ se totiž spouštěl přes postinstall a bez install skriptů neměl jak se dostat ke slovu. Podle analýzy společnosti Phoenix Security ale tahle obrana do června 2026 přestala stačit.
Dobrý příklad jsou git závislosti. Přímá i tranzitivní závislost načítaná z git repozitáře může obsahovat vlastní .npmrc, který přepíše cestu ke spustitelnému souboru git. Při instalaci se tak spustí libovolný kód, i když máte skripty vypnuté. npm na to od verze 11.10.0 reaguje přepínačem --allow-git, kterým můžete git závislosti explicitně povolit nebo zakázat.
Útočníci se navíc posunuli od typosquattingu k převzetí skutečných, populárních balíčků. Proti tomu žádný jednotlivý přepínač nepomůže. Obrana musí být vrstvená: na straně toho, kdo balíček publikuje, i toho, kdo ho instaluje.
Autor balíčku: jak se dnes publikuje
Pro autory balíčků se za poslední rok změnilo nejvíc. Základní myšlenka je jasná: dlouhodobý token uložený v CI je přesně to, co červ ukradne a použije k dalšímu šíření. npm proto zrušil klasické tokeny a zbylé granulární tokeny omezuje. V CI je nahrazuje trusted publishing, tedy krátkodobý token vydaný přes OIDC pro konkrétní běh workflow. Lokálně slouží dvouhodinové session tokeny. Staged publishing k tomu přidává lidské schválení s 2FA před zveřejněním verze.
Stručná historie změn
| Datum | Změna |
|---|---|
| 3. září 2026 | Více konfigurací trusted publishingu na balíček, staged publishing jako výchozí pro nové konfigurace, schválení až po malware skenu |
| srpen 2026 | Staged publishing obecně dostupný (npm CLI 11.15.0+); tokeny obcházející 2FA už nemohou spravovat účet, organizaci ani balíčky |
| 18. února 2026 | npm CLI 11.10.0: hromadné nastavení trusted publishingu (npm trust), přepínač --allow-git |
| 3. února 2026 | Efektivní termín zrušení klasických tokenů |
| 9. prosince 2025 | npm začíná rušit klasické tokeny |
| 31. července 2025 | Trusted publishing přes OIDC obecně dostupný |
Trusted publishing místo tokenu
Trusted publishing funguje přes OIDC. V nastavení balíčku na npmjs.com určíte, ze kterého repozitáře a kterého workflow smí publikovat. CI si pak při každém běhu vyžádá krátkodobý token s identitou konkrétního jobu a žádné tajemství v repozitáři nemusí být. Kromě GitHub Actions npm podporuje například CircleCI a do budoucna slibuje další poskytovatele.
Od 3. září 2026 může mít jeden balíček až deset konfigurací trusted publishingu. Dříve byla povolená jen jedna, takže kdo chtěl oddělit stabilní verze, prereleasy nebo staging, musel sáhnout po obezličce nebo si nechat dlouhodobý token. Pokud spravujete desítky balíčků, pomůže příkaz npm trust, který nastaví trusted publishing pro více balíčků najednou.
Staged publishing: CI nahraje, člověk schválí
Druhá velká novinka se jmenuje staged publishing a obecně dostupná je od letošního srpna. Příkaz npm stage publish verzi do registru nahraje, ale nezveřejní. Zveřejnit ji musí člověk příkazem npm stage approve nebo na webu npmjs.com. Schválení přitom nejde provést přes OIDC token, vyžaduje interaktivní přihlášení s 2FA. npm tomu říká důkaz přítomnosti. Staged publishing potřebuje npm CLI 11.15.0 nebo novější a funguje jen pro balíčky, které už v registru existují.
Pro červa je to zásadní překážka. I kdyby získal přístup k CI, nová verze se zasekne ve frontě a bez člověka se k uživatelům nedostanou. Od 3. září navíc npm nedovolí stagovanou verzi schválit, dokud nedoběhne sken na malware.
Konfigurace trusted publishingu vytvořené po 3. září 2026 mají staged publishing povolený automaticky a přímé npm publish si musíte povolit zvlášť. Konfigurace vytvořené před 20. květnem 2026 zůstávají nastavené jen na přímou publikaci, takže stávající workflow se nerozbije. Staged publishing do nich ale musíte zapnout sami.
Co musíte stihnout do ledna 2027
Kdo publikuje z CI pomocí granulárního tokenu s obcházením 2FA, má odpočet. Od začátku srpna 2026 takový token nesmí vytvářet ani mazat tokeny, měnit heslo, e-mail či nastavení 2FA, spravovat správce balíčku ani konfiguraci trusted publishingu. Od ledna 2027 s ním nepůjde ani přímo publikovat. Přesné datum npm zatím neuvedlo.
Migrace na trusted publishing vypadá zhruba takto:
- V nastavení balíčku na npmjs.com vyplňte v sekci Trusted Publisher organizaci, repozitář a název souboru workflow. Pozor na překlepy, npm je nekontroluje.
- V sekci Publishing access nastavte vyžadování 2FA.
- Upravte workflow podle ukázky níže a odstraňte z něj token.
- Po první úspěšné publikaci smažte starý token z nastavení repozitáře i z účtu na npm.
Minimální workflow pro GitHub Actions může vypadat takto:
name: Publish
on:
release:
types: [published]
permissions:
contents: read
id-token: write # nutné pro OIDC token
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 24
- run: npm install -g npm@latest # npm stage vyžaduje 11.15.0+
- run: npm ci
- run: npm stage publishCode language: PHP (php)
Na co si dát pozor: trusted publishing vyžaduje npm CLI 11.5.1 nebo novější, staged publishing dokonce 11.15.0, obojí s Node.js 22.14.0 a vyšším. npm přibalené k Node.js nemusí být dost nové, proto ho ve workflow raději aktualizujte. Pokud v kroku setup-node zůstane konfigurace pro přihlášení tokenem, npm se může pokusit autentizovat postaru a publikace skončí matoucí chybou 404. Příkaz npm whoami stav OIDC přihlášení neukazuje, protože přihlášení probíhá jen během samotné publikace.
Uživatel balíčků: vrstvená obrana
I když autoři balíčků všechno nastaví správně, některý útok projde. Jako uživatel balíčků proto potřebujete několik vrstev, z nichž každá zachytí něco jiného.
Cooldown: nové verze nechte chvíli uležet
Jednou z nejlevnějších obran je minimální stáří verze, anglicky cooldown. Správce balíčků prostě odmítne nainstalovat verzi, která vyšla před méně než stanovenou dobou. Kompromitované verze bývají často odhaleny a staženy z registru během několika hodin, takže už zpoždění 24 hodin zachytí velkou část rychlých útoků. Pokud útok zůstane nepovšimnut déle, cooldown nepomůže, proto je vhodné ho kombinovat s dalšími vrstvami. Nastavení je přitom otázkou jednoho řádku konfigurace.
Potíž je v tom, že každý správce balíčků používá jiný název i jinou jednotku:
| Nástroj | Nastavení | Jednotka | Od verze | Výchozí hodnota | Kde nastavit |
|---|---|---|---|---|---|
| npm | min-release-age | dny | 11.10.0 | vypnuto | .npmrc |
| pnpm | minimumReleaseAge | minuty | 10.16 | 1440 (od pnpm 11) | pnpm-workspace.yaml |
| Yarn | npmMinimalAgeGate | minuty | Berry 4.10.0 | cca 1 den (novější verze) | .yarnrc.yml |
Pro npm stačí do .npmrc v kořeni projektu přidat:
min-release-age=1
V pnpm 11 patří nastavení do pnpm-workspace.yaml, kde můžete zároveň vyjmout vlastní interní balíčky:
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
- '@moje-firma/*'Code language: JavaScript (javascript)
Pozor na jednotky. min-release-age=1440 v npm neznamená jeden den, ale necelé čtyři roky. U npm si také ověřte, že máte opravdu verzi 11.10.0 nebo novější, starší verze nastavení ignorují. Pokud používáte Renovate nebo Dependabot, nastavte cooldown i v nich. Jinak vám budou otevírat pull requesty na verze, které správce balíčků odmítne nainstalovat.
Cooldown má jednu nevýhodu: zdrží i bezpečnostní opravy. Pro naléhavé případy je proto dobré mít připravený postup, jak konkrétní balíček z pravidla dočasně vyjmout.
Lockfile a npm ci
Cooldown chrání jen ve chvíli, kdy se závislosti řeší. Aby se jednou vyřešené verze nemohly nepozorovaně změnit, commitujte lockfile a v CI instalujte přes npm ci (v pnpm pnpm install --frozen-lockfile). Ty nainstalují přesně to, co je v lockfilu, a při nesouladu selžou. Pro aplikace, na rozdíl od knihoven, zvažte i save-exact=true v .npmrc, aby se do package.json neukládaly rozsahy verzí.
Install skripty a git závislosti
--ignore-scripts sice nestačí, ale pořád se vyplatí. V npm ho můžete zapnout globálně přes ignore-scripts=true v .npmrc. pnpm jde dál a od verze 10 install skripty závislostí ve výchozím stavu nespouští, dokud je výslovně nepovolíte.
pnpm nabízí i dvě další užitečná nastavení. blockExoticSubdeps zakáže tranzitivním závislostem odkazovat na git repozitáře nebo URL místo registru. trustPolicy: no-downgrade odmítne verzi, která ztratila důkaz původu, tedy například balíček dříve publikovaný s provenance, jehož nová verze ho nemá. Přesně tak totiž často vypadá verze publikovaná útočníkem s ukradeným tokenem.
CI a vývojářské prostředí
Případ Red Hatu připomíná, že útok nemusí začít v registru. Začal škodlivým rozšířením pro VS Code, které kompromitovalo GitHub účet vývojáře. Útočník pak upravil i konfigurační soubory v repozitáři tak, aby nakazily další vývojáře, kteří si daný adresář otevřou. Tokeny, SSH klíče a přístupy ke cloudu na vývojářském stroji jsou pro červa stejně cenné jako tokeny v CI.
Na vývojářském stroji proto nemějte dlouhodobé npm tokeny. Pro lokální publikaci npm nabízí dvouhodinové session tokeny. Instalujte jen rozšíření editoru, která opravdu potřebujete, a u méně známých si ověřte vydavatele. Pro 2FA na npm používejte hardwarový klíč nebo passkey. SMS je zranitelná přes výměnu SIM karty.
V CI dejte každému jobu jen oprávnění, která potřebuje, a id-token: write povolte jen v jobu, který publikuje. Samostatnou kapitolou jsou self-hosted runnery. Na rozdíl od běžných jednorázových runnerů si drží stav mezi běhy a malware v nich podle analýz hledal způsob, jak se usadit natrvalo. Pokud je používáte, zvažte přechod na jednorázové runnery nebo aspoň jejich pravidelnou obnovu z čistého obrazu.
Závěr: co udělat ještě tento měsíc
Rok po Shai-Hulud je ekosystém npm v lepší kondici, než byl. Klasické tokeny jsou pryč, granulární mají užší oprávnění, trusted publishing je dostupný pro každého a staged publishing přidává lidské schválení, které červ neobejde. Útoky ale neustávají a nová ochrana funguje, jen pokud ji autoři i uživatelé balíčků opravdu zapnou.
Pokud publikujete balíčky
- Přejděte z tokenů na trusted publishing a starý token smažte.
- Zapněte staged publishing, zejména u balíčků s velkým počtem stažení.
- V nastavení balíčku vyžadujte 2FA a jako druhý faktor použijte hardwarový klíč nebo passkey.
- Pokud v CI používáte token obcházející 2FA, dokončete migraci do ledna 2027.
- Omezte
id-token: writejen na publikační job.
Pokud balíčky používáte
- Nastavte cooldown v npm, pnpm nebo Yarnu a pohlídejte si jednotky.
- Nastavte cooldown i v Renovate nebo Dependabotu.
- Commitujte lockfile a v CI instalujte přes
npm cinebo--frozen-lockfile. - Vypněte install skripty, nebo je povolujte jen vybraným balíčkům.
- Zkontrolujte git závislosti a v pnpm zapněte
blockExoticSubdepsatrustPolicy: no-downgrade. - Projděte rozšíření editoru a self-hosted runnery.
… liked this!
… reposted this!