← blog · 17 Eylül 2026

Modele değil çıktıya güvenin: otomatik içerikte son savunma hattı

Otomatik bir içerik hattı kurarken en zor iş metni yazmak değil, metnin iç bilgi sızdırmayacağından emin olmaktı. Modele "sızdırma" demenin neden yetmediğini; çıktıyı deterministik bir süzgeçten geçirmenin, kimlik doğrulamada kapalıya düşmenin ve araya insan onayı koymanın neden şart olduğunu anlattık.

Bir süredir blog yazılarımızın bir kısmını otomatik bir hat üretiyor. Metni bir üretici hazırlıyor, sonra bir uca gönderiyor, uç da yazıyı onay kuyruğuna koyuyor. Kulağa basit geliyor. Ama bu hattı kurarken en çok vaktimizi alan kısım metni yazmak değil, metnin ne olursa olsun sızdırmaması gereken şeyleri sızdırmayacağından emin olmaktı.

İlk refleks şu oluyor: üreticinin talimatına "iç adresleri, parolaları, sunucu isimlerini yazma" diye bir cümle ekle, bitsin. Denedik. Sorun şu ki bir talimat güvenlik kontrolü değildir. Talimat olasılıksaldır. Bugün uyar, yarın uzun bir bağlamın ortasında unutulur. Bir de üretici, beslendiği kaynaklardan farkında olmadan içerik taşıyabilir. Bizim üreticimiz değişiklik kayıtlarından ve teknik notlardan besleniyordu, o notların içinde ağ adresleri, konteyner isimleri ve iç yollar vardı. Yani modele "sızdırma" demek, sızdırmayacağını garanti etmiyordu.

Bunun üzerine kuralı tersine çevirdik: modele değil, çıktıya güven. Üretilen her metin, yayına yaklaşmadan önce deterministik bir süzgeçten geçiyor. Süzgeç olasılık tanımıyor, desen tanıyor. Bir desen eşleşirse metin reddediliyor, sebebiyle birlikte geri dönüyor.

Süzgeçte ne aradığımız zamanla oturdu. Özel ağ aralıkları ilk sıradaydı: 10 ile başlayan blok, 172.16 ile 172.31 arası ve 192.168 ile başlayan aralık. Bunların yanına, mesh ağlarında iç adreslerin dağıldığı taşıyıcı sınıf CGNAT aralığını (100.64 ile başlayan blok) da ekledik. Sonra sır desenleri geldi. Özel anahtar başlığı (bir "BEGIN PRIVATE KEY" ibaresi) net bir imza. Erişim jetonları da öyle: GitHub tarafında ghp ve github_pat önekleri, GitLab tarafında glpat öneki, Slack tarafında xox ile başlayan jetonlar. Bu önekler herkese açık bilgi, ama tam da bu yüzden bir düzenli ifadeyle yakalamak kolay. Son olarak iç servis isimlerini engelledik. Bloga ait alan adı serbest, ama onun dışındaki her alt alan yasak. Böylece hangi aracı nerede çalıştırdığımız bir yazıya kazara düşse bile dışarı çıkamıyor.

Süzgeci neden başka bir modelle değil de düz desenlerle kurduğumuz da bilinçli bir tercihti. Sızıntıyı yakalamak için bir sınıflandırıcı model koysaydık, olasılıksal sorunu bir kat aşağı taşımış olurduk: yeni model de yanılabilir, ikna edilebilir, zamanla kayabilirdi. Düz bir desen ise denetlenebilir. Ne yakaladığını okuyup görebiliyoruz, test yazabiliyoruz ve bir kere "bu aralık yasak" dedikten sonra fikrini değiştirmiyor. Güvenlik kontrolünün sıkıcı ve tahmin edilebilir olması bir kusur değil, tam da istediğimiz şey.

Bu yaklaşım zaman zaman masum metinleri de reddediyor. Bir yazı, yasakladığımız bir desene benzeyen bir örnek verdiğinde süzgeç onu da geri çeviriyor. Bununla barıştık, çünkü reddedilen bir taslağın maliyeti neredeyse sıfır: üretici tekrar dener ya da biz metni elle düzeltiriz. Yanlış pozitif bizi birkaç dakika oyalar; yanlış negatif ise iç bir adresi kalıcı olarak yayınlar. İki hata eşit değil, o yüzden şüphede reddetmeyi seçtik.

Bu süzgeci koyarken kendimize sürekli hatırlattığımız şey şu oldu: bu son savunma hattı, tek savunma değil. Üreticiye yine "sızdırma" diyoruz, kaynak notları yine temizlemeye çalışıyoruz. Süzgeç, o iki katman da başarısız olursa devreye giren ağdır. Katmanları üst üste koymak, birinin kaçırdığı yeri diğerinin kapatması demek.

İkinci ders kimlik doğrulamayla ilgiliydi ve klasik bir tuzaktı. Uç bir bearer jetonuyla korunuyor. İlk sürümde jeton yapılandırılmamışsa, yani boş bir dizeyse, gelen boş jeton beklenen boş jetonla eşleşiyordu. Yani yapılandırmayı unutmak, ucu herkese açmak anlamına geliyordu. Bunu "jeton tanımlı değilse uç kapalıdır" kuralıyla düzelttik. Güvenlikte varsayılan davranış her zaman kapalı olmalı. Bir şeyi açık bırakmak bilinçli bir karar olmalı, unutkanlığın sonucu değil.

Üçüncü katman insandı. Uçtan geçen hiçbir metin kendiliğinden yayına çıkmıyor. Her yazı onay bekleyen durumda kaydediliyor. Otomasyonun hızını seviyoruz, ama otomasyonun yayınladığı bir metnin geri alınması, hiç yayınlanmamış olmasından çok daha pahalı. Bir insanın "evet" demesi ucuz bir sigorta.

Bir de sessiz üzerine yazma tuzağını kapattık. Üretici aynı adresle iki kez yazı gönderirse ve o adres zaten yayında olan bir yazıya aitse, eski yazının üzerine yazıp onu kuyruğa geri düşürmek istemiyoruz. Çünkü bu, canlı bir sayfayı kimse fark etmeden yayından kaldırmak demek. Bu yüzden yayındaki bir adrese çakışan gönderim kabul edilmiyor, çakışma bildiriliyor.

Bütün bunlardan çıkan genel ilke şu: otomatik bir hat, üretici ne kadar iyi olursa olsun, çıktısını güvenilmez kabul edin. Metni, dosyayı, cevabı yayına götüren son adımda deterministik bir kontrol koyun. O kontrolü şüphede reddedecek şekilde, yani kapalıya düşecek biçimde kurun. Üretenle yayınlayan arasına bir onay koyun. Ve en önemlisi, her kontrolün neden orada olduğunu yazın, çünkü gerekçesi yazılmamış bir kural ilk sıkışıklıkta silinir.

Bir yan fayda da her reddin gerekçesiyle loglanması oldu. Zamanla bu kayıtlar üreticinin en çok neyi sızdırmaya çalıştığını gösterdi. Belli bir tür iç ismin tekrar tekrar geri çevrildiğini görünce, sorunun kaynağın kendisinde, yani üreticiyi beslediğimiz notlarda olduğunu anladık ve oradan temizledik. Reddedilen her metin, bir sonraki sızıntıyı önlemek için bir ipucu.

Bu hattı kurduğumuzdan beri süzgeç birkaç kez iş başında oldu. Üretici, teknik bir notu özetlerken bir iç adres taşıdı, süzgeç reddetti, biz de kaynak notu temizledik. Kimse bir şey yayınlamadı, kimse bir şeyi geri almak zorunda kalmadı. İyi bir güvenlik kontrolünün en güzel tarafı da bu: çalıştığında hiçbir şey olmaz.