Job v GitHub Actions je dobré místo, odkud krást tajné klíče. Obsahuje klíče pro nasazení, tokeny registrů a přihlašovací údaje ke cloudu a spouští kód, který před minutou napsal někdo jiný: actions třetích stran, balíčky a jejich instalační skripty. Práce GitHubu s tajnými klíči (secrets) chrání před nehodami. Nebyla navržena tak, aby zastavila kód, který už uvnitř jobu běží.
Tento článek popisuje, jak secrets v GitHub Actions fungují, pět způsobů, jak v praxi unikají, a opatření, která každý z nich uzavřou.
Jak secrets v GitHub Actions fungují
Secrets existují na třech úrovních: organizace, repozitáře a prostředí (environment). Pokud stejný název existuje na více úrovních, vyhrává ta nejkonkrétnější: prostředí před repozitářem, repozitář před organizací.
Několik limitů a pravidel, která stojí za to znát:
- Velikost a počet. Až 1 000 secrets organizace, 100 secrets repozitáře a 100 secrets prostředí, každý nejvýše 48 KB. Workflow vidí všechny secrets repozitáře a prostředí, ale jen prvních 100 secrets organizace v abecedním pořadí.
- Forky nedostanou nic. Kromě
GITHUB_TOKENse secrets nepředávají workflow spuštěným pull requestem z forku. - Dependabot se považuje za fork. Workflow spuštěná Dependabotem dostanou token jen pro čtení a pouze secrets pro Dependabot, ne secrets pro Actions.
- Opakovaně použitelným workflow je nutné secrets předat.
secrets: inheritje předá všechny implicitně, v rámci stejné organizace nebo enterprise. Secrets prostředí nelze předat přesworkflow_call. - Prostředí řídí přístup. S povinnými schvalovateli job nemůže číst secrets prostředí, dokud ho někdo neschválí. Pravidla pro větve nasazení omezují, které větve mohou prostředí vůbec použít.
Co maskování dělá a co ne
GitHub hodnoty secrets z logů odstraňuje, ale spoléhá přitom na nalezení přesné shody. Vlastní dokumentace GitHubu uvádí, kde selhává:
- secret uvnitř strukturovaných dat, jako je JSON nebo YAML
- hodnota odvozená ze secretu, například token vyměněný za jiný token, pokud ji nezaregistrujete pomocí
::add-mask:: - secret, který byl transformován, například zakódován do base64
Maskování chrání před tím, abyste secret omylem vypsali. Proti kódu, který secret nejdřív zakóduje, nedělá nic.
Jak secrets z workflow unikají
1. Kompromitovaná action je vypíše
V březnu 2025 útočník kompromitoval tj-actions/changed-files (CVE-2025-30066), action používanou více než 23 000 repozitáři. Existující tagy verzí action přesunul na škodlivý commit, takže každé workflow, které odkazovalo na tag jako @v45, spustilo kód útočníka. V bezpečí byla jen workflow připnutá na plné SHA commitu.
Payload četl secrets z paměti runneru a vypisoval je do logu jobu, dvakrát zakódované do base64, aby je maskování nezachytilo. U veřejných repozitářů může tyto logy číst kdokoli. Kompromitace začala u jiné action, reviewdog/action-setup (CVE-2025-30154), a ještě předtím u osobního přístupového tokenu ukradeného přes workflow s pull_request_target.
2. Workflow s pull_request_target spustí nedůvěryhodný kód
pull_request_target běží v kontextu základního repozitáře, s jeho secrets a tokenem, který může zapisovat, i když pull request přichází z forku. To je bezpečné jen do té doby, dokud workflow nikdy nespustí kód z pull requestu. Když workflow checkoutne head pull requestu a sestaví ho, kdokoli, kdo může otevřít pull request, si přečte všechny secrets, které workflow má. GitHub Security Lab tomu říká „pwn requests“.
GitHub tento prostor postupně zužuje. Od prosince 2025 pull_request_target vždy používá soubor workflow z výchozí větve. Od července 2026 actions/checkout odmítá checkoutnout kód z forku ve workflow s pull_request_target, která používají známé nebezpečné vzory, a ochrany spouštění workflow, obecně dostupné od září 2026, od 2. listopadu 2026 ve výchozím stavu vypnou pull_request_target ve veřejných repozitářích bez politiky událostí.
3. Nedůvěryhodný vstup se vloží do skriptu
Výrazy ve workflow se rozbalí dřív, než se spustí shell. Krok jako tento:
- run: echo "Title: ${{ github.event.issue.title }}"
provede cokoli, co obsahuje název issue. Útočník otevře issue, jehož název je příkaz shellu, a job ho spustí, se secrets jobu na dosah. Nedůvěryhodné hodnoty místo toho předávejte přes proměnnou prostředí, aby je shell bral jako data:
- env:
TITLE: ${{ github.event.issue.title }}
run: echo "Title: $TITLE"
4. Cache a artefakty je vynesou ven
- Artefakty. V roce 2024 výzkum ArtiPACKED od Unit 42 ukázal, že
actions/checkoutve výchozím stavu zapisujeGITHUB_TOKENdo.git/config, takže nahrání pracovního adresáře jako artefaktu prozradí token každému, kdo si ho může stáhnout.upload-artifactod září 2024 ve výchozím stavu vynechává skryté soubory aactions/checkoutv6 ukládá přihlašovací údaj mimo adresář checkoutu, ale starší připnuté verze a vlastní kroky pro nahrávání stále prozrazují. - Cache. Útočník, který může spustit kód v jakémkoli workflow repozitáře, může naplnit jeho cache, vytlačit skutečné položky a podstrčit otrávenou položku pod stejným klíčem. Pozdější privilegované workflow ji obnoví. V květnu 2026 tento řetězec, který začal u
pull_request_targeta skončil tokenem OIDC přečteným z paměti runneru, umožnil útočníkovi vydat 84 škodlivých verzí 42 balíčků TanStack.
5. Závislost je pošle na server
Instalační skripty běží se stejným přístupem jako zbytek jobu. Červ Shai-Hulud, poprvé zaznamenaný na npm v září 2025, běžel během npm install, sbíral z prostředí tokeny npm, tokeny GitHubu a cloudové klíče a odesílal je do veřejných repozitářů na GitHubu. Jeho vlna z listopadu 2025 běžela ve fázi preinstall a registrovala napadený stroj jako vlastní (self-hosted) runner, aby se útočník mohl později vrátit.
Tato cesta nevyžaduje žádnou chybu ve vašem workflow. Stačí jediná kompromitovaná verze kdekoli ve stromu závislostí a secrets odejdou po síti.
Jak je zabezpečit
Uložené cloudové klíče nahraďte OIDC. Místo uchovávání klíče k AWS, Azure nebo Google Cloud jako secretu nechte job vyžádat si krátkodobý token od vydavatele OIDC GitHubu a vyměnit ho u poskytovatele cloudu. Politika důvěry rozhoduje, který repozitář, větev nebo prostředí může roli převzít, a přihlašovací údaj vyprší spolu s jobem. Job potřebuje permissions: id-token: write. Tokeny OIDC lze z běžícího jobu stále ukrást, jak ukázal TanStack, ale ukradený token má mnohem menší hodnotu než klíč, který nikdy nevyprší.
Připínejte actions na plné SHA commitu. Dokumentace GitHubu to označuje za jediný způsob, jak použít action jako neměnné vydání. Tagy lze přesunout, SHA ne. Od srpna 2025 umí politika povolených actions vynutit připínání na SHA a blokovat konkrétní actions v celé organizaci. Dependabot a Renovate dokážou připnutá SHA udržovat aktuální.
Dejte GITHUB_TOKEN jen to, co potřebuje. Výchozí nastavení nastavte jen na čtení a oprávnění k zápisu udělujte po jednotlivých jobech. Nové organizace a repozitáře mají výchozí nastavení jen pro čtení od února 2023, ale starší si ponechaly benevolentní výchozí nastavení, pokud ho někdo nezměnil. Viz Oprávnění GITHUB_TOKEN: nejnižší oprávnění pro každé workflow.
Omezte secrets na krok, který je potřebuje. Secret v env: kroku je jen v prostředí tohoto kroku. Secret nastavený na úrovni jobu vidí každý krok, včetně každé action třetí strany.
Produkční secrets dejte za prostředí. Povinní schvalovatelé a pravidla pro větve nasazení zajistí, že je workflow na feature větvi nebo workflow spuštěné pull requestem nepřečte.
Berte pull_request_target a nedůvěryhodný vstup jako spuštění kódu. Vyhněte se pull_request_target, pokud ho nepotřebujete, nikdy v něm nesestavujte kód z pull requestu a data událostí předávejte přes proměnné prostředí. CodeQL umí ve workflow kontrolovat vzory injekce a pwn requestů; pro GitHub Actions je obecně dostupný od dubna 2025.
Zapněte skenování tajných klíčů a push protection. Zachytí secrets commitnuté do repozitáře, což je jiný únik než ty výše, ale stejně častý.
Omezte, kam se job může připojit. Většina jobů potřebuje krátký seznam cílů: registry balíčků, registry kontejnerů a několik API. Pokud se job dostane jen k nim, secret přečtený škodlivým kódem nemá kam odejít. Viz jak egress policy zastaví únik dat z GitHub Actions.
Po každém vyzrazení rotujte. Pokud workflow spustilo kompromitovanou action nebo balíček, předpokládejte, že všechny secrets, které mohlo číst, byly ukradeny. Zrušte a znovu vydejte všechny, nejen ty, o kterých si myslíte, že byly použity.
Kde má své místo CRACI
CRACI spouští vaše joby GitHub Actions na vlastních runnerech: změníte runs-on na craci a workflow, secrets i historie běhů zůstávají v GitHubu. Dvě věci, které dělá, míří na úniky popsané výše.
- Egress policy, která při selhání vše zablokuje. Nastavíte, ke kterým zdrojům balíčků, registrům a hostitelům se job může dostat. Politika se ověří před spuštěním jobu a vynucuje se na runneru, takže spojení kamkoli jinam je zablokováno, a při porušení politiky dostanete upozornění.
- Záznam o tom, co job dělal. CRACI zaznamenává každý balíček, který každý job skutečně stáhl, včetně balíčků obnovených z cache, a záznam síťových spojení jobu. Až bude ohlášen další kompromitovaný balíček, uvidíte, které buildy ho stáhly a kam se připojovaly, místo hádání z lockfilů.
Každý job běží ve vlastním virtuálním stroji, takže nic, co jeden job zanechá, nečeká na další. CRACI ve vašem kódu tajné klíče neskenuje; na to si ponechte skenování tajných klíčů od GitHubu.
Rezervujte si demo a prohlédněte si záznam jednoho z vašich vlastních buildů.
