Et GitHub Actions-job er et godt sted at stjæle secrets fra. Det indeholder deploy-nøgler, registry-tokens og cloud-legitimationsoplysninger, og det kører kode, som en anden skrev for et minut siden: tredjeparts-actions, pakker og deres install-scripts. GitHubs håndtering af secrets beskytter mod uheld. Den er ikke designet til at stoppe kode, der allerede kører inde i jobbet.
Dette indlæg gennemgår, hvordan secrets virker i GitHub Actions, de fem måder, de lækker på i praksis, og de kontroller, der lukker hver af dem.
Sådan virker GitHub Actions-secrets
Secrets findes på tre niveauer: organisation, repository og environment. Når samme navn findes på mere end ét niveau, vinder det mest specifikke: environment over repository, repository over organisation.
Et par grænser og regler er værd at kende:
- Størrelse og antal. Op til 1.000 organisations-secrets, 100 repository-secrets og 100 environment-secrets, hver på højst 48 KB. Et workflow ser alle repository- og environment-secrets, men kun de første 100 organisations-secrets i alfabetisk rækkefølge.
- Forks får intet. Bortset fra
GITHUB_TOKENbliver secrets ikke givet videre til workflows, der udløses af en pull request fra en fork. - Dependabot behandles som en fork. Workflows udløst af Dependabot får et skrivebeskyttet token og kun Dependabot-secrets, ikke Actions-secrets.
- Genanvendelige workflows skal have secrets givet med.
secrets: inheritgiver dem alle videre implicit, inden for samme organisation eller enterprise. Environment-secrets kan ikke gives videre viaworkflow_call. - Environments styrer adgangen. Med påkrævede reviewers kan et job ikke læse et environments secrets, før nogen godkender det. Regler for deployment-branches begrænser, hvilke branches der overhovedet kan bruge environmentet.
Hvad maskering gør, og hvad den ikke gør
GitHub fjerner secret-værdier fra logs, men fjernelsen afhænger af et nøjagtigt match. GitHubs egen dokumentation viser, hvor den fejler:
- en secret inde i strukturerede data som JSON eller YAML
- en værdi afledt af en secret, for eksempel et token byttet til et andet token, medmindre du registrerer den med
::add-mask:: - en secret, der er blevet transformeret, for eksempel base64-kodet
Maskering er en beskyttelse mod at komme til at udskrive en secret ved en fejl. Den gør intet mod kode, der først koder secreten.
Sådan lækker secrets fra workflows
1. En kompromitteret action udskriver dem
I marts 2025 kompromitterede en angriber tj-actions/changed-files (CVE-2025-30066), en action brugt af mere end 23.000 repositories. Angriberen flyttede actionens eksisterende versionstags til et ondsindet commit, så hvert workflow, der refererede til et tag som @v45, kørte angriberens kode. Kun workflows fastlåst til en fuld commit-SHA var sikre.
Payloaden læste secrets ud af runnerens hukommelse og udskrev dem til jobloggen, base64-kodet to gange, så maskeringen ikke fangede dem. På offentlige repositories kan alle læse de logs. Kompromitteringen startede fra en anden action, reviewdog/action-setup (CVE-2025-30154), og før det fra et personligt adgangstoken stjålet via et pull_request_target-workflow.
2. Et pull_request_target-workflow kører ikke-betroet kode
pull_request_target kører i base-repositoriets kontekst, med dets secrets og et token, der kan skrive, selv når pull requesten kommer fra en fork. Det er kun sikkert, så længe workflowet aldrig kører kode fra pull requesten. Når et workflow checker pull requestens head ud og bygger den, kan enhver, der kan åbne en pull request, læse hver secret, workflowet har. GitHub Security Lab kalder dem "pwn requests".
GitHub har strammet det her. Siden december 2025 bruger pull_request_target altid workflow-filen fra default-branchen. Siden juli 2026 nægter actions/checkout at checke fork-kode ud i pull_request_target-workflows, der bruger kendte farlige mønstre, og workflow execution protections, der har været generelt tilgængelige siden september 2026, vil fra den 2. november 2026 som standard slå pull_request_target fra i offentlige repositories uden en event policy.
3. Ikke-betroet input bliver injiceret i et script
Workflow-udtryk bliver udfoldet, før shellen kører. Et trin som dette:
- run: echo "Title: ${{ github.event.issue.title }}"
eksekverer, hvad end issue-titlen indeholder. En angriber åbner et issue med en shell-kommando som titel, og jobbet kører den, med jobbets secrets inden for rækkevidde. Send i stedet ikke-betroede værdier gennem en miljøvariabel, så shellen behandler dem som data:
- env:
TITLE: ${{ github.event.issue.title }}
run: echo "Title: $TITLE"
4. Caches og artefakter bærer dem ud
- Artefakter. I 2024 viste Unit 42's ArtiPACKED-research, at
actions/checkoutsom standard skriverGITHUB_TOKENi.git/config, så upload af workspacet som artefakt lækker tokenet til alle, der kan downloade det.upload-artifacthar som standard udeladt skjulte filer siden september 2024, ogactions/checkoutv6 gemmer legitimationsoplysningerne uden for checkout-mappen, men ældre fastlåste versioner og brugerdefinerede upload-trin lækker stadig. - Caches. En angriber, der kan køre kode i et hvilket som helst workflow i et repository, kan fylde dets cache, skubbe de rigtige poster ud og plante en forgiftet post under samme nøgle. Et senere privilegeret workflow gendanner den. I maj 2026 gjorde denne kæde, der startede fra
pull_request_targetog sluttede med et OIDC-token læst fra runnerens hukommelse, det muligt for en angriber at udgive 84 ondsindede versioner af 42 TanStack-pakker.
5. En afhængighed sender dem til en server
Install-scripts kører med samme adgang som resten af jobbet. Shai-Hulud-ormen, der først blev set på npm i september 2025, kørte under npm install, indsamlede npm-tokens, GitHub-tokens og cloud-nøgler fra miljøet og postede dem til offentlige GitHub-repositories. Bølgen i november 2025 kørte ved preinstall og registrerede den inficerede maskine som en selvhostet runner, så angriberen kunne komme tilbage senere.
Denne vej kræver ingen fejl i dit workflow. Én kompromitteret version et sted i afhængighedstræet er nok, og secrets forlader jobbet over netværket.
Sådan låser du dem ned
Erstat gemte cloud-nøgler med OIDC. I stedet for at gemme en AWS-, Azure- eller Google Cloud-nøgle som secret kan du lade jobbet bede om et kortlivet token fra GitHubs OIDC-udsteder og bytte det hos cloud-udbyderen. Trust-policyen afgør, hvilket repository, hvilken branch eller hvilket environment der må overtage rollen, og legitimationsoplysningerne udløber med jobbet. Jobbet skal have permissions: id-token: write. OIDC-tokens kan stadig stjæles fra et kørende job, som TanStack viste, men et stjålet token er langt mindre værd end en nøgle, der aldrig udløber.
Fastlås actions på en fuld commit-SHA. GitHubs dokumentation kalder det den eneste måde at bruge en action som en uforanderlig release. Tags kan flyttes, det kan en SHA ikke. Siden august 2025 kan allowed-actions-policyen håndhæve SHA-fastlåsning og blokere bestemte actions på tværs af en organisation. Dependabot og Renovate kan holde fastlåste SHA'er opdateret.
Giv GITHUB_TOKEN det mindste, det har brug for. Sæt standarden til skrivebeskyttet, og tildel skriverettigheder pr. job. Nye organisationer og repositories har haft skrivebeskyttet som standard siden februar 2023, men ældre beholdt den permissive standard, medmindre nogen ændrede den. Se GITHUB_TOKEN-rettigheder: least privilege for hvert workflow.
Afgræns secrets til det trin, der har brug for dem. En secret i et trins env: er kun i det trins miljø. En secret sat på jobniveau er synlig for hvert trin, inklusive hver tredjeparts-action.
Læg produktions-secrets bag environments. Påkrævede reviewers og regler for deployment-branches betyder, at et workflow på en feature-branch eller et workflow udløst af en pull request ikke kan læse dem.
Behandl pull_request_target og ikke-betroet input som kodeeksekvering. Undgå pull_request_target, medmindre du har brug for det, byg aldrig pull request-kode i det, og send event-data gennem miljøvariabler. CodeQL kan tjekke workflows for injection og pwn request-mønstre og har været generelt tilgængelig for GitHub Actions siden april 2025.
Slå secret scanning og push protection til. De fanger secrets, der er committet til repositoriet, hvilket er en anden type læk end dem ovenfor, men lige så almindeligt.
Begræns, hvad jobbet kan oprette forbindelse til. De fleste jobs har brug for en kort liste over destinationer: pakkeregistre, container-registre og nogle få API'er. Hvis jobbet kun kan nå dem, har en secret læst af ondsindet kode ingen steder at tage hen. Se hvordan en egress-politik stopper eksfiltrering fra GitHub Actions.
Rotér efter enhver eksponering. Hvis et workflow kørte en kompromitteret action eller pakke, så gå ud fra, at hver secret, det kunne læse, er taget. Tilbagekald og genudsted dem alle, ikke kun dem, du tror, blev brugt.
Her passer CRACI ind
CRACI kører dine GitHub Actions-jobs på sine egne runnere: Du ændrer runs-on til craci, og workflows, secrets og kørselshistorik bliver i GitHub. To af de ting, det gør, er rettet mod lækkene ovenfor.
- En egress-politik, der blokerer ved fejl. Du bestemmer, hvilke pakkekilder, registre og hosts et job må nå. Politikken valideres, før jobbet starter, og håndhæves på runneren, så en forbindelse til alt andet bliver blokeret, og du får en alarm, når politikken bliver overtrådt.
- En registrering af, hvad jobbet gjorde. CRACI registrerer hver pakke, hvert job faktisk hentede, inklusive pakker gendannet fra caches, og et trace af jobbets netværksforbindelser. Når den næste kompromitterede pakke bliver annonceret, kan du se, hvilke builds der trak den ind, og hvad de oprettede forbindelse til, i stedet for at gætte ud fra lockfiles.
Hvert job kører i sin egen virtuelle maskine, så intet, ét job efterlader, er der til det næste. CRACI scanner ikke efter secrets i din kode, så behold GitHubs secret scanning til det.
Book en demo for at se registreringen af et af dine egne builds.
