En GitHub Actions-jobb er et godt sted å stjele hemmeligheter fra. Den har deploy-nøkler, registry-tokens og skylegitimasjon, og den kjører kode noen andre skrev for ett minutt siden: tredjeparts actions, pakker og installasjonsskriptene deres. GitHubs håndtering av hemmeligheter beskytter mot uhell. Den ble ikke laget for å stoppe kode som allerede kjører inne i jobben.
Dette innlegget tar for seg hvordan hemmeligheter fungerer i GitHub Actions, de fem måtene de lekker på i praksis, og kontrollene som lukker hver av dem.
Slik fungerer GitHub Actions-hemmeligheter
Hemmeligheter finnes på tre nivåer: organisasjon, repositorium og miljø. Når samme navn finnes på flere nivåer, vinner det mest spesifikke: miljø over repositorium, repositorium over organisasjon.
Noen grenser og regler som er verdt å kjenne til:
- Størrelse og antall. Opptil 1 000 organisasjonshemmeligheter, 100 repositoriehemmeligheter og 100 miljøhemmeligheter, hver på maksimalt 48 KB. En workflow ser alle repositorie- og miljøhemmeligheter, men bare de første 100 organisasjonshemmelighetene i alfabetisk rekkefølge.
- Forks får ingenting. Bortsett fra
GITHUB_TOKENsendes ikke hemmeligheter til workflows som utløses av en pull request fra en fork. - Dependabot behandles som en fork. Workflows utløst av Dependabot får et skrivebeskyttet token og bare Dependabot-hemmeligheter, ikke Actions-hemmeligheter.
- Gjenbrukbare workflows må få hemmeligheter sendt inn.
secrets: inheritsender alle implisitt, innenfor samme organisasjon eller enterprise. Miljøhemmeligheter kan ikke sendes gjennomworkflow_call. - Miljøer styrer tilgangen. Med påkrevde godkjennere kan en jobb ikke lese et miljøs hemmeligheter før noen har godkjent den. Regler for utrullingsgrener begrenser hvilke grener som i det hele tatt kan bruke miljøet.
Hva maskering gjør, og hva den ikke gjør
GitHub sensurerer hemmelige verdier i logger, men sensuren er avhengig av å finne et eksakt treff. GitHubs egen dokumentasjon lister hvor det svikter:
- en hemmelighet inne i strukturerte data som JSON eller YAML
- en verdi avledet fra en hemmelighet, for eksempel et token byttet mot et annet token, med mindre du registrerer den med
::add-mask:: - en hemmelighet som er transformert, for eksempel base64-kodet
Maskering er et vern mot å skrive ut en hemmelighet ved en feiltakelse. Den gjør ingenting mot kode som koder hemmeligheten først.
Slik lekker hemmeligheter fra workflows
1. En kompromittert action skriver dem ut
I mars 2025 kompromitterte en angriper tj-actions/changed-files (CVE-2025-30066), en action brukt av mer enn 23 000 repositorier. Angriperen flyttet actionens eksisterende versjonstagger til en ondsinnet commit, så hver workflow som viste til en tagg som @v45, kjørte angriperens kode. Bare workflows festet til en full commit-SHA var trygge.
Nyttelasten leste hemmeligheter fra runnerens minne og skrev dem til jobbloggen, base64-kodet to ganger så maskeringen ikke skulle fange dem. I offentlige repositorier kan hvem som helst lese disse loggene. Kompromitteringen startet fra en annen action, reviewdog/action-setup (CVE-2025-30154), og før det fra et personlig tilgangstoken stjålet gjennom en pull_request_target-workflow.
2. En pull_request_target-workflow kjører upålitelig kode
pull_request_target kjører i konteksten til base-repositoriet, med hemmelighetene dets og et token som kan skrive, selv når pull requesten kommer fra en fork. Det er trygt bare så lenge workflowen aldri kjører kode fra pull requesten. Når en workflow sjekker ut pull requestens head og bygger den, kan alle som kan åpne en pull request, lese hver hemmelighet workflowen har. GitHub Security Lab kaller disse «pwn requests».
GitHub har snevret dette inn. Siden desember 2025 bruker pull_request_target alltid workflow-filen fra standardgrenen. Siden juli 2026 nekter actions/checkout å sjekke ut fork-kode i pull_request_target-workflows som bruker kjente farlige mønstre, og workflow execution protections, generelt tilgjengelig siden september 2026, vil deaktivere pull_request_target som standard i offentlige repositorier uten en event policy fra 2. november 2026.
3. Upålitelige inndata injiseres i et skript
Workflow-uttrykk utvides før skallet kjører. Et steg som dette:
- run: echo "Title: ${{ github.event.issue.title }}"
kjører det issue-tittelen inneholder. En angriper åpner en issue med en skallkommando som tittel, og jobben kjører den, med jobbens hemmeligheter innen rekkevidde. Send upålitelige verdier gjennom en miljøvariabel i stedet, så skallet behandler dem som data:
- env:
TITLE: ${{ github.event.issue.title }}
run: echo "Title: $TITLE"
4. Cacher og artefakter bærer dem ut
- Artefakter. I 2024 viste Unit 42s ArtiPACKED-forskning at
actions/checkoutskriverGITHUB_TOKENtil.git/configsom standard, så opplasting av arbeidsområdet som en artefakt lekker tokenet til alle som kan laste det ned.upload-artifacthar utelatt skjulte filer som standard siden september 2024, ogactions/checkoutv6 lagrer legitimasjonen utenfor checkout-katalogen, men eldre festede versjoner og egendefinerte opplastingssteg lekker fortsatt. - Cacher. En angriper som kan kjøre kode i en hvilken som helst workflow i et repositorium, kan fylle cachen, presse ut de ekte oppføringene og plante en forgiftet oppføring under samme nøkkel. En senere privilegert workflow gjenoppretter den. I mai 2026 lot denne kjeden, som startet med
pull_request_targetog endte med et OIDC-token lest fra runnerens minne, en angriper publisere 84 ondsinnede versjoner av 42 TanStack-pakker.
5. En avhengighet sender dem til en server
Installasjonsskript kjører med samme tilgang som resten av jobben. Shai-Hulud-ormen, først sett på npm i september 2025, kjørte under npm install, samlet npm-tokens, GitHub-tokens og skynøkler fra miljøet og la dem ut i offentlige GitHub-repositorier. Bølgen i november 2025 kjørte ved preinstall og registrerte den infiserte maskinen som en selvhostet runner, så angriperen kunne komme tilbake senere.
Denne veien krever ingen feil i workflowen din. Én enkelt kompromittert versjon hvor som helst i avhengighetstreet er nok, og hemmelighetene forsvinner over nettverket.
Slik låser du dem ned
Erstatt lagrede skynøkler med OIDC. I stedet for å ha en AWS-, Azure- eller Google Cloud-nøkkel som hemmelighet lar du jobben be om et kortlivet token fra GitHubs OIDC-utsteder og bytte det hos skyleverandøren. Tillitspolicyen avgjør hvilket repositorium, hvilken gren eller hvilket miljø som kan påta seg rollen, og legitimasjonen utløper med jobben. Jobben trenger permissions: id-token: write. OIDC-tokens kan fortsatt stjeles fra en kjørende jobb, slik TanStack viste, men et stjålet token er verdt langt mindre enn en nøkkel som aldri utløper.
Fest actions til en full commit-SHA. GitHubs dokumentasjon kaller dette den eneste måten å bruke en action som en uforanderlig utgivelse på. Tagger kan flyttes, en SHA kan ikke. Siden august 2025 kan policyen for tillatte actions håndheve SHA-festing og blokkere bestemte actions i en hel organisasjon. Dependabot og Renovate kan holde festede SHA-er oppdatert.
Gi GITHUB_TOKEN det minste det trenger. Sett standarden til skrivebeskyttet, og gi skrivetillatelser per jobb. Nye organisasjoner og repositorier har hatt skrivebeskyttet som standard siden februar 2023, men eldre beholdt den permissive standarden med mindre noen endret den. Se GITHUB_TOKEN-tillatelser: minste privilegium for hver workflow.
Avgrens hemmeligheter til steget som trenger dem. En hemmelighet i et stegs env: finnes bare i miljøet til det steget. En hemmelighet satt på jobbnivå er synlig for hvert steg, inkludert hver tredjeparts action.
Legg produksjonshemmeligheter bak miljøer. Påkrevde godkjennere og regler for utrullingsgrener betyr at en workflow på en funksjonsgren, eller en som utløses av en pull request, ikke kan lese dem.
Behandle pull_request_target og upålitelige inndata som kodekjøring. Unngå pull_request_target med mindre du trenger det, bygg aldri pull request-kode i det, og send event-data gjennom miljøvariabler. CodeQL kan sjekke workflows for injeksjon og pwn request-mønstre, og har vært generelt tilgjengelig for GitHub Actions siden april 2025.
Slå på secret scanning og push protection. De fanger hemmeligheter som er committet til repositoriet, en annen type lekkasje enn dem over, men like vanlig.
Begrens hvor jobben kan koble seg til. De fleste jobber trenger en kort liste med destinasjoner: pakkeregistre, container-registre og noen få API-er. Kan jobben bare nå disse, har en hemmelighet som ondsinnet kode har lest, ingen steder å dra. Se slik stopper en egress-policy eksfiltrering fra GitHub Actions.
Roter etter enhver eksponering. Hvis en workflow kjørte en kompromittert action eller pakke, må du anta at hver hemmelighet den kunne lese, er tatt. Tilbakekall og utsted alle på nytt, ikke bare dem du tror ble brukt.
Der CRACI kommer inn
CRACI kjører GitHub Actions-jobbene dine på egne runnere: Du endrer runs-on til craci, og workflows, hemmeligheter og kjørehistorikk blir i GitHub. To ting CRACI gjør, retter seg mot lekkasjene over.
- En egress-policy som blokkerer ved feil. Du bestemmer hvilke pakkekilder, registre og verter en jobb kan nå. Policyen valideres før jobben starter og håndheves på runneren, så en tilkobling til alle andre steder blokkeres, og du får et varsel når policyen brytes.
- En registrering av hva jobben gjorde. CRACI registrerer hver pakke hver jobb faktisk hentet, inkludert pakker gjenopprettet fra cacher, og et spor av jobbens nettverkstilkoblinger. Når den neste kompromitterte pakken blir kjent, kan du se hvilke bygg som hentet den og hva de koblet seg til, i stedet for å gjette ut fra lockfiler.
Hver jobb kjører i sin egen virtuelle maskin, så ingenting én jobb legger igjen, er der for den neste. CRACI skanner ikke etter hemmeligheter i koden din. Behold GitHubs secret scanning til det.
Book en demo for å se registreringen av et av dine egne bygg.
