İstemde sır tutmamak ve yapılandırılmış çıktı
- Sırrı sistem isteminden araç katmanına taşır, kayıttaki istemi maskeler, model çıktısını JSON Şema ve iş kuralıyla iki kez doğrularsın.
- Bu derste sızıntıya açık istemi bulur, anahtarı ortam değişkenine taşır, Pydantic doğrulama hatasını okur ve güvenli varsayılana düşersin.
Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +50 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.
İkinci derste yetkinin modelde değil araç katmanında durduğunu gördün. Aynı ayrım sır için de geçerlidir. Altay Kargo'nun destek asistanı, depo sisteminden gönderi durumunu soruyor.
İlk sürümde ekip, depo API anahtarını doğrudan sistem istemine (system prompt) yazdı. Amaç basitti: model isteği kendisi biçimlendirsin. Birkaç hafta sonra bir müşteri, asistandan aldığı yanıtın sonunda tuhaf bir harf dizisi gördü ve destek hattını aradı.
Dizi, depo API anahtarının ta kendisiydi. Bu derste iki konuyu işlersin. İlki modele ne gösterdiğindir: sır istemde değil, araç katmanında durur ve kayıtlara da maskelenerek girer. İkincisi modelden ne aldığındır: serbest metin yerine şemaya uyan bir çıktı istersin ve bu çıktıyı hem şemayla hem iş kuralıyla doğrularsın.
Sistem istemi sır değildir
Geliştiriciler sistem istemini çoğu zaman kullanıcının görmediği gizli bir kanal sanır. Oysa sistem istemi modelin okuduğu sıradan bir metindir. Model bu metni özetleyebilir, tekrar edebilir ya da bir araç çağrısının parametresine taşıyabilir.
Dolaylı istem enjeksiyonu modeli bunu yapmaya yönlendirebilir. Bazen hiçbir saldırı da gerekmez. Model, bir hata ayıklama sorusuna yanıt verirken talimatlarını kendiliğinden aktarabilir.
Kayıt ve izleme araçları da çoğu zaman istemin tamamını, sistem istemi dahil, bir yerde saklar. Bu yüzden istemdeki her bilgi, en az üç yoldan dışarı çıkabilir: modelin yanıtı, aracın parametresi ve kayıt sistemi. Sırrı isteme yazmak kolaylık uğruna alınan bir karardır, ama fark edilmesi zor bir borç bırakır.
OWASP'ın 2025 listesi bu riski LLM07 koduyla, sistem istemi sızıntısı (System Prompt Leakage) adıyla sayar. Listenin ana ilkesi açıktır: sistem istemi sır sayılmaz ve bir güvenlik denetimi olarak kullanılmaz. Belge dört tipik örnek verir.
İstem bir API anahtarını, veritabanı bilgisini ya da mimari ayrıntıyı açığa çıkarabilir. İstem iç kuralları ele verebilir: günlük işlem limitini öğrenen saldırgan, denetimi atlatma girişimini tam bu sınıra göre planlar. İstem hangi içeriğin reddedileceğini gösteren filtre ölçütlerini açabilir.
Son olarak rol ve izin yapısını açığa çıkarabilir. Belgenin vurguladığı nokta şudur: asıl risk istemin görünmesi değil, güvenliğin isteme emanet edilmesidir. Yetki ve ayrım, modelin dışında, belirlenimci kodla uygulanır.
Etkinlik 1 / 6
Bu sistem promptunda sızıntıya açık satırı seç.
Sırrı araç katmanına taşımak
Doğru yerleşim şudur: model yalnız bir aracın adını, ne işe yaradığını ve hangi girdiyi beklediğini bilir. Aracı çağırırken gereken sırrı bilmez. Sır sunucu tarafında, araç katmanında durur ve çağrı gerçekleştiğinde koda eklenir. Model depo durumunu getir aracını çağırır, araç kendi kimlik bilgisini kendi ortam değişkeninden okur.
Model bu kimlik bilgisini hiçbir zaman görmez, dolayısıyla sızdıramaz. Aradaki fark, modelin ne yapabileceğini bilmesiyle nasıl yapılacağını bilmesi arasındaki farktır. Aşağıdaki örnekte önce modelin gördüğü araç tanımı, ardından sunucudaki aracın kendisi yer alıyor.
# Modelin gördüğü araç tanımı: ad, amaç ve girdi şeması.
DEPO_DURUMU_ARACI = {
"name": "depo_durumu",
"description": "Bir gönderinin depodaki durumunu takip numarasıyla getirir.",
"input_schema": {
"type": "object",
"properties": {"takip_no": {"type": "string", "pattern": "^AK[0-9]{6}$"}},
"required": ["takip_no"],
"additionalProperties": False,
},
}
# Sunucudaki araç: anahtar ortam değişkeninden okunur, modele hiç gitmez.
def depo_durumu(takip_no: str) -> dict:
anahtar = os.environ["DEPO_API_ANAHTARI"]
yanit = requests.get(
f"https://depo-api.altaykargo.example/gonderi/{takip_no}",
headers={"Authorization": f"Bearer {anahtar}"},
timeout=5,
)
yanit.raise_for_status()
return {"durum": yanit.json()["durum"]}İlk bölüm araç tanımıdır ve modele giden tek bilgi budur. name ve description aracın ne yaptığını anlatır. input_schema modelin üretebileceği girdiyi sınırlar: tek bir takip numarası, AK ile başlayan altı haneli bir dizge. additionalProperties false olduğu için model fazladan alan ekleyemez.
İkinci bölüm sunucuda koşar. 15. satır anahtarı ortam değişkeninden okur. Değişkenin değeri bir sır yönetim aracından gelir ve kod deposunda hiç yer almaz.
16-20. satırlar isteği kurar. Anahtar yalnız istek başlığına girer, zaman aşımı da takılan bağlantıyı keser.
Son satır dikkat ister. Depo yanıtının tamamı değil, yalnız durum alanı modele döner. Yanıtta iç adres, depo kodu ya da başka bir müşterinin bilgisi varsa model onları hiç görmez.
Etkinlik 2 / 6
Aşağıdaki bilgileri, modelin görmesi güvenli olan ve olmayan olarak ayır.
Önce bir seçeneğe, sonra aşağıdaki uygun kutuya bas. İstersen seçeneği kutuya sürükleyip bırak. Yerini değiştirmek için aynı işlemi tekrarla.
0/4 seçenek yerleşti.
Kayıtta istem ve yanıt
Sırrı istemden çıkarmak yetmez, kayıt da ayrı bir sızıntı kapısıdır. Ekipler hata ayıklamak için istemi ve yanıtı olduğu gibi kayda yazar. Müşteri biletine kendi e-posta adresini ya da kimlik numarasını yazmışsa, bu bilgi kayıt sistemine de akar. Kayda erişen herkes artık modelin işlediği kişisel veriyi görür.
OWASP listesi bu riski LLM02 koduyla, hassas bilgi ifşası adıyla sayar. Türkiye'de konu KVKK'ya da dokunur. Kanunun 4. maddesi kişisel verinin amaçla bağlantılı, sınırlı ve ölçülü işlenmesini, gerekli süre kadar saklanmasını ister.
12. madde de veriye hukuka aykırı erişimi önleyecek teknik ve idari tedbiri şart koşar. Bu yüzden kayda yazmadan önce maskeler, kaydı kısa bir süre saklar ve erişimi daraltırsın.
MASKELER = [
(re.compile(r"\bak_canli_[A-Za-z0-9]{24}\b"), "[ANAHTAR]"),
(re.compile(r"[\w.+-]+@[\w-]+(\.[\w-]+)+"), "[EPOSTA]"),
(re.compile(r"\b[1-9][0-9]{10}\b"), "[TCKN]"),
]
def maskele(metin: str) -> str:
for kalip, yerine in MASKELER:
metin = kalip.sub(yerine, metin)
return metin
# önce : Faturayı [email protected] adresine gönderin
# sonra: Faturayı [EPOSTA] adresine gönderinMASKELER listesi üç kalıp taşır. İlki Altay Kargo'nun kendi API anahtarı biçimini yakalar: ak_canli öneki ve ardından 24 harf ya da rakam. Kendi anahtar biçimini bilmek, genel kalıplardan çok daha isabetli sonuç verir.
İkinci kalıp e-posta adresini, üçüncüsü 11 haneli ve sıfırla başlamayan bir sayıyı yakalar. Üçüncü kalıp her 11 haneli sayıyı kimlik numarası sanabilir. Bu bir yanlış pozitiftir, ama kayıtta fazla maskelemek eksik maskelemekten daha az zarar verir.
maskele işlevi kalıpları sırayla uygular ve her eşleşmeyi bir etiketle değiştirir. Maskeleme kayıt kütüphanesinin içine, tek bir noktaya konur. Her geliştiricinin kendi satırında hatırlamasına bırakılan maskeleme bir gün mutlaka unutulur.
Maskeleme tek başına yetmez, kayıt düzeninin üç ayarı daha var. İlki saklama süresidir. Altay Kargo, hata ayıklama için tuttuğu istem kayıtlarını 30 gün sonra siler, yalnız sayısal ölçüleri daha uzun saklar. İkincisi erişimdir.
Ham kayda yalnız nöbetteki küçük bir ekip erişir ve her erişim ayrıca kaydedilir. Üçüncüsü sızıntı anındaki sıradır. Bir anahtar kayda ya da yanıta düşmüşse önce anahtarı döndürürsün, kaydı temizlemek ikinci iştir. Sızmış bir anahtarı kayıttan silmek, onu bir yere kopyalamış birinin elinden almaz.
Etkinlik 3 / 6
Altay Kargo'nun model kaydında maskelenmeden yazılmış hassas veriyi taşıyan satırları işaretle.
Şüpheli satırlara tıkla. Birden fazla olabilir.
Serbest metinden yapılandırılmış çıktıya
İkinci konu, modelden ne aldığınla ilgili. Altay Kargo'nun ilk iade asistanı, modelin serbest metin yanıtından tutarı düzenli ifadeyle ayıklıyordu. Model biçimi biraz değiştirdiğinde ayrıştırma bozuldu.
Beş yüz lira ile 500 TL aynı anlama gelir ama farklı dizgedir. Daha kötüsü, yanlış ayıklanan bir tutar sessizce bir sonraki adıma geçebilir. Daha sağlam yol, modelden önceden tanımlı bir şemaya uyan yapılandırılmış bir çıktı istemektir.
JSON Şema bu iş için kullanılan standart bir dildir. Alanların türünü, zorunlu alanları, izinli değerleri ve fazladan alana izin verilip verilmediğini yazarsın. Kategori gibi sınırlı bir değer için enum kullanırsın, böylece model serbest bir kelime uyduramaz.
{
"type": "object",
"properties": {
"kategori": { "type": "string", "enum": ["gecikme", "hasar", "kayip", "diger"] },
"takip_no": { "type": "string", "pattern": "^AK[0-9]{6}$" },
"tutar": { "type": "integer", "minimum": 0, "maximum": 500 }
},
"required": ["kategori", "takip_no", "tutar"],
"additionalProperties": false
}Büyük model sağlayıcıları bu şemayı doğrudan kabul eden bir mod sunar. Yapılandırılmış çıktı (structured outputs) modunda yanıt, verdiğin şemaya uyacak biçimde üretilir. Bazı sağlayıcılar bu modda bütün alanların zorunlu olmasını ve additionalProperties alanının false olmasını ister.
Garanti biçimle sınırlıdır. Bir sağlayıcının belgesi, minimum ve maximum gibi sayı kısıtlarını desteklemediğini açıkça yazar. Model güvenlik gerekçesiyle isteği reddedebilir de. Bu durumda gelen ret yanıtı şemaya uymaz.
Çıktıya üst sınırlar da koyarsın: en fazla jeton sayısı, dizgenin uzunluğu ve bir dizideki eleman sayısı. Sınırsız bir alan hem maliyeti hem de saldırı yüzeyini büyütür. Bütün bunlar aynı sonuca çıkar: şema modelin ne üreteceğini daraltır, sunucu yine de kendi doğrulamasını yapar.
Etkinlik 4 / 6
Bir ajan, modelin serbest metninden 'tutar: 500 TL' ifadesini düzenli ifadeyle ayıklıyor. Model bazen 'beş yüz lira' yazınca ayrıştırma bozuluyor. En sağlam çözüm hangisidir?
Şema, iş kuralı ve güvenli varsayılan
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, ValidationError
class IadeKarari(BaseModel):
model_config = ConfigDict(extra="forbid")
kategori: Literal["gecikme", "hasar", "kayip", "diger"]
takip_no: str = Field(pattern=r"^AK\d{6}$")
tutar: int = Field(ge=0, le=500)
def karari_al(ham: str, oturum) -> IadeKarari | None:
try:
karar = IadeKarari.model_validate_json(ham)
except ValidationError as hata:
kayit.uyar("model_cikti_gecersiz", hata_sayisi=hata.error_count())
insan_kuyrugu.ekle(ham, oturum)
return None
if not siparisler.musteriye_ait_mi(karar.takip_no, oturum.musteri_id):
insan_kuyrugu.ekle(ham, oturum)
return None
return karar>>> IadeKarari.model_validate_json('{"kategori":"hasar","takip_no":"AK20411","tutar":750,"onay":"tamam"}')
ValidationError: 3 validation errors for IadeKarari
onay
Extra inputs are not permitted [type=extra_forbidden, input_value='tamam', input_type=str]
takip_no
String should match pattern '^AK\d{6}$' [type=string_pattern_mismatch, input_value='AK20411', input_type=str]
tutar
Input should be less than or equal to 500 [type=less_than_equal, input_value=750, input_type=int]4-8. satırlar şemanın Python karşılığıdır. extra="forbid" fazladan alanı reddeder, Literal kategoriyi dört değerle sınırlar, Field takip numarasına kalıp ve tutara aralık koyar.
Sağlayıcının desteklemediği sayı kısıtı burada kesin olarak uygulanır. 12. satır model çıktısını tek adımda ayrıştırır ve doğrular. Çıktı örneğinde üç hata birden yakalandı.
Model şemada olmayan bir onay alanı ekledi, takip numarasından bir hane eksik kaldı ve tutar tavanı aştı. 13-16. satırlar güvenli varsayılandır. Doğrulama başarısızsa hiçbir işlem yapılmaz, kayda bir uyarı düşer ve karar insan kuyruğuna gider.
Gerekirse modele hata mesajıyla bir kez daha sorabilirsin, ama yeniden denemeyi sınırlarsın. İkinci başarısızlık doğrudan insana gider.
17. satır iş kuralı katmanıdır ve şemanın hiç göremeyeceği bir soruyu sorar: bu takip numarası bu müşteriye mi ait? AK204118 kalıba uyar, ama başka bir müşterinin gönderisi olabilir. Cevap model çıktısından değil, veritabanından gelir.
Müşteri kimliği de modelden alınmaz, oturumdan okunur. Alanlar arası kurallar da bu katmana girer. Kategori gecikme ise iade tutarı ödenen kargo ücretini aşamaz. Kategori kayıp ise tutar, sipariş kaydındaki ürün bedeliyle karşılaştırılır.
Şema biçimi garanti eder, iş kuralı anlamı. İkisi birlikte çalışır. Biri eksik kalırsa, şemaya kusursuz uyan bir çıktı yine de yanlış kişiye para iade ettirebilir.
Derinleş:Kısıtlı çözümleme ve sınırları
Sağlayıcılar şemaya uyumu kısıtlı çözümleme (constrained decoding) adı verilen bir yöntemle garanti eder. Model her adımda bir sonraki parçayı seçerken, şemayı bozacak seçenekler baştan elenir. Bir nesnenin ilk karakteri süslü parantez olmak zorundaysa, model o adımda başka bir karakter seçemez. Enum tanımlı bir alanda yalnız izinli değerlerden biri yazılabilir.
Yöntem söz dizimini güçlü biçimde garanti eder. Değerin doğruluğunu ise garanti etmez. Model şemaya uygun ama yanlış bir takip numarası, olması gerekenden yüksek ama tavanın altında bir tutar üretebilir. Dolaylı istem enjeksiyonu da bu sınırın içinde kalarak değerleri etkileyebilir.
Desteklenen şema özellikleri sağlayıcıdan sağlayıcıya değişir. Desteklenmeyen kısıt sessizce yok sayılabilir ya da istek hata verebilir. Bu yüzden şemayı sağlayıcıya verdiğin hâliyle değil, sunucuda uyguladığın hâliyle güvenlik sınırı sayarsın.
Etkinlik 5 / 6
Model yapılandırılmış bir çıktı üretse bile, sunucu bu çıktıyı kullanmadan önce ____ doğrulamasından geçirir.
Etkinlik 6 / 6
Doğru mu, yanlış mı?
Sistem promptuna gömülen bir API anahtarı, modelin ürettiği yanıt aracılığıyla dışarı sızabilir.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.