Mert Özen Her Satırda Daha İleri
Güvenlik DevSecOps — #02

Secret Scanning: Pre-commit, Push Protection ve Sızan Secret'a İlk 30 Dakika

Mert Özen 14 Kas 2026 13 dk 4 görüntülenme
Secret Scanning: Pre-commit, Push Protection ve Sızan Secret'a İlk 30 Dakika

Secret scanning tek bir araç değil, katmanlardır: geliştirici makinesinde pre-commit, pipeline'da PR commit aralığı, GitHub'da push protection ve tüm geçmişin periyodik taranması; sızıntı olursa sıra her zaman önce rotate, sonra etki analizi, en son geçmişi temizlemektir. gitleaks 8.30'un varsayılan kurallarının dev önekli iki secret'ı neden kaçırdığını, Bookstore'a özel kuralları, base64 tespitini ve git-filter-repo ile temizliği gerçek çıktılarla gösteriyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 10 ile derlenip denenmiştir; taramalar gitleaks 8.30.1, Git 2.54 ve git-filter-repo 2.47 ile çalıştırılmıştır.

Önce Tahmin Et

Bookstore'da bir geliştirici yerel veritabanı container'ına (SQL Server) bağlanamıyor, acelesi var. appsettings.Development.json'a parolalı connection string'i ve Inventory servisini çağırmak için kullandığı API key'ini yazıyor, git commit --no-verify ile hook'u atlayıp push ediyor. On dakika sonra fark ediyor, dosyadan parolayı silip yeni bir commit atıyor. Soru: secret artık güvende mi? Ve ekipte gitleaks kurulu olduğu hâlde, varsayılan ayarlarla bu iki değerden kaçını yakalardı?

İlk sorunun cevabı hayır: silinen satır Git geçmişinde, push edildiği remote'ta, varsa fork'larda ve o arada clone alan herkeste duruyor. İkinci sorunun cevabı ise birazdan göreceğimiz gibi sıfır. Bu yazıda secret scanning'i katman katman kuruyoruz: geliştirici makinesinde pre-commit, pipeline'da commit aralığı taraması, GitHub'da push protection ve tüm geçmişin taranması. Sonra en önemli kısma geçiyoruz: bir secret sızdığında ilk 30 dakikada ne yapılır, hangi sırayla.

Katmanlar: Neden Tek Tarayıcı Yetmez

Secret scanning'in her katmanı bir öncekinin kaçırdığını yakalamak için var. Pre-commit hook en ucuz katman: secret daha commit'e girmeden durur, düzeltme maliyeti sıfıra yakın. Ama hook geliştiricinin makinesinde çalışır, kurulmamış olabilir ve Git ve GitHub serisinin hook'ları ve commit imzalamayı anlattığı yazıda gördüğümüz gibi --no-verify ile atlanabilir. Sunucu tarafındaki push protection secret'ı remote'a ulaşmadan durdurur ve geliştiricinin makinesindeki kuruluma bağlı değildir. Pipeline'daki tarama, PR'ın commit'lerini kendi kurallarımızla kontrol eder ve hook'un atlandığını yakalar. Tüm geçmişin periyodik taranması ise yeni eklenen bir kuralın eski commit'lerdeki karşılığını bulur.

Bu katmanların hiçbiri sızan secret'ı geri almaz. Hepsinin amacı sızıntı penceresini küçültmek. Pencere kapandıktan sonra yapılacak tek doğru iş secret'ı geçersiz kılmaktır; buna yazının sonunda geleceğiz.

Geliştirici Makinesinde: gitleaks ve pre-commit

Açık kaynak secret tarayıcılar arasında gitleaks yaygın ve Windows, Linux ve macOS için tek binary olarak geliyor. Önce senaryodaki dosyayı stage'leyip varsayılan kurallarla tarayalım. Değerler bu yazı için üretilmiş sahte değerler; connection string'deki parolaya ekipteki gelenek gereği dev-only- öneki konmuş, API key ise API Güvenliği serisinde machine-to-machine authentication için tasarladığımız bks_dev_<keyId>_<secret> formatında:

{
  "ConnectionStrings": {
    "Bookstore": "Server=localhost,1433;Database=Bookstore;User Id=bookstore_app;Password=dev-only-Xq7v9Lm2Rt8Kp4Wz;TrustServerCertificate=True"
  },
  "InventoryApi": {
    "BaseUrl": "http://localhost:5080",
    "ApiKey": "bks_dev_sjbcsri7b7q6_1Z62kfo84dCncxHxvfJE_6UGUlThcVCg0-0dDgtxuRc"
  }
}
git add src/Bookstore.Api/appsettings.Development.json
gitleaks git --pre-commit --staged --redact --no-banner .
12:26AM INF 0 commits scanned.
12:26AM INF scanned ~324 bytes (324 bytes) in 72.9ms
12:26AM INF no leaks found

İkisi de kaçtı. Sebebi gitleaks'in generic-api-key kuralının nasıl çalıştığında. Bu kural belirli bir sağlayıcının formatını aramaz; password, key, token gibi bir anahtar kelimeden sonra gelen ve entropy'si 3.5'in üzerinde olan değeri yakalar. False positive'i azaltmak için de 8.30.1'in varsayılan configuration'ında bin dört yüzü aşkın stopword'ü var: değerin içinde bunlardan biri geçerse bulgu atılır. Parolamızdaki dev- ve only, key'imizdeki dev_ bu listede. Yani "açıkça sahte görünsün" diye koyduğumuz önek, tarayıcının gözünde de "sahte" anlamına geliyor ve gerçek bir development secret'ı görünmez kılıyor.

Buradan çıkan sonuç: generic kurallar yalnızca bir yedek önlemdir; kendi secret formatların için kendi kurallarını yazarsın. Bookstore'un API key'i tanınabilir bir önekle tasarlandığı için kural yazmak kolay. Connection string için de SQL Server'a özgü bir kural ekliyoruz. Kurallar repo kökündeki .gitleaks.toml'da duruyor ve varsayılan kuralları genişletiyor:

title = "Bookstore gitleaks config"

[extend]
useDefault = true

[[rules]]
id = "bookstore-api-key"
description = "Bookstore partner API key"
regex = '''\bbks_[a-z]+_[a-z2-7]{12}_[A-Za-z0-9_-]{43}\b'''
keywords = ["bks_"]

[[rules]]
id = "sqlserver-connection-password"
description = "Password inside a SQL Server connection string"
regex = '''(?i)(?:server|data source)=[^"';]+;[^"']*?(?:password|pwd)\s*=\s*([^;"'\s]{8,})'''
secretGroup = 1
keywords = ["password", "pwd"]

[[allowlists]]
description = "Fake keys in test fixtures"
targetRules = ["bookstore-api-key", "generic-api-key"]
paths = ['''^tests/.*/Fixtures/.*\.json$''']
regexes = ['''^bks_test_''']
condition = "AND"

keywords alanı performans için: gitleaks regex'i yalnız bu kelimeleri içeren satırlarda çalıştırır. secretGroup = 1, connection string'in tamamı yerine yalnız parolanın secret sayılmasını sağlar; redaksiyon ve fingerprint bu değer üzerinden yapılır. Aynı tarama artık iki bulgu veriyor:

Finding:     "ApiKey": "REDACTED
Secret:      REDACTED
RuleID:      bookstore-api-key
Entropy:     5.128524
File:        src/Bookstore.Api/appsettings.Development.json
Line:        7
Fingerprint: src/Bookstore.Api/appsettings.Development.json:bookstore-api-key:7

Finding:     "Bookstore": "Server=localhost,1433;Database=Bookstore;User Id=bookstore_app;Password=REDACTED;TrustServerCertific...
Secret:      REDACTED
RuleID:      sqlserver-connection-password
Entropy:     4.483856
File:        src/Bookstore.Api/appsettings.Development.json
Line:        3
Fingerprint: src/Bookstore.Api/appsettings.Development.json:sqlserver-connection-password:3

12:26AM WRN leaks found: 2

Şimdi bunu commit'ten önce otomatik çalıştıralım. Hook mekaniğini Git ve GitHub serisinde anlattık; Git 2.54'ün config tabanlı hook'larıyla tek bir komut yeterli. --verbose önemli, yoksa geliştirici yalnızca "leaks found: 2" görür ve neyin nerede olduğunu bilmez:

git config set hook.gitleaks.command "gitleaks git --pre-commit --staged --redact --verbose --no-banner"
git config set --append hook.gitleaks.event pre-commit
git hook list pre-commit
git commit -m "Add development settings"
echo $?
gitleaks
Finding:     "ApiKey": "REDACTED
Secret:      REDACTED
RuleID:      bookstore-api-key
...
12:26AM WRN leaks found: 2
1

Commit oluşmadı. Ekip genelinde aynı hook'u dağıtmak istiyorsan pre-commit framework'ünün .pre-commit-config.yaml dosyası da iş görür; gitleaks repo'su gitleaks, gitleaks-docker ve sistemdeki binary'yi kullanan gitleaks-system adlı üç hook tanımı yayımlıyor. Hangisini seçersen seç, hook bir emniyet kemeridir: atlanabilir, bu yüzden aynı kurallar pipeline'da da çalışmalı.

Düzeltme, secret'ı dosyadan tamamen çıkarmak. Development ortamında connection string'in parolasız kısmı dosyada kalır, parola ve key environment variable'dan (ConnectionStrings__Bookstore, InventoryApi__ApiKey) ya da user-secrets'tan gelir. Bu mekanizmaları OWASP (web uygulama güvenliği üzerine çalışan açık topluluk) serisinin "OWASP A04: Cryptographic Failures (2) — Parola Saklama, Data Protection ve Secret Yönetimi" yazısında kurduk; Key Vault ve Vault gibi secret store'lar ilerideki Configuration ve Secret Management serisinin konusu.

{
  "ConnectionStrings": {
    "Bookstore": "Server=localhost,1433;Database=Bookstore;User Id=bookstore_app;TrustServerCertificate=True"
  },
  "InventoryApi": {
    "BaseUrl": "http://localhost:5080",
    "ApiKey": ""
  }
}

Test Fixture'ları ve Allowlist

Bookstore'un testleri API key doğrulamasını gerçek formatta bir key ile deniyor; bu key tests/Bookstore.Api.Tests/Fixtures/partner-keys.json'da bks_test_ önekiyle duruyor ve hiçbir ortamda geçerli değil. Allowlist'i iki koşulun birlikte sağlanmasına bağladım (condition = "AND"): dosya bir Fixtures klasöründe olacak ve değer bks_test_ ile başlayacak. Böylece birisi fixture'a yanlışlıkla bir bks_dev_ key'i yapıştırırsa yine yakalanıyor; denemede tam olarak bu oldu, oops.json'daki dev key için tarama "leaks found: 1" ile çıktı.

İlk denemede targetRules'a yalnız kendi kuralımı yazmıştım ve commit yine durdu, çünkü fixture'daki "validKey" satırını generic-api-key yakaladı: test_ stopword listesinde değil. Bir değer birden fazla kurala takılabilir; allowlist'i hangi kurallara uyguladığını açıkça yaz. Satır bazında istisna için gitleaks:allow yorumu da var, ama JSON (JavaScript Object Notation) yorum desteklemediği ve satır yorumu code review'da kolayca gözden kaçtığı için path ve regex ile tanımlanmış, review'dan geçen merkezi bir allowlist'i tercih ediyorum.

Base64 ve Diğer Kılıklar

Secret her zaman düz metin durmaz. Kubernetes Secret manifest'lerinde değerler base64'tür ve base64 bir şifreleme değil, bir encoding'dir. gitleaks 8.30.1'de decoding varsayılan olarak açık (--max-decode-depth varsayılanı 5):

apiVersion: v1
kind: Secret
metadata:
  name: inventory-api
type: Opaque
data:
  apiKey: YmtzX2Rldl9zamJjc3JpN2I3cTZfMVo2Mmtmbzg0ZENuY3hIeHZmSkVfNlVHVWxUaGNWQ2cwLTBkRGd0eHVSYw==
RuleID:      kubernetes-secret-yaml
...
RuleID:      generic-api-key
...
Finding:     REDACTED
Secret:      REDACTED
RuleID:      bookstore-api-key
Entropy:     5.128524
Tags:        [decoded:base64 decode-depth:1]
File:        deploy/inventory-secret.yaml
Line:        7

12:27AM WRN leaks found: 3

Üçüncü bulgu önemli: decoded:base64 etiketiyle kendi kuralımız, encode edilmiş değerin içindeki Bookstore key'ini tanıdı. --max-decode-depth 0 ile decoding kapatıldığında bu bulgu kayboluyor ve iki bulgu kalıyor. Kubernetes Secret'ları neden repo'ya düz manifest olarak girmemeli sorusu Kubernetes serisinin "ConfigMap ve Secret ile .NET Configuration: Environment Variable, Volume ve Değişikliklerin Yansıması" yazısının konusu.

appsettings.Development.json ayrıca dikkat ister. Proje template'i onu oluşturur ve commit'lenir; adındaki "Development" kelimesi insanlara zararsız gelir. Ama development veritabanı çoğu zaman production verisinin bir kopyasıdır, development key'i de çoğu zaman staging'e erişir. Kural basit: bu dosyada da secret yok.

Pipeline'da ve Tüm Geçmişte Tarama

Hook atlanabildiği için aynı .gitleaks.toml PR'da da çalışmalı. Pipeline'da gitleaks binary'sini release sayfasından indirip checksum'ını doğruluyoruz; bir tarayıcının kendisinin de supply chain'in parçası olduğunu serinin beşinci yazısında ayrıntılı göreceğiz:

  secrets:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          fetch-depth: 0
      - name: Install gitleaks
        env:
          GITLEAKS_VERSION: 8.30.1
        run: |
          base="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}"
          file="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
          curl -sSfL -o "$file" "$base/$file"
          curl -sSfL -o checksums.txt "$base/gitleaks_${GITLEAKS_VERSION}_checksums.txt"
          grep " $file\$" checksums.txt | sha256sum -c -
          tar -xzf "$file" gitleaks
      # yalnız bu PR'ın commit'leri taranır
      - name: Scan PR commits
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
        run: ./gitleaks git --redact --verbose --no-banner --log-opts="$BASE_SHA..$HEAD_SHA" .

Kurulum adımını yerelde çalıştırdım; dosyanın sonuna bir satır ekleyince sha256sum FAILED yazıp 1 döndü ve adım durdu. Tarama adımını da Linux binary'siyle bir container'da, senaryodaki branch'in commit aralığına karşı koşturdum. Son commit parolayı silmiş olsa da aradaki --no-verify commit'i yakalanıyor:

Finding:     "Bookstore": "Server=localhost,1433;Database=Bookstore;User Id=bookstore_app;Password=REDACTED;TrustServerCertific...
Secret:      REDACTED
RuleID:      sqlserver-connection-password
Entropy:     4.483856
File:        src/Bookstore.Api/appsettings.Development.json
Line:        3
Commit:      f564e296a4103a49a856e3fae5d81dcb1f55fe0b
Author:      Ada
Email:       ada@example.test
Date:        2026-11-13T21:27:19Z
Fingerprint: f564e296a4103a49a856e3fae5d81dcb1f55fe0b:src/Bookstore.Api/appsettings.Development.json:sqlserver-connection-password:3

9:28PM INF 2 commits scanned.
9:28PM INF scanned ~255 bytes (255 bytes) in 174ms
9:28PM WRN leaks found: 1

PR'da yalnız PR'ın commit'lerini taramak, eski geçmişteki bulguların her PR'ı kırmasını engeller; birinci yazıdaki "yeni bulguyu engelle" ilkesinin secret karşılığı bu. Tüm geçmişi tarayan ayrı bir job'ı ise main'de ve haftalık bir schedule ile çalıştır: yeni bir kural eklediğinde eski commit'lerde ne yakaladığını ancak böyle görürsün. Rotate edilmiş, artık geçersiz olan eski bir bulguyu bu taramadan çıkarmak için fingerprint'ini .gitleaksignore'a gerekçesiyle yaz:

# Rotated on 2026-11-14, see SEC-142
f564e296a4103a49a856e3fae5d81dcb1f55fe0b:src/Bookstore.Api/appsettings.Development.json:sqlserver-connection-password:3

Fingerprint commit SHA'sını (yaygın kullanılan hash algoritması ailesi) içerdiği için aynı secret başka bir commit'te yeniden görünürse istisna ona uygulanmaz. --baseline-path ile önceki bir raporu baseline olarak vermek de mümkün; tek tek gerekçe yazılabilen .gitleaksignore review açısından daha okunaklı.

GitHub Secret Scanning ve Push Protection

gitleaks senin kontrolündeki katman; GitHub tarafı ise sunucuda çalışır ve geliştiricinin makinesine bağlı değildir. Bu bölümdeki davranışı GitHub'ın dokümantasyonuna ve changelog'larına dayanarak anlatıyorum, kendi organizasyonumda çalıştırmadım; ürün ve lisans durumu hızlı değiştiği için kendi planını docs.github.com'dan kontrol et.

Secret scanning public repo'larda ücretsiz ve otomatik çalışır; tüm branch'lerin tüm Git geçmişini, ayrıca issue, PR ve discussion metinlerini, wiki'leri tarar. Organizasyonun private ve internal repo'larında ise Nisan 2025'ten beri ayrı satılan GitHub Secret Protection lisansı gerekiyor (Code Security de ayrı bir ürün oldu). Push protection iki türlü: hesap seviyesinde, varsayılan açık olan ve public repo'lara push'u koruyan kullanıcı push protection'ı; ve admin'in açtığı, Secret Protection gerektiren repo push protection'ı. İkincisi command line push'larını, web arayüzündeki commit'leri, dosya yüklemelerini ve REST (Representational State Transfer) API isteklerini kapsar.

Push engellendiğinde, varsayılan ayarda yazma yetkisi olan herkes bir gerekçe seçerek bypass edebilir: "testte kullanılıyor" ve "false positive" seçenekleri kapalı bir alert, "sonra düzelteceğim" açık bir alert oluşturur; her bypass audit log'a düşer ve ilgili kişilere e-posta gider. Delegated bypass açıldığında bypass bir isteğe dönüşür ve yalnız belirlenen kişiler ya da ekipler onaylayabilir; Bookstore için bu, security champion'ın onayı demek. Kendi formatların için custom pattern tanımlayabilir ve push protection'ı pattern bazında açabilirsin: yukarıdaki bks_ regex'i GitHub'a da aynı şekilde girer.

2026'nın önemli değişikliği, 28 Ekim 2026'da public preview olarak duyurulan genişleme: push protection artık yalnız önceden tanımlı formatlara değil, bağlamdan secret olduğu anlaşılan formatsız değerlere de (parola gibi) bakabiliyor. Özellik varsayılan kapalı, Secret Protection ya da Advanced Security gerektiriyor ve AI kredisi tüketiyor. Gördüğümüz dev-only- parolası tam olarak bu tür bir değer; ama önizleme sürecindeki bir özelliğe gate kurmadan önce kendi kuralların yerinde dursun.

Sızıntı Olunca: İlk 30 Dakika

Bir secret sızdığında sıra hayati: önce secret'ı geçersiz kıl, sonra etkisini anla, en son geçmişi temizle. Sırayı ters çevirmek klasik hatadır; geçmişi yeniden yazmakla geçen her dakika, secret'ın hâlâ geçerli olduğu bir dakikadır.

İlk beş dakika: revoke ve rotate. SQL Server login'i için yeni parola üret ve ALTER LOGIN ile değiştir, uygulamaya yeni değeri secret store'dan ver; eski parolayla açık oturumlar varsa kapat. API key için Bookstore'un tasarımı işimizi kolaylaştırıyor: key'in keyId kısmı (örnekte sjbcsri7b7q6) secret olmadığı için loglarda ve ticket'larda güvenle geçer, revoke işlemi de bu id üzerinden yapılır. Rotation'ı bu kadar hızlı yapabilmek için önceden prova etmiş olman gerekir; hiç rotate edilmemiş bir secret'ın nerelerde kullanıldığını kriz anında öğrenmek pahalıdır.

Sonraki yirmi dakika: etki analizi. Sorular net: secret ne zaman push edildi ve ne zaman revoke edildi, repo public mi, fork var mı, o aralıkta secret kullanıldı mı? GitHub tarafında audit log ve secret scanning alert'inin zaman çizelgesi, SQL Server tarafında login audit kayıtları, API tarafında o keyId ile gelen isteklerin log'ları bu sorulara cevap verir. Beklenmeyen bir IP adresi ya da saat görürsen bu artık bir secret temizliği değil, bir incident'tır ve ona göre ilerlersin.

En son: geçmişi temizlemek. GitHub'ın dokümantasyonu bunun çoğu zaman gerekmeyebileceğini açıkça söylüyor; secret geçersizse geçmişteki değer zararsızdır. Yine de temizleyeceksen aracı git-filter-repo; 2.47 sürümü bunun için --sensitive-data-removal seçeneğini getirdi. Rewrite'ı taze bir clone'da yapıyoruz:

git clone https://github.com/bookstore/bookstore.git cleanup
cd cleanup
echo 'dev-only-Xq7v9Lm2Rt8Kp4Wz==>***REMOVED***' > ../replacements.txt
git filter-repo --sensitive-data-removal --replace-text ../replacements.txt
NOTICE: Fetching all refs from origin to make sure we rewrite
        all history that may reference the sensitive data, via
      git fetch -q --prune --update-head-ok --refmap "" origin +refs/*:refs/*
Parsed 4 commitsHEAD is now at 813cc08 Remove password from settings

New history written in 0.10 seconds; now repacking/cleaning...
You rewrote 2 (of 4) commits.

NOTE: First Changed Commit(s) is/are:
  f564e296a4103a49a856e3fae5d81dcb1f55fe0b
...
NEXT STEPS FOR YOUR SENSITIVE DATA REMOVAL:
...
  * Force push the rewritten history to the server:
      git push --force --mirror origin

Denemeyi yerel bir bare repo'yu origin yaparak koşturdum. Rewrite'tan sonra gitleaks'i tekrar çalıştırınca sürpriz bir bulgu çıktı: Password=***REMOVED*** satırı, sekiz karakterden uzun bir parola değeri gördüğü için kendi sqlserver-connection-password kuralımıza takıldı. filter-repo'nun bıraktığı işareti kurala allowlist olarak ekledik:

[[rules.allowlists]]
description = "Marker left by git filter-repo"
regexes = ['''^\*\*\*REMOVED\*\*\*$''']
12:28AM INF 4 commits scanned.
12:28AM INF scanned ~2516 bytes (2.52 KB) in 94.1ms
12:28AM INF no leaks found

Rewrite'ın sınırlarını bil. Değişen commit'ten sonraki bütün SHA'lar değişir, imzalar düşer, eski clone'lardan yapılan bir push secret'ı geri getirebilir. Eski clone'da git cat-file -t f564e29 hâlâ "commit" döndürüyor; temizlenmiş clone'da ise object yok. Fork'lardaki ve PR ref'lerindeki kopyalara sen dokunamazsın: GitHub'ın dokümantasyonuna göre bu durumda force push'tan sonra GitHub Support'a filter-repo'nun verdiği "First Changed Commit(s)" bilgisi ve etkilenen PR sayısıyla başvurursun. İşte bu yüzden rotate etmeden geçmişi temizlemek hiçbir şeyi çözmez.

Üretimde Dikkat

Gerçek bir repo'da tüm geçmişi taramak saniyeler değil dakikalar sürebilir; bunu PR yoluna koyma, schedule'a koy. İlk tam taramada çıkan bulguları bir kerede triage et: her biri için "geçerli mi, rotate edildi mi" sorusunu cevapla, rotate edilenleri gerekçeyle .gitleaksignore'a al, geçerli olanlar için yukarıdaki 30 dakikalık sırayı uygula.

Tarayıcı çıktısının kendisi de hassastır. --redact olmadan gitleaks secret'ı log'a basar ve CI log'u secret'ın yeni bir kopyası olur. Rapor dosyası ürettiğinde de --redact kullan; denemedeki JSON raporunda Secret alanı "REDACTED" olarak yazıldı.

Kuralları ölç. Bir kural haftada birkaç false positive üretiyorsa ekip allowlist'i genişletmeye başlar ve allowlist yavaş yavaş kuralın yerini alır. .gitleaks.toml'u CODEOWNERS ile koru, allowlist değişikliklerini security champion review'una düşür.

Sık Düşülen Tuzaklar

Birincisi, silen commit'i çözüm sanmak. Secret geçmişte duruyor; tek çözüm rotate etmek.

İkincisi, varsayılan kurallara güvenmek. Gördüğümüz gibi generic kural stopword'ler yüzünden dev önekli iki değeri birden kaçırdı. Kendi secret formatların için kural yaz; yeni bir secret türü tasarlarken de tanınabilir bir önek koy.

Üçüncüsü, allowlist'i geniş tutmak. tests/ klasörünün tamamını muaf tutmak, oraya yapıştırılan gerçek bir key'i de muaf tutar. Path ile değer önekini birlikte şart koş.

Dördüncüsü, base64'ü gizleme sanmak. Kubernetes Secret manifest'i ya da base64'lenmiş bir config değeri düz metin kadar açıktır.

Beşincisi, bypass'ı sıradan bir buton saymak. "Sonra düzelteceğim" ile geçilen her push açık bir alert bırakır; o alert'lerin sahibi ve tarihi yoksa push protection yalnızca bir uyarı ekranına dönüşür.

Küçük Bir Deneme

★☆☆ Kendi projende gitleaks git --redact --verbose . ile tüm geçmişi tara. Bulgu çıkarsa her biri için "hâlâ geçerli mi?" sorusunu cevapla.

★★☆ Ekibinin kullandığı bir secret formatı (bir connection string, bir iç servis token'ı) için .gitleaks.toml'a kural yaz. Önce varsayılan kurallarla yakalanıp yakalanmadığını dene, sonra kendi kuralınla; değeri bir de base64'leyip tekrar tara.

★★★ Yerel bir bare repo'yu origin yaparak bu yazıdaki senaryoyu kur: secret'ı --no-verify ile commit'le, sonraki commit'te sil, PR commit aralığını tara, ardından git-filter-repo ile temizle. Eski clone'dan normal bir push denediğinde secret'ın geri gelip gelmediğini gözle.

Secret'lar en sol uçtaydı; bir sonraki yazıda bağımlılıklara geçiyoruz. .NET 10'da NuGetAudit'in transitive paketleri varsayılan olarak denetlemesini, npm audit ve Dependabot'u kuracak, bir zafiyetin önceliğini CVSS (zafiyetin ciddiyetini 0-10 arası puanlayan sistem) skorunun ötesinde EPSS (bir zafiyetin istismar edilme olasılığını tahmin eden skor), KEV (CISA'nın aktif olarak istismar edilen zafiyetler listesi) ve reachability ile nasıl belirleyeceğimizi göreceğiz.