GitHub Actions, depoya gelen her push, pull request veya zamanlanmış tetikleyicide otomatik kod çalıştıran yaygın bir CI/CD platformudur. Bu otomasyon gücü aynı zamanda geniş bir saldırı yüzeyi yaratır: iş akışı içinde çalışan kod deponun GITHUB_TOKEN'ına, tanımlı secret'lara ve çoğu zaman doğrudan bulut altyapısına erişebilir. Ele geçirilmiş tek bir üçüncü taraf action ya da kötü niyetli bir pull request, tüm yazılım tedarik zincirini tehlikeye atabilir.

Bu rehberde GitHub'ın resmî güvenlik dokümantasyonunu temel alarak bir iş akışını adım adım sertleştiriyoruz: en az yetki ilkesiyle token izinleri, script injection'a karşı güvenli ifade kullanımı, riskli tetikleyiciler, commit SHA ile action sabitleme, secret yönetimi ve uzun ömürlü bulut anahtarları yerine OpenID Connect (OIDC).

1. permissions ile en az yetki

GitHub'ın önerisi, GITHUB_TOKEN için varsayılan iznin depo içeriğine yalnızca okuma olarak ayarlanması, gerekiyorsa iznin tek tek job'larda artırılmasıdır. İzinler iş akışı dosyasındaki permissions anahtarıyla belirlenir. Önemli bir ayrıntı: herhangi bir izin açıkça yazıldığında, yazılmayan tüm izinler none olur.

CODE TERMINAL
name: build
on: [push, pull_request]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - run: make test


Pull request'e yorum yazması gereken bir job için izin yalnızca o job'da genişletilir:

CODE TERMINAL
jobs:
  yorum:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - run: echo "yalnızca bu job PR'a yazabilir"


Kısayollar da mevcuttur: permissions: read-all tüm izinleri okuma, permissions: {} ise tüm izinleri kapatır. write-all kullanımından kaçının.

2. Script injection: güvenilmeyen girdiyi ortam değişkenine alın

Pull request başlığı, dal adı veya issue gövdesi gibi değerler saldırganın kontrolündedir. Bu değerler run adımına doğrudan ifade olarak gömülürse kabuk komutuna dönüşebilir. Örneğin başlığına "; curl ... ; echo " biçiminde komut yerleştirilmiş bir PR aşağıdaki adımda komut çalıştırır:

CODE TERMINAL
# GÜVENSİZ
- run: echo "PR başlığı: ${{ github.event.pull_request.title }}"


GitHub'ın önerdiği çözüm, ifadenin değerini önce bir ara ortam değişkenine atamak ve kabukta bu değişkeni tırnak içinde kullanmaktır:

CODE TERMINAL
# GÜVENLİ
- env:
    PR_BASLIK: ${{ github.event.pull_request.title }}
  run: echo "PR başlığı: $PR_BASLIK"


Böylece değer kabuk tarafından komut olarak değil, veri olarak işlenir.

3. pull_request_target ve workflow_run tetikleyicileri

GitHub dokümantasyonu, pull_request_target ve workflow_run tetikleyicilerinin güvenilmeyen bir pull request'in checkout edilmesiyle birlikte kullanıldığında depoyu tehlikeye attığını açıkça belirtir ve gerekmedikçe pull_request_target kullanılmamasını önerir. Bu tetikleyici hedef deponun bağlamında, yani secret'lara ve yazma yetkili token'a erişimle çalışır. Fork'tan gelen kod bu bağlamda checkout edilip çalıştırılırsa saldırgan secret'ları sızdırabilir.

Güvenli kullanım, pull_request_target altında PR kodunu hiç çalıştırmamak ve yalnızca etiketleme gibi meta veri işlemleri yapmaktır:

CODE TERMINAL
on:
  pull_request_target:
    types: [opened]

permissions:
  pull-requests: write

jobs:
  etiket:
    runs-on: ubuntu-latest
    steps:
      # PR kodu checkout EDİLMİYOR
      - env:
          PR_NO: ${{ github.event.pull_request.number }}
        run: echo "PR #$PR_NO etiketleniyor"


Test ve derleme gibi PR kodunu çalıştıran işler için secret erişimi olmayan pull_request tetikleyicisini kullanın.

4. Action'ları tam commit SHA ile sabitleyin

uses: kisi/action@v1 gibi bir etiket değiştirilebilir; etiketin sahibi veya hesabı ele geçiren biri onu kötü amaçlı bir commit'e taşıyabilir. GitHub'a göre bir action'ı tam uzunluktaki commit SHA ile sabitlemek, onu değişmez bir sürüm olarak kullanmanın şu an tek yoludur. Etiket kullanılacaksa action yazarına güvenilmeli; GitHub Marketplace'teki "Verified creator" rozeti bu konuda yararlı bir işarettir.

CODE TERMINAL
steps:
  # Değişebilir referans
  - uses: actions/checkout@v4
  # Değişmez referans (sürüm yorum satırında)
  - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2


GitHub, depo ve organizasyon düzeyinde action'ların tam SHA ile sabitlenmesini zorunlu kılan politikalar da sunar. Sabitleme güncellemeyi engellemez: Dependabot, iş akışlarında kullanılan action ve yeniden kullanılabilir workflow referanslarını güncel tutabilir.

CODE TERMINAL
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"


5. Secret yönetimi


  • Hassas veriler iş akışı dosyalarında asla düz metin olarak tutulmamalıdır.
  • GitHub secret'ı olmayan ancak hassas olan değerler ::add-mask::DEĞER komutuyla günlüklerde maskelenmelidir.
  • Bir secret'ı JSON, XML veya YAML bloğu içinde saklamayın; GitHub'a göre bu, değerin günlüklerde doğru biçimde gizlenme olasılığını ciddi ölçüde düşürür.
  • Açığa çıktığından şüphelenilen secret'ları hemen iptal edip yenileyin.



CODE TERMINAL
- name: Geçici token'ı maskele
  run: |
    TOKEN=$(./token-uret.sh)
    echo "::add-mask::$TOKEN"


6. OIDC ile secret'sız bulut erişimi

En etkili sertleştirme adımı, AWS erişim anahtarı gibi uzun ömürlü bulut kimlik bilgilerini GitHub secret'ı olarak saklamayı bırakmaktır. OIDC ile her job çalıştığında GitHub bir JWT üretir; bulut sağlayıcı bu token'ı doğrular ve yalnızca o job için geçerli, kendiliğinden süresi dolan kısa ömürlü bir erişim token'ı verir. GitHub bu yaklaşımın avantajlarını üç maddede özetler: bulut secret'larını çoğaltmaya gerek kalmaz, erişim bulut sağlayıcının kendi kimlik doğrulama ve yetkilendirme araçlarıyla ayrıntılı biçimde yönetilir ve kimlik bilgileri otomatik olarak döner.

OIDC token'ı talep edebilmek için job'a id-token: write izni verilmelidir; bu izin yalnızca write veya none değerini alır.

CODE TERMINAL
name: deploy
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: prod
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/gh-deploy
          aws-region: eu-central-1
      - run: aws s3 sync ./dist s3://ornek-bucket


Bulut tarafındaki güven koşulu en kritik noktadır. Token'daki sub alanı iş akışının kimliğini taşır; örneğin repo:octo-org/octo-repo:environment:prod değeri yalnızca belirtilen deponun prod ortamından gelen job'ları tanımlar. Token'ın vericisi (iss) https://token.actions.githubusercontent.com adresidir. Güven politikasında sub koşulunu joker karakterlerle genişletmek, başka depoların veya dalların sizin rolünüzü üstlenmesine yol açabilir; koşulu mümkün olduğunca dar yazın.

7. Runner güvenliği ve sürekli denetim

GitHub'a göre self-hosted runner'lar herkese açık depolarda neredeyse hiçbir zaman kullanılmamalıdır; çünkü herkes depoya pull request açabilir ve runner ortamını ele geçirebilir. Özel depolarda da self-hosted runner'ları geçici (her job sonrası yok edilen) ve ağ olarak izole çalıştırmak riski azaltır.

İş akışlarınızı düzenli olarak denetlemek için OpenSSF Scorecards action'ı kullanılabilir. Scorecards, script injection, token izinleri ve sabitlenmiş action'lar dahil birçok kontrol çalıştırır.

Özet tablo

RiskVarsayılan/Hatalı KullanımSertleştirilmiş Kullanım
GITHUB_TOKEN izinleriGeniş veya belirtilmemiş izinlerpermissions: contents: read, gerekirse job bazında artırma
Script injection${{ }} ifadesini run içine gömmekDeğeri env ile ara değişkene alıp tırnakla kullanmak
PR tetikleyicisipull_request_target + PR kodunu checkoutPR kodu için pull_request; pull_request_target yalnızca meta veri
Action referansı@v4 gibi değişebilir etiketTam commit SHA + Dependabot
Secret'larJSON/YAML blob, maskelenmemiş değerTekil secret, ::add-mask::, hızlı rotasyon
Bulut erişimiUzun ömürlü erişim anahtarıOIDC, id-token: write, dar sub koşulu
RunnerAçık depoda self-hostedAçık depoda GitHub-hosted runner


Sık yapılan hatalar


  • permissions anahtarında tek bir izin yazıp diğerlerinin varsayılan kaldığını sanmak: yazılmayan izinler none olur, bu da beklenmeyen hatalara yol açabilir.
  • id-token: write iznini tüm iş akışına verip dağıtım yapmayan job'ların da OIDC token'ı alabilmesine izin vermek; izni yalnızca gereken job'a verin.
  • SHA ile sabitlenen action'ların sürüm yorumunu güncellememek; hangi sürümün kullanıldığı izlenemez hale gelir.
  • Bulut güven politikasında sub koşulunu repo:org/* gibi geniş yazmak.
  • Kullanıcı girdisini yalnızca run adımlarında değil, betiklere argüman olarak iletirken de doğrudan ifadeyle kullanmak.



Sonuç

GitHub Actions güvenliği tek bir ayardan değil, katmanlı birkaç alışkanlıktan oluşur: token'ı en az yetkiyle çalıştırmak, güvenilmeyen girdiyi veri olarak işlemek, riskli tetikleyicilerden kaçınmak, action'ları değişmez referanslarla sabitlemek ve bulut erişiminde OIDC'ye geçmek. Sitemizdeki Sigstore/Cosign ile keyless imzalama rehberiyle birlikte uygulandığında, derleme hattınızdan yayın aşamasına kadar izlenebilir ve daha dayanıklı bir tedarik zinciri kurabilirsiniz.

Kaynaklar: GitHub Docs — Security hardening for GitHub Actions, GitHub Docs — OpenID Connect, GitHub Docs — Workflow syntax (permissions), actions/checkout v4.2.2
TR Siber Ekibi Yazar · TRSiber
← Ana sayfaya dön