GitHub Actions -jobi on hyvä paikka varastaa salaisuuksia. Siinä on käyttöönottoavaimia, rekisterien tokeneita ja pilvipalvelujen tunnuksia, ja se ajaa koodia, jonka joku muu kirjoitti minuutti sitten: kolmansien osapuolten actioneita, paketteja ja niiden asennusskriptejä. GitHubin salaisuuksien käsittely suojaa vahingoilta. Sitä ei ole suunniteltu pysäyttämään koodia, joka on jo käynnissä jobin sisällä.
Tämä kirjoitus käy läpi, miten salaisuudet toimivat GitHub Actionsissa, viisi tapaa, joilla ne käytännössä vuotavat, ja kontrollit, jotka sulkevat kunkin.
Miten GitHub Actionsin salaisuudet toimivat
Salaisuuksia on kolmella tasolla: organisaatio, repositorio ja ympäristö. Kun sama nimi on useammalla tasolla, tarkin voittaa: ympäristö ohittaa repositorion ja repositorio organisaation.
Muutama raja ja sääntö, jotka kannattaa tuntea:
- Koko ja määrä. Enintään 1 000 organisaation salaisuutta, 100 repositorion salaisuutta ja 100 ympäristön salaisuutta, kukin enintään 48 kt. Työnkulku näkee jokaisen repositorion ja ympäristön salaisuuden, mutta vain 100 ensimmäistä organisaation salaisuutta aakkosjärjestyksessä.
- Forkit eivät saa mitään.
GITHUB_TOKENia lukuun ottamatta salaisuuksia ei välitetä työnkuluille, jotka käynnistyvät forkista tulevasta pull requestista. - Dependabotia kohdellaan kuin forkkia. Dependabotin käynnistämät työnkulut saavat vain lukuoikeudellisen tokenin ja vain Dependabot-salaisuudet, eivät Actions-salaisuuksia.
- Uudelleenkäytettäville työnkuluille salaisuudet on välitettävä.
secrets: inheritvälittää ne kaikki implisiittisesti saman organisaation tai enterprisen sisällä. Ympäristön salaisuuksia ei voi välittääworkflow_call-kutsun kautta. - Ympäristöt rajaavat pääsyä. Kun vaaditut tarkastajat on määritetty, jobi ei voi lukea ympäristön salaisuuksia ennen kuin joku hyväksyy sen. Käyttöönottohaarojen säännöt rajaavat, mitkä haarat voivat ylipäätään käyttää ympäristöä.
Mitä maskaus tekee ja mitä ei
GitHub peittää salaisuuksien arvot lokeista, mutta peittäminen perustuu tarkkaan osumaan. GitHubin oma dokumentaatio luettelee, milloin se pettää:
- salaisuus jäsennellyn datan, kuten JSONin tai YAML:n, sisällä
- salaisuudesta johdettu arvo, kuten toiseen tokeniin vaihdettu token, ellet rekisteröi sitä komennolla
::add-mask:: - muunnettu salaisuus, esimerkiksi base64-koodattu
Maskaus suojaa siltä, että salaisuus tulostetaan vahingossa. Se ei tee mitään koodia vastaan, joka koodaa salaisuuden ensin.
Miten salaisuudet vuotavat työnkuluista
1. Vaarantunut action tulostaa ne
Maaliskuussa 2025 hyökkääjä vaaransi actionin tj-actions/changed-files (CVE-2025-30066), jota käytti yli 23 000 repositoriota. Hän siirsi actionin olemassa olevat versiotagit haitalliseen committiin, joten jokainen työnkulku, joka viittasi tagiin kuten @v45, ajoi hyökkääjän koodia. Vain täyteen commit-SHA:han lukitut työnkulut olivat turvassa.
Haittakoodi luki salaisuudet runnerin muistista ja tulosti ne jobin lokiin kahdesti base64-koodattuina, jotta maskaus ei tunnistaisi niitä. Julkisissa repositorioissa nuo lokit ovat kenen tahansa luettavissa. Murto alkoi toisesta actionista, reviewdog/action-setup (CVE-2025-30154), ja sitä ennen henkilökohtaisesta käyttötunnuksesta (personal access token), joka varastettiin pull_request_target-työnkulun kautta.
2. pull_request_target-työnkulku ajaa epäluotettavaa koodia
pull_request_target ajetaan perusrepositorion kontekstissa sen salaisuuksien ja kirjoitusoikeudellisen tokenin kanssa, vaikka pull request tulisi forkista. Tämä on turvallista vain niin kauan kuin työnkulku ei koskaan aja pull requestin koodia. Kun työnkulku hakee pull requestin headin ja rakentaa sen, kuka tahansa, joka voi avata pull requestin, voi lukea jokaisen työnkulun salaisuuden. GitHub Security Lab kutsuu näitä nimellä "pwn requests".
GitHub on kaventanut tätä. Joulukuusta 2025 alkaen pull_request_target käyttää aina oletushaaran työnkulkutiedostoa. Heinäkuusta 2026 alkaen actions/checkout kieltäytyy hakemasta forkin koodia pull_request_target-työnkuluissa, joissa käytetään tunnetusti vaarallisia malleja, ja syyskuusta 2026 yleisesti saatavilla olevat workflow execution protections -suojaukset poistavat pull_request_targetin oletuksena käytöstä julkisissa repositorioissa, joilla ei ole tapahtumakäytäntöä, 2.11.2026 alkaen.
3. Epäluotettava syöte injektoidaan skriptiin
Työnkulun lausekkeet laajennetaan ennen kuin shell ajetaan. Tällainen vaihe:
- run: echo "Title: ${{ github.event.issue.title }}"
suorittaa sen, mitä issuen otsikko sisältää. Hyökkääjä avaa issuen, jonka otsikkona on shell-komento, ja jobi ajaa sen jobin salaisuudet ulottuvillaan. Välitä epäluotettavat arvot sen sijaan ympäristömuuttujan kautta, jolloin shell käsittelee ne datana:
- env:
TITLE: ${{ github.event.issue.title }}
run: echo "Title: $TITLE"
4. Välimuistit ja artefaktit kuljettavat ne ulos
- Artefaktit. Vuonna 2024 Unit 42:n ArtiPACKED-tutkimus osoitti, että
actions/checkoutkirjoittaa oletuksenaGITHUB_TOKENin tiedostoon.git/config, joten työtilan lataaminen artefaktina vuotaa tokenin kaikille, jotka voivat ladata sen.upload-artifacton jättänyt piilotiedostot oletuksena pois syyskuusta 2024 alkaen, jaactions/checkoutv6 tallentaa tunnuksen checkout-hakemiston ulkopuolelle, mutta vanhemmat lukitut versiot ja omat latausvaiheet vuotavat edelleen. - Välimuistit. Hyökkääjä, joka voi ajaa koodia missä tahansa repositorion työnkulussa, voi täyttää sen välimuistin, työntää oikeat merkinnät ulos ja istuttaa myrkytetyn merkinnän samalla avaimella. Myöhempi etuoikeutettu työnkulku palauttaa sen. Toukokuussa 2026 tämä ketju, joka alkoi
pull_request_targetista ja päättyi runnerin muistista luettuun OIDC-tokeniin, antoi hyökkääjän julkaista 84 haitallista versiota 42 TanStack-paketista.
5. Riippuvuus lähettää ne palvelimelle
Asennusskriptit ajetaan samoilla oikeuksilla kuin muu jobi. Shai-Hulud-mato, joka havaittiin npm:ssä ensimmäisen kerran syyskuussa 2025, ajettiin npm install -komennon aikana, keräsi ympäristöstä npm-tokenit, GitHub-tokenit ja pilviavaimet ja lähetti ne julkisiin GitHub-repositorioihin. Sen marraskuun 2025 aalto ajettiin preinstall-vaiheessa ja rekisteröi saastuneen koneen itse ylläpidetyksi runneriksi, jotta hyökkääjä voisi palata myöhemmin.
Tämä reitti ei vaadi virhettä työnkulussasi. Yksi vaarantunut versio missä tahansa riippuvuuspuussa riittää, ja salaisuudet lähtevät verkon yli.
Miten ne suojataan
Korvaa tallennetut pilviavaimet OIDC:llä. Sen sijaan, että säilyttäisit AWS-, Azure- tai Google Cloud -avaimen salaisuutena, anna jobin pyytää lyhytikäinen token GitHubin OIDC-myöntäjältä ja vaihtaa se pilvipalveluntarjoajalla. Luottamuskäytäntö ratkaisee, mikä repositorio, haara tai ympäristö voi ottaa roolin, ja tunnus vanhenee jobin mukana. Jobi tarvitsee asetuksen permissions: id-token: write. OIDC-tokeneita voi yhä varastaa käynnissä olevasta jobista, kuten TanStack osoitti, mutta varastettu token on paljon vähemmän arvokas kuin avain, joka ei koskaan vanhene.
Lukitse actionit täyteen commit-SHA:han. GitHubin dokumentaation mukaan se on ainoa tapa käyttää actionia muuttumattomana julkaisuna. Tageja voi siirtää, SHA:ta ei. Elokuusta 2025 alkaen sallittujen actionien käytäntö voi pakottaa SHA-lukituksen ja estää tiettyjä actioneita koko organisaatiossa. Dependabot ja Renovate voivat pitää lukitut SHA:t ajan tasalla.
Anna GITHUB_TOKENille vain se, mitä se tarvitsee. Aseta oletukseksi vain luku ja myönnä kirjoitusoikeudet jobikohtaisesti. Uusien organisaatioiden ja repositorioiden oletus on ollut vain luku helmikuusta 2023 alkaen, mutta vanhemmat säilyttivät sallivan oletuksen, ellei joku muuttanut sitä. Katso GITHUB_TOKEN-oikeudet: vähimmät oikeudet jokaiselle työnkululle.
Rajaa salaisuudet vaiheeseen, joka niitä tarvitsee. Vaiheen env:-kohdassa oleva salaisuus on vain sen vaiheen ympäristössä. Jobitasolla asetettu salaisuus näkyy jokaiselle vaiheelle, myös jokaiselle kolmannen osapuolen actionille.
Pidä tuotannon salaisuudet ympäristöjen takana. Vaaditut tarkastajat ja käyttöönottohaarojen säännöt tarkoittavat, että feature-haaran työnkulku tai pull requestin käynnistämä työnkulku ei voi lukea niitä.
Käsittele pull_request_targetia ja epäluotettavaa syötettä koodin suorituksena. Vältä pull_request_targetia, ellet tarvitse sitä, älä koskaan rakenna siinä pull requestin koodia ja välitä tapahtumadata ympäristömuuttujien kautta. CodeQL voi tarkistaa työnkulut injektio- ja pwn request -mallien varalta; se on ollut yleisesti saatavilla GitHub Actionsille huhtikuusta 2025 alkaen.
Ota käyttöön salaisuuksien skannaus ja push protection. Ne havaitsevat repositorioon commitoidut salaisuudet, mikä on eri vuoto kuin edellä kuvatut, mutta yhtä yleinen.
Rajaa, mihin jobi voi muodostaa yhteyden. Useimmat jobit tarvitsevat lyhyen listan kohteita: pakettirekisterit, konttirekisterit ja muutaman API:n. Jos jobi tavoittaa vain ne, haitallisen koodin lukemalla salaisuudella ei ole minne mennä. Katso, miten egress-käytäntö pysäyttää tietojen vuotamisen GitHub Actionsista.
Vaihda salaisuudet jokaisen altistumisen jälkeen. Jos työnkulku ajoi vaarantuneen actionin tai paketin, oleta, että jokainen salaisuus, jonka se pystyi lukemaan, on viety. Mitätöi ja myönnä ne kaikki uudelleen, ei vain niitä, joita luulet käytetyn.
Mihin CRACI sopii
CRACI ajaa GitHub Actions -jobisi omilla runnereillaan: vaihdat runs-on-arvoksi craci, ja työnkulut, salaisuudet ja ajohistoria pysyvät GitHubissa. Kaksi sen toimintoa on suunnattu edellä kuvattuja vuotoja vastaan.
- Egress-käytäntö, joka toimii fail closed -periaatteella. Määrität, mihin pakettilähteisiin, rekistereihin ja isäntiin jobi saa ottaa yhteyden. Käytäntö tarkistetaan ennen jobin alkua ja sitä valvotaan runnerilla, joten yhteys mihin tahansa muualle estetään, ja saat hälytyksen, kun käytäntöä rikotaan.
- Tallenne siitä, mitä jobi teki. CRACI tallentaa jokaisen paketin, jonka kukin jobi todella haki, myös välimuisteista palautetut paketit, sekä jäljen jobin verkkoyhteyksistä. Kun seuraava vaarantunut paketti julkistetaan, näet, mitkä buildit hakivat sen ja mihin ne ottivat yhteyttä, eikä sinun tarvitse arvailla lukitustiedostojen perusteella.
Jokainen jobi ajetaan omassa virtuaalikoneessaan, joten mitään, mitä yksi jobi jättää jälkeensä, ei ole seuraavan saatavilla. CRACI ei skannaa koodiasi salaisuuksien varalta; käytä siihen GitHubin salaisuuksien skannausta.
Varaa demo ja näe tallenne yhdestä omasta buildistasi.
