
Tue Sep 15 2026

Selam! 👋 Hazırsan bu konuyu beraber açalım.
Pull Request (PR) açtıktan sonra gelen code review süreci genellikle "kod çalışıyor mu?", "style uygun mu?", "mantık doğru mu?" sorularıyla geçer. Ama güvenlik? Genelde son kalan — hatta bazen de unutulan — bir detay olur.
Oysa PR aşamasında güvenlik açıklarını yakalamak, ekip ve ürün için oyun değiştiren bir fark yaratır:
Kısaca: PR güvenlik kapısı değil, güvenlik filtresi olmalı. 🎯
Hadi şimdi bunu nasıl pratik bir altyapıya dönüştürebileceğimize bakalım — araçlar, akışlar ve alışkanlıklar üzerinden. 🚀
Hazırsan başlayalım 🎯. Manuel code review, kağıt üzerinde harika kulağa gelse de gerçek hayatta sık sık şu sorunları yaratıyor:
Senaryo: Akşam 18:00, الإنتاجa çıkacak bir feature branch’i merge etmek için onay bekliyorsun. Reviewer “LGTM” der, sen de merge edersin. Ertesi sabah loglarda
AWS_SECRET_ACCESS_KEY=AKIA...görünüyor. Hard‑coded secret, production’a sızmış 😱.
Bu durum, manuel gözden geçirme sadece “kod çalışıyor mu?” diye baktığı için gerçekleşti.
.env veya config dosyasında düz metin olarak kalıyor.package.json/requirements.txt’ta bilinen CVE’li bir sürüm duruyor.# Örnek: Yanlışlıkla commitlenmiş secret
AWS_ACCESS_KEY_ID=AKIAEXAMPLE
AWS_SECRET_ACCESS_KEY=supersecretkey123
Bu sayede ne oluyor?
Secret’lar versiyon kontrolüne girer, CI/CD loglarında görünür ve kötü niyetli birisi eline geçirirse tüm bulut hesabınız ele geçirilebilir ⛔.
Özet: Manuel review güvenlik kata eklemez, sadece fonksiyonel hataları yakalar. Otomatik statik analiz, secret scanning ve bağımlılık denetimi zorunlu birer kapı olmalı 🛠.
SAST (Static Application Security Testing), kaynak kodunu derlemeden tarayarak güvenlik açıklarını erken yakalayan bir yöntemdir 🎯.
Kodunuzu bir derleyiciye vermeden önce, söz dizimi ağacı (AST) üzerinden kuralları kontrol eder — yani program çalışmadan önce analiz yapar.
Kısaca: Kodunuzu “statik” olarak okur, veri akışını ve kontrol akışını analiz ederek riskli desenleri işaretler.
lint/test adımlarıyla aynı stage’de çalışır, build fail olur eğer kritik bulgu varsa.Özetle: SAST, kodunuzu derlemeden önce güvenlik gözlüğüyle inceler, CI/CD’nin kalp atışında yer alarak güvenli yazılım teslimatını otomatikleştirir ✅.
Hazırsan GitHub Actions üzerinden CodeQL ile SAST eklemeyi adım adım geçelim. Ben yaptım, sen de aynı mantıkla kendi projene uyarlayabilirsin 🚀
contents: read, security-events: write (pull_request tetikleyicisi için yeterli).CODEQL_TOKEN gibi bir secret ekle.CODEQL_TOKEN (veya GITHUB_TOKEN zaten varsayılan gelir).actions/cache@v4 ile key: codeql-${{ runner.os }}-${{ hashFiles('**/codeql-config.yml') }} şeklinde bir key kullan.codeql-${{ runner.os }}- ekle..github/workflows/codeql-analysis.yml adıyla şu içeriği ekle:
name: "CodeQL Analysis"
on:
pull_request:
branches: [ main, develop ]
schedule:
- cron: '0 3 * * 1' # haftalık tam tarama (opsiyonel)
permissions:
contents: read
security-events: write
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
language: [ javascript, typescript, python ] # projenizdeki diller
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml # opsiyonel custom config
# cache için:
# cache: true
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
upload: true
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ runner.temp }}/codeql-results.sarif
category: "/language:${{ matrix.language }}"
Ne oluyor burada?
on.pull_request → her PR açıldığında/ güncellendiğinde tetiklenir.matrix.language → birden fazla dil için paralel job çalıştırır.permissions → sonuçları Security sekmesine yazabilmek için security-events: write zorunlu.cache: true (init adımında) → veritabanı indirilmişse tekrar indirmez, pipeline süresini kısaltır.upload-sarif → analiz sonuçlarını GitHub Code Scanning’e gönderir, PR üzerinde Security → Code scanning alerts olarak görünür..github/codeql/codeql-config.yml içine kendi query paketlerini ekle.github/codeql-action/analyze step’ine severity-threshold: high ekleyerek sadece kritik bulguları fail edebilirsin.paths-ignore / paths ile sadece değişen servisleri tara.Özet:
1️⃣ Repo izinlerini aç → 2️⃣ Secret/token ayarla → 3️⃣ Cache ekle → 4️⃣ Yukarıdaki YAML’ı commit et → 5️⃣ PR ile test et. Artık her PR’de kod güvenliği otomatik kontrol ediliyor 🎉
Hazırsan PR açıldığında güvenlik taramasının nasıl devreye girdiğini, sonuçların nereye düştüğünü ve ekip arkadaşların nasıl anında haberdar olduğunu adım adım inceleyelim 🚀
PR açıldığı anda (veya synchronize / reopened olayında) CI/CD pipeline’ımız SAST job’ını tetikler. Job şunları yapar:
exit code != 0) PR merge engellenir (branch protection rules sayesinde)🎯 Kısaca: Kod pushlanır → SAST çalışır → Bulgular PR üzerine "yapışır" → Geliştirici PR’i açar açmaz güvendiği satırda uyarıyı görür.
| Kavram | Ne işe yarar? | Nerede görünür? |
|---|---|---|
| Check Run | Tek bir tarama sonucunun özeti (durum, başlık, özet markdown) | PR → Checks sekmesi |
| Annotation | Belirli bir dosya+satır aralığına bağlanmış hata/uyarı/not | PR → Files changed → satırın yanında 💬 ikonu |
Örnek akış:
in_progress check run oluşturulurcompleted + conclusion: failure (veya success)annotations dizisinde birer nesne gönderilirAşağıda CodeQL / Semgrep çıktısından kesit bir SARIF parçası var. Bu dosya github/codeql-action/upload-sarif action’ına verildiğinde GitHub otomatik annotation’lara dönüştürür 🛠
{
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Semgrep",
"version": "1.45.0",
"informationUri": "https://semgrep.dev"
}
},
"results": [
{
"ruleId": "javascript.lang.security.audit.xss.react-dangerously-set-inner-html",
"level": "error",
"message": {
"text": "dangerouslySetInnerHTML XSS riski — kullanıcı girdisi doğrudan render ediliyor"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "src/components/CommentBox.jsx"
},
"region": {
"startLine": 23,
"startColumn": 12,
"endLine": 23,
"endColumn": 45
}
}
}
],
"properties": {
"severity": "HIGH",
"tags": ["security", "xss", "react"]
}
}
]
}
]
}
Ne oluyor burada?
ruleId → Hangi kural ihlal edildi (Semgrep rule registry’den)level: "error" → GitHub bunu kırmızı annotation (❌) olarak render ederlocations[].physicalLocation → Tam dosya yolu + satır/kolon aralığıproperties.severity → Ekip filtrelemesi için (CI’da fail-on: high gibi)Bu JSON results.sarif olarak kaydedip upload edersen, PR → Files changed → CommentBox.jsx satır 23’ün yanında kırmızı uyarı simgesi belirir. Üzerine gelince yukarıdaki mesaj tooltip olarak çıkar ✨
curl örneğiBazen özel bir linter / script çıktısını manuel Checks API’ye göndermek isteyebiliriz. İşte minimal bir bash snippet’i 👇
#!/usr/bin/env bash
# PR #42 için check run oluşturup annotation ekleme örneği
# GITHUB_TOKEN: repo scope'lu classic token veya GITHUB_ACTIONS runtime token
PR_NUMBER=42
REPO="my-org/my-repo"
HEAD_SHA=$(gh pr view "$PR_NUMBER" --json headRefOid -q .headRefOid)
# 1️⃣ Check run oluştur (in_progress)
CHECK_RUN_ID=$(curl -s -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$REPO/check-runs" \
-d "{
\"name\": \"Custom SAST Scan\",
\"head_sha\": \"$HEAD_SHA\",
\"status\": \"in_progress\",
\"started_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
}" | jq -r .id)
echo "Check run oluşturuldu: $CHECK_RUN_ID"
# 2️⃣ Tarama bitti → annotation'larla completed yap
curl -s -X PATCH \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$REPO/check-runs/$CHECK_RUN_ID" \
-d "{
\"status\": \"completed\",
\"conclusion\": \"failure\",
\"completed_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"output\": {
\"title\": \"Özel SAST Sonuçları\",
\"summary\": \"3 HIGH, 5 MEDIUM bulgu bulundu.\",
\"annotations\": [
{
\"path\": \"src/utils/auth.js\",
\"start_line\": 14,
\"end_line\": 14,
\"annotation_level\": \"failure\",
\"message\": \"Hardcoded JWT secret — env variable kullanılmalı\",
\"title\": \"Secret Leakage\"
},
{
\"path\": \"src/api/users.js\",
\"start_line\": 87,
\"end_line\": 90,
\"annotation_level\": \"warning\",
\"message\": \"SQL injection riski — parameterized query kullanın\",
\"title\": \"SQLi Potential\"
}
]
}
}"
Nasıl çalışıyor?
gh pr view ile PR’in head_sha’sını alıyoruz (check run’lar commit’e bağlanır, PR numarasına değil)POST → in_progress check run yaratır, ID’sini jq ile çekerizPATCH → completed + conclusion: failure + annotations[] gönderirizpath, start_line/end_line, annotation_level (failure/warning/notice), message⚡ İpucu:
annotation_level: "failure"→ PR merge’i bloklar (branch protection “Require status checks to pass” açıksa).warningsadece sarı uyarı gösterir, bloklamaz.
| Kanal | Ne zaman tetiklenir? | Nasıl ayarlarız? |
|---|---|---|
| PR Checks sekmesi | Her push / rebase | Otomatik — herhangi bir ayar gerekmez |
| Satır içi annotation | Check run completed oldunda |
SARIF upload ya da Checks API annotation |
| GitHub Actions e-posta / Slack | Job failure oldunda |
Repo → Settings → Notifications / Slack app |
| CODEOWNERS + required reviewers | PR açıldığında | .github/CODEOWNERS dosyası + branch protection |
| Dependabot / CodeQL alert | Yeni CVE / pattern eşleştiğinde | Security tab → Alert settings |
Pratik taktik: Repo ayarlarından "Require status checks to pass before merging" aç, ardından SAST job’ının adını (örn. SAST / Semgrep) required checks listesine ekle. Artık yeşil ✅ gelmeden merge butonu pasif kalır ⛔
✅ PR açılır açılmaz SAST koşar
✅ Sonuçlar Checks sekmesinde özet, Files changed’de satır bazında annotation olarak görünür
✅ failure conclusion → PR bloklanır (branch protection sayesinde)
✅ Geliştirici PR’i inceleyip satır üstüne tıklayarak hatayı, öneri fix’i ve kural referansını görür
✅ Ekip Slack/e-posta bildirimiyle anında haberdar olur
Hadi şimdi bu akışı kendi repo’unda deneyelim — bir sonraki bölümde CI/CD pipeline’ına SAST job’ını nasıl ekleyeceğimizi (GitHub Actions / GitLab CI / Azure Pipelines örnekleriyle) göreceğiz 🛠
Hazırsan başlayalım ve kontrol listesini birlikte hazırlayalım. Her noktanın neden önemli olduğunu kısa bir şekilde not ettik; böylece ekipte herkes kontrol listesini anlayabilir ve kullanabilir.
| Kontrol | Açıklama | Durum |
|---|---|---|
| SAST geçti mi? 🎯 | Güvenlik açıklarını erken tespit eder, kod kalitesini artırır ve daha sonra ortaya çıkabilecek kritik riskleri önler. | |
| Dependency scan geçti mi? 🔁 | Bağımlılıklardaki zayıf noktaları tespit eder, projeyi bilinen güvenlik açıklarına karşı korur. | |
| Secret scan geçti mi? ⚡ | Yanlışlıkla commit edilen API anahtarlarını, tokenları, kredi kartı bilgilerini tespit eder; veri sızıntılarını önler. | |
| Kod standartları (lint) sağlandı mı? 🛠 | Kodun tutarlı ve okunabilir olmasını sağlar, takımın hızla uyum sağlamasını ve refactoring süreçlerini kolaylaştırır. | |
| Test coverage %80+ mi? ✅ | Testlerin uygulamanın büyük bir bölümünü kapsadığını doğrular, regresyon riskini azaltır ve istatistiksel güvenilirlik sağlar. |
Şimdi bu tabloyu PR durum sayfasına ekleyebilir, her maddeyi işaretleyebilir ve onaydan önce ilgili testlerimizi çalıştırabiliriz. Bu sayede ne oluyor? 🎉 Hep birlikte daha güvenilir ve bakımı kolay bir kod tabanına sahip oluyoruz.
Hazırsan bu başlık altına, benim de başımı ağrıtan ve muhtemelen senin de karşına çıkacak "klasik" CodeQL sorunlarını ve nasıl aştığımızı yazayım 🎯
Ne oluyor burada? Kodun güvenli olduğunu biliyorsun ama CodeQL "SQL Injection riski var!" diye bağırıyor. Örn: test dosyalarında hardcoded şifreler, mock data hazırlayan yardımcı fonksiyonlar, veya framework'ün kendi escape mekanizmasını kullandığın yerler.
Benim yaşadığım bir anekdot: 📖
Bir projede, test klasörü altındaki UserRepositoryTest.java dosyası her tarama da "Hardcoded Credentials" uyarısı veriyordu. Sebep? Test verisi olarak password = "test123" yazmıştım. CodeQL bunu gerçek bir secret sanıyordu. İki hafta boyunca her PR'da bu uyarıyı gördüm, "daha sonra hallederim" diyip geçtim. Sonunda bir sabah kahvem soğuken codeql-config.yml ile exclude ettim — hayat kurtarıcı oldu ✅
Çözüm yolları:
error/critical seviyelerini fail etRepo büyüdükçe (özellikle monorepo'larda) tarama 30-40 dakika buluyor. CI/CD pipeline'ın en yavaş adımı CodeQL oluyor.
Pratik ipuçları:
actions/cache ile CodeQL veritabanını önbelleklepaths-ignore / paths ile unrelated dosya değişikliklerinde tarama tetiklemeKüçük bir hatırlatma: Incremental scan CodeQL CLI 2.12+ ve GitHub Actions codeql-action v2+ ile geldi. Eski action sürümündeysen upgrade et 🛠
Monorepo yaşıyorsan frontend/, backend/, shared/ ayrı klasörlerde duruyor. Her PR'da hepsini taramak saçma.
Yaklaşımım:
codeql-config.yml ile include path belirle → Sadece ilgili modülü tarapaths filter ile GitHub Actions workflow tetikleyicisini daraltlanguages matrix'ini dynamic yap → Değişen dosyanın diline göre tarama çalıştırİki ana silahın var:
| Yöntem | Ne Zaman Kullanılır? | Nasıl? |
|---|---|---|
| Allowlist (suppression) | Belirli bir kural, belirli dosyalarda yanlış alarm veriyorsa | .github/codeql/codeql-allowlist.yml veya codeql-config.yml içinde query-filters ile |
| Severity threshold | Sadece kritik bulgular pipeline'ı kırsın, warning/info görüntülensin | github/codeql-action/analyze step'inde fail-on-severity: error |
Benim tercihim: Önce severity threshold ile pipeline'ı yumuşak tut, sonra tek tek false positive'ları allowlist'e ekle. Böylece güvenlik ekibi "neden bu kapalı?" diye sormaz 😄
codeql-config.yml ile Include/ExcludeŞimdi hadi gerçek bir konfigurasyon yazalım. Bu dosyayı repo köküne .github/codeql/codeql-config.yml olarak koyarsın:
name: "CodeQL Config for Monorepo"
disable-default-queries: false
# Sadece backend ve shared kütüphaneleri tara, frontend'i atla
paths:
- backend/
- shared/
paths-ignore:
- backend/tests/
- shared/tests/
- frontend/
- "**/*Test*.java"
- "**/*Mock*.java"
# Dil bazlı query pack'leri (isteğe bağlı)
queries:
- uses: security-and-quality
language: java
- uses: security-extended
language: javascript
# Bilinen false positive'ları bastır
query-filters:
- exclude:
id: "java/hardcoded-credentials"
reason: "Test verileri mock data içeriyor, gerçek secret yok"
path: "backend/tests/**"
- exclude:
id: "js/xss"
reason: "React'in kendi escape mekanizması kullanılıyor"
path: "shared/ui-components/**"
# Sadece error ve critical severity pipeline'ı fail etsin
# (Bu ayar workflow dosyasında da yapılabilir, burada dokümantasyon için)
# fail-on-severity: error
Bu sayede ne oluyor?
frontend/ tamamen ignore edilir → tarama süresi %40-50 düşer 🚀dangerouslySetInnerHTML kullanmıyorsun, React escape ediyor)error/critical bulgularda kırılırcodeql-config.yml dosyasını workflow'da şöyle çağırırsın:
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
config-file: .github/codeql/codeql-config.yml
languages: java, javascript
Önemli: config-file parametresi init step'inde verilmeli, analyze step'inde değil. Yanlış yerde verirsen config işlemez ❗️
| Sorun | Çözüm |
|---|---|
| False positive gürültüsü | Exclude path + allowlist + severity threshold |
| Tarama çok uzunca sürüyor | Incremental scan + cache + matrix + paths-ignore |
| Monorepo'da her PR'da her şey taranıyor | Include path + paths filter + dynamic language matrix |
| Pipeline her uyarıda kırılıyor | fail-on-severity: error ile sadece kritikler fail etsin |
Bu yöntemlerle benim pipeline'ım 35 dakikadan 8-10 dakikaya düştü ve gürültü neredeyse sıfırlandı. Sen de denerken takıldığın yer olursa, yorumlarda buluşuruz 🤝
Hazırsanız özetleyelim: SAST + CI/CD + PR otomasyonu = sürekli güvenlik 🚀
Ekip alışkanlıklarını değiştirin
Metrikleri takip edin 📊
Sürekli iyileştirme döngüsü 🔁
Bugün başlayın ✅
Güvenlik bir “ekstra” değil, günlük rutininizin bir parçası olmalı. Küçük adımlarla başlayın, ölçün, iyileştirin — güvenlik kültürü kendiliğinden büyür. 🌱
Hazırsan bu aşamada ekstra değer katan araçları ve faydalı linkleri hızlıca göz atalım 🚀
Son söz: Güvenlik otomasyonu bir marathon, sprint değil — küçük adımlarla başla, ölç ve iyileştir. Bol güvenli kodlar! 🎉
Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.
All rights reserved