7 min čteníJohnny UnarJohnny Unar

Pinování tagů není zabezpečení supply chainu

Kompromitace trivy-action skupinou TeamPCP přepsala během noci 76 ze 77 tagů. Pokud vaše CI pinuje na @v0.34.2, už jste prohráli.

co se vlastně stalo

19. března 2026 získal ten, kdo ovládá handle TeamPCP, write přístup k repozitáři aquasecurity/trivy-action a udělal něco, co většina lidí považovala v praxi za nemožné: force-pushnul nové commity a pak přesměroval 76 ze 77 verzovacích tagů na tenhle škodlivý kód. Ne jeden tag. Ne latest. Sedmdesát šest tagů, roky zpátky. Takže pokud jste měli ve workflow uses: aquasecurity/trivy-action@v0.34.2 v souboru, který jste napsali v roce 2024 a od té doby na něj nesáhli, další běh pipeline stáhnul přepsaný tag, spustil shim na sběr přihlašovacích údajů a poslal domů vaše cloudové tokeny, SSH klíče a všechny Kubernetes service account tokeny namountované do runneru. Všechno ještě před tím, než vůbec začal samotný Trivy scan. Scan proběhl. Dashboardy zůstaly zelené. Exfiltrace se odehrála v pre kroku, na který se většina lidí nikdy nedívá, protože kdo taky čte action.yml scanneru, který používá tři roky.

Ta chytrá část, a chytrá tady používám ve smyslu, v jakém byste to řekli o někom, kdo vás okradl efektivně, je, že payload nic nerozbil. Rozbité action si všimnete zhruba za čtyři minuty, protože se v PR objeví červený křížek. Funkční action, které si potichu zkopíruje $AWS_SESSION_TOKEN do HTTP POST před tím, než udělá svou skutečnou práci, si všimnete ve chvíli, kdy někdo jiný utrácí vaše peníze. TeamPCP pochopil, že celá hodnota útoku na bezpečnostní nástroj spočívá v tom, že od bezpečnostního nástroje nikdo nečeká, že bude vektorem.

proč si všichni mysleli, že jsou v bezpečí

Po průšvihu s tj-actions/changed-files v březnu 2025, kdy útočník upravil action tak, aby vypisovala CI tajemství do build logů, a pak zpětně přepsal tagy, se celý obor sjednotil na jedné radě, která se pak dalších dvanáct měsíců opakovala na každé konferenci a v každém interním security kanálu na Slacku. Pinujte tagy. Přestaňte používat @main, přestaňte používat @v4, pinujte na konkrétní verzi jako @v4.2.1, ať přesně víte, co spouštíte.

Ta rada byla špatná, nebo přesněji, bylo to poloviční řešení převlečené za skutečné, a kompromitace trivy-action je důkaz, že spousta velmi chytrých DevSecOps týmů nasadila kontrolní mechanismus, který nikdy nic nekontroloval. Git tag je měnitelný ukazatel. Je to ref, textový soubor pod .git/refs/tags/, který obsahuje hash, a kdokoli má push přístup do repozitáře, může kdykoli změnit, kam ten soubor ukazuje. Žádný požadavek na podpis, žádná immutabilita, na úrovni protokolu nic, co by maintainerovi (nebo tomu, kdo maintainera kompromitoval) zabránilo přesunout v0.34.2 na jakýkoli commit chce. GitHub ten force-push v klidu přijme. Váš runner v klidu přeloží tag na jeho aktuální cíl v době spuštění, ne v době, kdy jste workflow psali.

Takže když jste napsali @v0.34.2, nezapinovali jste vůbec nic. Zapsali jste si jméno a spolehli se, že to jméno bude navždy znamenat to samé, což je přesně ten předpoklad důvěry, který vybouchl. Číslo verze ve workflow souboru je dokumentace, ne vynucení.

jediná věc, která je skutečně neměnná

Commit SHA je content-addressed. Hash se odvozuje z obsahu commitu, ze stromu, z rodiče, z metadat, ze všeho. Takže když pinujete na aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5, pinujete na konkrétní objekt, který vám nikdo nemůže vyměnit pod rukama, aniž by se změnil hash. A když se hash změní, věc, na kterou jste odkazovali, se prostě přestane překládat. To je hranice důvěry. Cokoli měkčího je jen doporučení.

GitHub to ve svém vlastním hardening guide říká už roky: pinujte third-party actions na plný commit SHA. Většina lidí to přečetla, přikývla a stejně pak zapinovala na tag, protože SHA jsou ošklivé, špatně čitelné a na první pohled z nich nepoznáte, na jaké verzi jste. Chápu to. @a1b2c3d vám neřekne nic. Jenže ta ošklivost je právě ten smysl. SHA není zapamatovatelné pro člověka přesně proto, že je vázané na bajty, ne na label, který přiřadil člověk a který může člověk přeřadit.

Teď ta otravná realita, kterou nikdo nechce slyšet. Pinování na SHA zapinuje jen tuhle jednu action. Nezapinuje actions, které tahle action volá interně, a obrovské množství composite actions dělá přesně tohle: ve svém vlastním action.yml volají další uses: kroky, které se překládají za běhu. To znamená, že vaše pečlivě na SHA zapinovaná top-level action může o dvě vrstvy níž stejně stáhnout měnitelný tag a vy to ve svém repozitáři nikdy neuvidíte. Vyřešili jste jednu hranu grafu a zbytek nechali dokořán.

audit celého grafu actions

Mentální model, který vás skutečně ochrání, chápe vaše CI ne jako seznam kroků, ale jako graf závislostí, stejně jako přemýšlíte o svém npm nebo Go module stromu. Protože to přesně to je. Každá action, kterou zavoláte, může volat další, ty můžou volat další a tranzitivní množina kódu běžícího na runneru s přístupem k vašim tajemstvím je skoro vždy desetkrát větší než těch pár uses: řádků, které jste napsali vy.

Takže je potřeba tímhle grafem projít. U každé third-party action zapinujte SHA, pak otevřete její action.yml a zkontrolujte, jestli to není composite action, která volá další kroky. A pokud volá, tak buď jsou taky zapinované na SHA, nebo máte díru. Nástroje jako pin-github-action a StepSecurity Harden-Runner pomáhají s první vrstvou, a hlavně Harden-Runner stojí za spuštění, protože monitoruje odchozí síťový provoz z runneru a označil by trivy-action ve chvíli, kdy dělá neočekávaný POST na neznámý host. Přesně tenhle typ behaviorální detekce chytí to, co vaše statické pinování minulo. Dependabot vám umí SHA bumpovat a ukáže vám diff toho, co se mezi starým a novým commitem změnilo, což je kontrola reprodukovatelnosti, kterou většina lidí přeskočí. Máte si totiž fakt přečíst, co se změnilo, než bump smergujete.

Děláme to pro klienty v rámci bezpečnostních auditů a ten vzorec je depresivně konzistentní. Tým, který věří, že v roce 2025 vyřešil bezpečnost supply chainu, má krásně na tagy zapinovaná workflow a nulovou viditelnost do composite actions tři vrstvy hluboko, plus problém s pre a post kroky, na který se nikdo nedíval. Graf je ta hranice. Cokoli uvnitř něj, co jste neauditovali, je důvěryhodné, ať jste tomu chtěli věřit nebo ne.

co změnit v pondělí

Převeďte každý uses: odkaz, který míří na tag nebo branch, na plný commit SHA a čitelnou verzi dejte do koncového komentáře, ať si zachováte dokumentaci bez falešného pinu, třeba uses: aquasecurity/trivy-action@6c175e9c... # v0.34.2. Udělejte to i u actions, které jste psali sami, ne jen u third-party, protože interní repozitáře se kompromitují stejně jako externí.

Zapněte Harden-Runner v audit módu napříč kritickými pipelines a nechte ho týden baselinovat odchozí provoz, pak ho přepněte do block módu, jakmile víte, jak vypadá normál. Příště, když se nějaká action pokusí exfiltrovat na host, který není na vašem allowlistu, run selže nahlas, místo aby potichu prošel. Přidejte CI check, který shodí jakékoli PR, kde je uses: řádek pinovaný na cokoli, co není 40znakové SHA. Ta disciplína se totiž rozpadne ve chvíli, kdy závisí na tom, že si člověk vzpomene, a někdo za šest týdnů vloží @v1 z README, pokud ho nic nezastaví.

A pořádně stáhněte oprávnění runneru, protože pinování na SHA omezuje, jaký kód běží, ale scoping oprávnění omezuje, co ten kód může ukrást, až vaše pinování nakonec selže. A ono někde selže, na nějaké tranzitivní závislosti, na kterou jste se nedostali. Krátkodobé OIDC tokeny místo dlouhodobých AWS klíčů, permissions: read-all jako výchozí nastavení s explicitními granty jen tam, kde jsou potřeba, žádné široké Kubernetes tokeny namountované do jobů, které jen spouštějí linter. Týmy, které z 19. března vyšly v pohodě, nebyly ty s dokonalým pinováním. Byly to ty, jejichž ukradené přihlašovací údaje vypršely za patnáct minut a stejně se nedostaly do produkce. Defense in depth je nuda a je to jediná věc, která funguje, když se ukáže, že vaše hranice důvěry je textový soubor, který může editovat někdo jiný.

Johnny Unar

Napsal/a

Johnny Unar

Chcete s námi spolupracovat?

Kompromitace trivy-action skupinou TeamPCP přepsala během noci 76 ze 77 tagů. Pokud vaše CI pinuje na @v0.34.2, už jste prohráli.