Güvenlik açıkları çoğunlukla “yayına alırız, sonra bakarız” diye geçilen adımlarda doğar. Bu yüzden web siteleri ve mobil uygulamalar için yayını bir kapıya bağladım: kapıdan geçemeyen sürüm yayına çıkmıyor. Bu kapıyı kendim geliştirdim ve adı security-gate. Bu yazıda neyi, nasıl kontrol ettiğini ve sahada öğrendiğim dersleri anlatıyorum.
Kapı neleri kontrol ediyor
| Alan | Araç ya da yöntem |
|---|---|
| Sızmış sırlar | gitleaks ve trufflehog; hem çalışma dosyaları hem git geçmişinin tamamı |
| Statik kod analizi | semgrep, OWASP ve CWE kural setleri |
| Bağımlılıklar ve imajlar | trivy ve yazılım malzeme listesi (SBOM) |
| Açık yüzey taraması | nuclei, 900’ü aşkın topluluk şablonu |
| TLS yapılandırması | testssl |
| Dinamik test (DAST) | OWASP ZAP |
| HTTP güvenlik başlıkları | Araca gömülü kontrol |
| DNS ve e-posta hijyeni | DMARC, CAA, MTA-STS, TLS-RPT ve DKIM kontrolü |
| Altyapı | Port taraması, SSH denetimi, imaj ve sunucu sertleştirme |
Kontroller projeye göre açılıp kapatılabiliyor. Statik bir sitede SSH denetimi anlamsızken, bir API sunucusunda şart.
Karar mantığı
Her kontrol dört sonuçtan birini döndürüyor: geçti, kaldı, insan incelemesi gerekli ya da uygulanamaz. Bulgular OWASP Top 10:2025, ASVS 5.0 ve CWE Top 25 ile eşleniyor; raporda her bulgunun hangi standarda karşılık geldiği görünüyor.
Yayın iki durumda otomatik olarak durduruluyor:
- Doğrulanmış ve hâlâ geçerli bir sır bulunduysa. Bir anahtarın yalnızca dosyada görünmesi değil, gerçekten çalıştığının doğrulanması önemli.
- Zorunlu insan incelemesi maddeleri kapatılmadıysa.
Neden bazı maddeler insana bırakılıyor
Kimlik doğrulama ve oturum yönetimi, erişim kontrolü, iş mantığı, tehdit modeli ve dış sızma testi otomatik araçlarla güvenilir biçimde onaylanamaz. Bir tarayıcı, “müşteri A, müşteri B’nin faturasını görebiliyor mu” sorusunu cevaplayamaz. Bu maddeler, bir insan kontrol edip kapatmadan yayın açılmıyor.
Çıktılar
Rapor JSON, Markdown ve SARIF biçiminde üretiliyor; SARIF, bulguların doğrudan kod barındırma platformunun güvenlik sekmesinde görünmesini sağlıyor. Ayrıca CycloneDX biçiminde bir yazılım malzeme listesi ve her adımı sırasıyla kaydeden bir olay günlüğü çıkıyor.
Güvenlik aracının kendisi nasıl test ediliyor
Bir güvenlik aracı, yanlışlıkla “geçti” dediği gün en tehlikeli hâline gelir. Bu yüzden aracın kendi test kapsamını ciddiye alıyorum: son sürümde 464 test, 13 Docker entegrasyon testi ve yaklaşık %90 satır kapsamı var; testler her değişiklikte sürekli entegrasyonda çalışıyor.
Bu testler sayesinde yakaladığım iki gerçek sorun:
- Sessizce geçen tarayıcılar: Tarayıcılar, güvenlik için bütün yetkileri kaldırılmış konteynerlerde çalışıyordu. Bu kısıtlama yüzünden yalnızca sahibinin okuyabildiği dosyaları okuyamıyor ve hata vermek yerine temiz sonuç döndürüyorlardı. Çözüm, kaynak tarayıcılarını dosyaların sahibi olan kullanıcı kimliğiyle çalıştırmak oldu.
- Kapıyı atlatan yapılandırma: Bir tarayıcı, taranan projenin içindeki kendi yapılandırma dosyasını otomatik yüklüyordu. Yani taranan proje, kendi içine koyduğu bir dosyayla “kaldı” sonucunu “geçti”ye çevirebiliyordu. Bu yolu kapattım; tarayıcılar artık yalnızca kapının verdiği yapılandırmayla çalışıyor.
Web ve mobil için standart kontrollerim
- İçerik güvenlik politikası (CSP): Satır içi betik ve stil yok, yalnızca sitenin kendi dosyaları. Bu blogun bulunduğu site de aynı politikayla yayında.
- HSTS: Tarayıcı siteyi yalnızca HTTPS ile açıyor.
- Alt kaynak bütünlüğü (SRI): Dışarıdan yüklenen betiklerin değiştirilmediği doğrulanıyor.
- Nginx tuzağı:
add_headerüst seviyeden miras alınmaz. Birlocationbloğunda tek bir başlık tanımlarsanız, sunucu seviyesindeki bütün güvenlik başlıkları o yanıtta kaybolur. Bu yüzden başlıkları her yol için ayrı ayrı doğruluyorum. - Mobil: Silme işlemlerinin üç katmanda engellenmesi ve yazma işlemlerinin açık onaya bağlanması. Ayrıntıları mobil uygulama yazısında.
Sunucu tarafında öğrendiğim dersler
Birden fazla sunucuyu tek bir hedef sunucuda birleştirirken ve kendi e-posta sunucumu işletirken karşılaştığım, dokümanlarda kolay bulunmayan durumlar:
Docker, güvenlik duvarını atlar
Docker’ın dışarıya açtığı konteyner portları, ufw kurallarının bulunduğu zincirden değil, Docker’ın kendi yönlendirme kurallarından geçer. Yani ufw ile kapattığınızı düşündüğünüz port açık kalabilir. Filtre DOCKER-USER zincirine yazılmalı. Üstelik Docker paketi hedef porta çevirdikten sonra eşleşme yapıldığı için kural, özgün porta -m conntrack --ctorigdstport ile bakmalı; aksi hâlde port sessizce açık kalır.
Tek dosya bağlamaları ve yerinde düzenleme
Konteynere tek bir dosya olarak bağlanmış bir yapılandırmayı sed -i ile düzenlemek yeni bir dosya oluşturur. Konteyner eski dosyayı görmeye devam eder: komut başarılı görünür ama değişiklik uygulanmaz. Bu yüzden bir değişikliği her zaman canlı yanıttan doğruluyorum, komutun çıktısından değil.
Yeniden oluşturulan konteyner ağ bağlantısını kaybeder
Elle bir ağa bağlanmış ters vekil konteyneri docker compose up -d ile yeniden oluşturulduğunda o bağlantı düşer ve arkasındaki bütün siteler 502 verir. Kalıcı çözüm, paylaşılan ağı compose dosyasında harici ağ olarak tanımlamak.
Geri yüklenmemiş yedek, yedek değildir
Resmi bir yedekleme aracının ürettiği paketlerin, boş kalmış tek bir ayar yüzünden geri yüklenemediğini ancak geri yüklemeyi denediğimde gördüm. O günden beri her yedek, geri yüklenip doğrulanmadan “var” sayılmıyor. Her gece arşiv bütünlüğü test ediliyor ve dosya sayıları canlı sistemle karşılaştırılıyor.
Taşımada sessiz veri kaybı
- Kayan imaj etiketleri (
latest) beklenmedik şema değişikliği getirebilir; imajları özet (digest) değeriyle sabitliyorum. - Konteynerin yazılabilir katmanı, imaj ve volume yedeğine dahil değildir; ayrıca taşınmalı.
- SQLite’ın
-walve-shmdosyaları ana dosyayla birlikte kopyalanmazsa en güncel veri kaybolur. - Kaynak sunucu, hedefte bağımsız olarak doğrulanmadan kapatılmaz.
E-posta sunucusunun tuzakları
- Sertifikayı alan adıyla (SNI) test edin. Alan adına özel bir sertifikanın süresi dolmuş olabilir. Alan adı belirtmeden yapılan test ana sertifikayı gösterir ve sorunu gizler.
- Yenilemeden sonra servisi yeniden başlatın. Bazı servisler yeni sertifikayı yalnızca yeniden başlatınca okur; yeniden yükleme yetmez.
- Dahili test araçlarına körü körüne güvenmeyin. Bir platformun kendi parola test aracının doğru parolaya “yanlış” dediğini gördüm. Asıl teşhis günlük satırlarından yapılmalı; bu sırada parolanın kendisi hiçbir yere yazdırılmamalı.
- Günlük döndürme sahte alarm üretir. Konteyner günlüğü son döndürmeden beri tutulduğu için günlük bazlı sayım “e-posta kesildi” alarmı verebilir. Doğru ölçüm, posta kutusu dosyalarını değişim zamanına göre saymaktır.
İlkelerim
- Gerçek bir sır, özel bir depoda bile olsa hiçbir dosyaya, commit’e ya da sohbet kaydına yazılmaz.
- Bir kişinin girmesi gereken sır, kendi terminaline gizli girişle yazılır. Otomasyon değeri görmez, yalnızca özetini doğrular.
- Canlı bir sistemde değişiklikten önce her zaman bir geri dönüş noktası bırakılır.
- Yedekler sahibi açıkça istemedikçe otomatik silinmez.
- Yönetim arayüzleri ağa kapatılır ve yalnızca tünel üzerinden erişilir.
- Bir değişiklik, canlıda doğrulanmadan bitmiş sayılmaz.
Bu kapının kontrol ettiği sitelerin nasıl kurulduğunu web sitesi yazısında anlattım.