Ett GitHub Actions-jobb är ett bra ställe att stjäla hemligheter från. Det har driftsättningsnycklar, registertokens och molnuppgifter, och det kör kod som någon annan skrev för en minut sedan: tredjepartsactions, paket och deras installationsskript. GitHubs hantering av hemligheter skyddar mot misstag. Den var inte utformad för att stoppa kod som redan körs inne i jobbet.

Det här inlägget går igenom hur hemligheter fungerar i GitHub Actions, de fem sätt de läcker på i praktiken och kontrollerna som stänger vart och ett.

Så fungerar hemligheter i GitHub Actions

Hemligheter finns på tre nivåer: organisation, repo och miljö. När samma namn finns på mer än en nivå vinner den mest specifika: miljö går före repo, repo går före organisation.

Några begränsningar och regler som är värda att känna till:

  • Storlek och antal. Upp till 1 000 organisationshemligheter, 100 repohemligheter och 100 miljöhemligheter, var och en högst 48 KB. Ett arbetsflöde ser varje repo- och miljöhemlighet, men bara de första 100 organisationshemligheterna i alfabetisk ordning.
  • Forkar får ingenting. Utöver GITHUB_TOKEN skickas inga hemligheter till arbetsflöden som utlöses av en pull request från en fork.
  • Dependabot behandlas som en fork. Arbetsflöden som utlöses av Dependabot får en skrivskyddad token och bara Dependabot-hemligheter, inte Actions-hemligheter.
  • Återanvändbara arbetsflöden måste få hemligheter skickade till sig. secrets: inherit skickar alla implicit, inom samma organisation eller enterprise. Miljöhemligheter kan inte skickas via workflow_call.
  • Miljöer styr åtkomsten. Med obligatoriska granskare kan ett jobb inte läsa en miljös hemligheter förrän någon godkänt det. Regler för driftsättningsgrenar begränsar vilka grenar som över huvud taget får använda miljön.

Vad maskering gör, och inte gör

GitHub döljer hemliga värden i loggar, men döljningen bygger på en exakt matchning. GitHubs egen dokumentation listar var den misslyckas:

  • en hemlighet i strukturerade data som JSON eller YAML
  • ett värde som härletts från en hemlighet, till exempel en token som bytts mot en annan token, om du inte registrerar det med ::add-mask::
  • en hemlighet som har omvandlats, till exempel base64-kodats

Maskering skyddar mot att skriva ut en hemlighet av misstag. Den gör ingenting mot kod som kodar hemligheten först.

Så läcker hemligheter från arbetsflöden

1. En komprometterad action skriver ut dem

I mars 2025 komprometterade en angripare tj-actions/changed-files (CVE-2025-30066), en action som användes av fler än 23 000 repon. Angriparen flyttade actionens befintliga versionstaggar till en skadlig commit, så varje arbetsflöde som refererade till en tagg som @v45 körde angriparens kod. Bara arbetsflöden som var låsta till en fullständig commit-SHA var säkra.

Nyttolasten läste hemligheter ur runnerns minne och skrev ut dem i jobbloggen, dubbelt base64-kodade så att maskeringen inte skulle fånga dem. På publika repon kan vem som helst läsa de loggarna. Komprometteringen började i en annan action, reviewdog/action-setup (CVE-2025-30154), och innan dess med en personlig åtkomsttoken som stulits via ett pull_request_target-arbetsflöde.

2. Ett pull_request_target-arbetsflöde kör opålitlig kod

pull_request_target körs i basrepots kontext, med dess hemligheter och en token som kan skriva, även när pull requesten kommer från en fork. Det är säkert bara så länge arbetsflödet aldrig kör kod från pull requesten. När ett arbetsflöde checkar ut pull requestens head och bygger den kan alla som kan öppna en pull request läsa varje hemlighet arbetsflödet har. GitHub Security Lab kallar dessa pwn requests.

GitHub har stramat åt det här. Sedan december 2025 använder pull_request_target alltid arbetsflödesfilen från standardgrenen. Sedan juli 2026 vägrar actions/checkout att checka ut fork-kod i pull_request_target-arbetsflöden som använder kända farliga mönster, och skydd för arbetsflödeskörning, allmänt tillgängliga sedan september 2026, kommer att inaktivera pull_request_target som standard i publika repon utan händelsepolicy från och med den 2 november 2026.

3. Opålitlig indata injiceras i ett skript

Arbetsflödesuttryck expanderas innan skalet körs. Ett steg som det här:

- run: echo "Title: ${{ github.event.issue.title }}"

kör vad issue-titeln än innehåller. En angripare öppnar en issue med ett skalkommando som titel och jobbet kör det, med jobbets hemligheter inom räckhåll. Skicka i stället opålitliga värden via en miljövariabel, så att skalet behandlar dem som data:

- env:
    TITLE: ${{ github.event.issue.title }}
  run: echo "Title: $TITLE"

4. Cachar och artefakter bär ut dem

  • Artefakter. 2024 visade Unit 42:s ArtiPACKED-forskning att actions/checkout som standard skriver GITHUB_TOKEN till .git/config, så att uppladdning av arbetsytan som en artefakt läcker tokenen till alla som kan ladda ner den. upload-artifact har exkluderat dolda filer som standard sedan september 2024, och actions/checkout v6 lagrar autentiseringsuppgiften utanför utcheckningskatalogen, men äldre låsta versioner och egna uppladdningssteg läcker fortfarande.
  • Cachar. En angripare som kan köra kod i vilket arbetsflöde som helst i ett repo kan fylla dess cache, tränga ut de riktiga posterna och plantera en förgiftad post under samma nyckel. Ett senare privilegierat arbetsflöde återställer den. I maj 2026 lät den här kedjan, som började med pull_request_target och slutade med en OIDC-token som lästes ur runnerns minne, en angripare publicera 84 skadliga versioner av 42 TanStack-paket.

5. Ett beroende skickar dem till en server

Installationsskript körs med samma åtkomst som resten av jobbet. Masken Shai-Hulud, som först sågs på npm i september 2025, kördes under npm install, samlade in npm-tokens, GitHub-tokens och molnnycklar från miljön och publicerade dem i publika GitHub-repon. Vågen i november 2025 kördes vid preinstall och registrerade den infekterade maskinen som en egenhostad runner, så att angriparen kunde komma tillbaka senare.

Den här vägen kräver inget misstag i ditt arbetsflöde. En enda komprometterad version någonstans i beroendeträdet räcker, och hemligheterna lämnar över nätverket.

Så låser du dem

Ersätt lagrade molnnycklar med OIDC. I stället för att ha en AWS-, Azure- eller Google Cloud-nyckel som hemlighet låter du jobbet begära en kortlivad token från GitHubs OIDC-utfärdare och byta den hos molnleverantören. Förtroendepolicyn avgör vilket repo, vilken gren eller vilken miljö som får anta rollen, och autentiseringsuppgiften upphör att gälla med jobbet. Jobbet behöver permissions: id-token: write. OIDC-tokens kan fortfarande stjälas från ett körande jobb, som TanStack visade, men en stulen token är värd långt mindre än en nyckel som aldrig går ut.

Lås actions till en fullständig commit-SHA. GitHubs dokumentation kallar det det enda sättet att använda en action som en oföränderlig release. Taggar kan flyttas, men det kan inte en SHA. Sedan augusti 2025 kan policyn för tillåtna actions kräva SHA-låsning och blockera specifika actions i hela organisationen. Dependabot och Renovate kan hålla låsta SHA:er uppdaterade.

Ge GITHUB_TOKEN så lite som den behöver. Sätt standarden till skrivskyddad och bevilja skrivbehörigheter per jobb. Nya organisationer och repon har haft skrivskyddad standard sedan februari 2023, men äldre behöll den tillåtande standarden om ingen ändrade den. Se behörigheter för GITHUB_TOKEN: minsta behörighet för varje arbetsflöde.

Begränsa hemligheter till steget som behöver dem. En hemlighet i ett stegs env: finns bara i det stegets miljö. En hemlighet som satts på jobbnivå är synlig för varje steg, inklusive varje tredjepartsaction.

Lägg produktionshemligheter bakom miljöer. Obligatoriska granskare och regler för driftsättningsgrenar innebär att ett arbetsflöde på en funktionsgren, eller ett som utlöses av en pull request, inte kan läsa dem.

Behandla pull_request_target och opålitlig indata som kodkörning. Undvik pull_request_target om du inte behöver det, bygg aldrig pull request-kod i det och skicka händelsedata via miljövariabler. CodeQL kan kontrollera arbetsflöden för injektion och pwn request-mönster, och har varit allmänt tillgängligt för GitHub Actions sedan april 2025.

Slå på secret scanning och push protection. De fångar hemligheter som committats till repot, en annan typ av läcka än de ovan men lika vanlig.

Begränsa vart jobbet kan ansluta. De flesta jobb behöver en kort lista med destinationer: paketregister, containerregister och några API:er. Om jobbet bara kan nå dem har en hemlighet som lästs av skadlig kod ingenstans att ta vägen. Se hur en egress-policy stoppar exfiltrering från GitHub Actions.

Rotera efter varje exponering. Om ett arbetsflöde körde en komprometterad action eller ett komprometterat paket, utgå från att varje hemlighet det kunde läsa togs. Återkalla och utfärda alla på nytt, inte bara dem du tror användes.

Där CRACI passar in

CRACI kör dina GitHub Actions-jobb på sina egna runners: du ändrar runs-on till craci, och arbetsflödena, hemligheterna och körhistoriken stannar i GitHub. Två saker som CRACI gör riktar sig mot läckorna ovan.

  • En egress-policy som blockerar vid fel. Du anger vilka paketkällor, register och värdar ett jobb får nå. Policyn valideras innan jobbet startar och upprätthålls på runnern, så en anslutning till något annat ställe blockeras, och du får ett larm när policyn överträds.
  • En registrering av vad jobbet gjorde. CRACI registrerar varje paket som varje jobb faktiskt hämtade, inklusive paket som återställts från cachar, och en spårning av jobbets nätverksanslutningar. När nästa komprometterade paket tillkännages kan du se vilka byggen som drog in det och vart de anslöt, i stället för att gissa utifrån lockfiler.

Varje jobb körs i en egen virtuell maskin, så inget som ett jobb lämnar efter sig finns kvar för nästa. CRACI skannar inte efter hemligheter i din kod, så behåll GitHubs secret scanning för det.

Boka en demo för att se registreringen av ett av dina egna byggen.