Türkçe Casefold ve Dotsuz-ı Bypass'ı: Guardrail'leri Kıran 8 Harf Dönüşümü
tr-TR locale'inde ortaya çıkan, İngilizce test setlerinde görünmeyen bir filtre kaçırma sınıfı
Türkçe'nin noktalı/noktasız i çifti, Unicode büyük-küçük harf dönüşümünü tek bir locale'de sessizce bozar. Bir guardrail'in "IGNORE" için yazdığı filtre, "ıgnore" karşısında çaresiz kalır; çünkü hatayı yalnızca tr-TR case-mapping'i üretir.
Türkçe Casefold Bypass Nedir?
Türkçe casefold bypass, bir uygulamanın metni büyük-küçük harf açısından normalize ederken (casefolding) tr-TR locale kurallarını kullanması sonucu, güvenlik filtresinin "aynı" saymayı beklediği iki dizginin farklı; "farklı" saymayı beklediği iki dizginin ise aynı sayıldığı bir kaçırma sınıfıdır. Kökeninde Türkçe'nin dört-üyeli i ailesi vardır: noktasız küçük ı (U+0131), noktalı küçük i (U+0069), noktasız büyük I (U+0049) ve noktalı büyük İ (U+0130). İngilizce'de I harfinin küçüğü i'dir; Türkçe'de ise I'nın küçüğü ı, i'nin büyüğü İ'dir.
Bu tek fark, locale-bağımlı bir toLowerCase()/toUpperCase() çağrısını girdi-bağımlı bir güvenlik açığına dönüştürür. Sunucu tr-TR locale altında çalışıyorsa, saldırganın yasaklı bir anahtar kelimeyi (örneğin bir talimat sızdırma tetikleyicisini) noktasız-ı ile yazması, filtrenin normalize ettiği biçimle eşleşmemesine yeter. Filtre metni görür ama tanımaz.
Mekanizma: Unicode Case-Mapping Collision'ı
Unicode standardı, çoğu dilde tek bir doğru büyük-küçük eşlemesi tanımlar; ancak birkaç karakter için locale-özel istisnalar vardır. Türkçe (ve Azerice) i harfleri bunların en meşhurudur ve Unicode'un SpecialCasing.txt dosyasında açıkça tr/az koşuluyla listelenir. Sonuç, aynı kod satırının işletim sistemi/JVM/çalışma zamanı locale'ine göre farklı çıktı üretmesidir:
| Girdi | Varsayılan (en) lowercase | tr-TR lowercase |
|---|---|---|
| IGNORE | ignore | ıgnore |
| ADMIN | admin | admın |
| İPTAL | i̇ptal | iptal |
Buradaki tehlike, geliştiricinin filtresini bir locale'de test edip başka bir locale'de çalıştırmasıdır. Guardrail geliştiricisi "ignore previous instructions" kalıbını en-US makinesinde doğrular; sistem tr-TR sunucuda koşar; saldırgan payload'ı ıgnore ile gönderir. İki normalize edilmiş biçim asla buluşmaz.
Bu olgunun kimlik doğrulamadaki gerçek karşılığı iyi belgelenmiştir: bir araştırmacı, John@Gıthub.com adresinin tr-TR toLowerCase() altında [email protected] ile çakışabildiğini ve bunun GitHub şifre sıfırlama akışında istismar edilebildiğini göstermiştir. LLM guardrail'leri de tam olarak aynı normalize-sonra-karşılaştır desenini kullandığından, aynı collision doğrudan prompt filtrelemesine taşınır.
AltaySec Taksonomisi: 8 Dönüşüm Ailesi
Olguyu tek bir "noktasız-ı hatası" olarak görmek yüzeyseldir. AltaySec'in kendi turkish-casefold-evasion çalışmasında bypass'ı üreten mekanizmaları sekiz aileye ayırıyoruz. Kritik nokta: bunların hepsi casefold değildir. Mekanizma ayrımını bilerek işaretliyoruz, çünkü savunma da mekanizmaya göre değişir.
| # | Aile | Mekanizma | Örnek |
|---|---|---|---|
| 1 | I → ı (locale lowercase) | Gerçek casefold collision (tr-TR) | IGNORE → ıgnore |
| 2 | İ → i (locale lowercase) | Gerçek casefold collision (tr-TR) | İPTAL → iptal |
| 3 | ş → s | ASCII-fold / deaccent (casefold değil) | şifre → sifre |
| 4 | ğ → g / ç → c / ü → u / ö → o | ASCII-fold / deaccent (casefold değil) | güvenlik → guvenlik |
| 5 | Homoglif ikamesi | Görsel eşdeğer Latin/Kiril karakter | аdmin (Kiril а) |
| 6 | NFKC normalizasyon farkı | Kombine vs önceden-birleşik biçim | i + birleşen nokta |
| 7 | Locale-bağımlı toLowerCase | Aynı kod, farklı runtime locale çıktısı | ADMIN → admın |
| 8 | Harf-arası sıfır-genişlik ekleme | ZWSP/ZWNJ ile token kırma | admin (görünmez U+200B) |
Bir uyarı: aile 1, 2 ve 7 gerçek locale-casefold hatalarıdır; aile 3 ve 4 aslında ASCII-fold / deaccent mekanizmasıdır (Türkçe'ye özgü aksanlı harflerin ASCII'ye indirgenmesi), casefold değil; aile 5, 6 ve 8 ise Unicode normalizasyon ve homoglif katmanına aittir. Hepsini "Türkçe casefold" başlığı altında topluyoruz çünkü ortak sonuçları aynı: Türkçe metnin normalize edilme biçimindeki bir sapma, anahtar-kelime filtresini atlatır. Ama düzeltmeleri farklıdır ve mekanizmayı karıştırmak yanlış savunmaya yol açar.
Bu 8-aile taksonomisi AltaySec'in kendi sınıflandırmasıdır; aile 1 ve 2'nin case-mapping gösterimini ise NVIDIA garak'a açtığımız Türkçe casefold PR'ında (TurkishLowercase/TurkishUppercase buff'ları, "IGNORE" → "ıgnore" bypass demosu) somutlaştırdık. Bu PR yazımı itibarıyla topluluk incelemesinde açık durumdadır.
Neden İngilizce Test Setlerinde Görünmez?
Bu boşluğun kanonik bir Türkçe kaynağının neredeyse hiç olmamasının nedeni yapısaldır: hata yalnızca tr-TR locale'inde ortaya çıkar. İngilizce bir red-team corpus'unda IGNORE harfini küçültürseniz her zaman ignore elde edersiniz; collision hiç doğmaz. Test matrisiniz İngilizce ise bypass gözlemlenemez — açık orada değildir, gözünüz oraya bakmıyordur.
Dolayısıyla küresel LLM güvenlik literatürünün büyük çoğunluğu, encoding tabanlı kaçırmaları (Base64, leetspeak, ROT13) incelerken locale-casefold ailesini es geçer. AltaySec'in kendi guardrail-arena ölçümleri, Türkçe payload'ların İngilizce muadillerine kıyasla sınıflandırıcılar tarafından belirgin biçimde daha yüksek oranda kaçırıldığını gösteriyordu; casefold ailesi bu farkın az konuşulan bir bileşenidir. Bunu bilinen tek sistematik Türkçe ölçüm çabası olarak konumlandırıyoruz — mutlak bir "tek kaynak" iddiası değil, savunulabilir bir boşluk tespiti.
Doğru Düzeltme: Locale-Bağımsız Casefold
İyi haber: locale-casefold ailesinin (aile 1, 2, 7) düzeltmesi tek satırlıktır. Anahtar prensip, güvenlik-kritik normalizasyonun asla runtime locale'ine bırakılmamasıdır. Locale'e duyarlı toLowerCase() yerine, locale-değişmez (invariant) casefold kullanın.
# Python — locale-bağımsız, güvenlik için doğru:
normalized = user_input.casefold() # locale-bağımsız tam case-fold; güvenlik için str.lower() yerine casefold
# Java — invariant locale'i açıkça zorla:
String n = input.toLowerCase(java.util.Locale.ROOT);
# JavaScript — dil-duyarlı API'yi kullanma:
const n = input.toLowerCase(); // ECMAScript spec'i locale-BAĞIMSIZDIR (güvenli)
// AMA String.prototype.toLocaleLowerCase() KULLANMAYIN — locale'e bağlıdır
Python'un str.casefold() metodu, Unicode'un locale-bağımsız casefold algoritmasını uygular ve Türkçe i çiftini beklenen İngilizce davranışına sabitler. Java'da Locale.ROOT geçmek, sunucu tr-TR olsa bile deterministik sonuç verir. JavaScript'te toLowerCase() zaten locale-bağımsızdır; asıl tuzak toLocaleLowerCase()'dir.
Diğer aileler için düzeltme mekanizmaya göre değişir: ASCII-fold ailesi (3, 4) için filtreyi hem orijinal hem deaccent edilmiş biçim üzerinde çalıştırın; homoglif ve NFKC aileleri (5, 6) için girdiyi karşılaştırmadan önce NFKC ile normalize edip Unicode script-mixing kontrolü uygulayın; sıfır-genişlik ailesi (8) için U+200B/U+200C/U+200D gibi görünmez kod noktalarını normalizasyondan önce sökün. Tek bir casefold() tüm sekiz aileyi kapatmaz — bu, mekanizma ayrımını korumanın pratik nedenidir.
Guardrail Mimarisi İçin Çıkarımlar
Bu sınıf, "anahtar kelime filtresi + naif lowercase" mimarisinin neden yetersiz olduğunu gösteren temiz bir örnektir. Sağlam bir savunma katmanı için:
- Normalize-önce, karşılaştır-sonra: Her girdiyi tek bir kanonik biçime (locale-bağımsız casefold + NFKC + görünmez-karakter temizliği) indirgeyin; filtreyi bu kanonik biçim üzerinde çalıştırın.
- Locale'i sabitleyin: Güvenlik-kritik yollarda çalışma zamanı locale'ine güvenmeyin; invariant/ROOT locale'i açıkça zorlayın.
- Türkçe payload ile test edin: Red-team corpus'unuza noktasız-ı, deaccent ve homoglif varyantlarını ekleyin; İngilizce-yalnız test bu aileyi göremez.
- Anlamsal katman ekleyin: Regex tabanlı filtreler yüzey biçimine bağlıdır; anlam düzeyinde çalışan bir sınıflandırıcı casefold varyantlarına daha dayanıklıdır (ama tek başına yeterli değildir).
AltaySec'in Guardian LLM güvenlik duvarında girdi normalizasyonunu bilinçli olarak filtre eşleşmesinden önceye koyuyoruz; Türkçe-öncelik moat'ımızın somut bir parçası da bu casefold ailesini varsayılan test matrisimize dahil etmektir. Amaç bir üstünlük iddiası değil, İngilizce test setlerinin kör kaldığı bir yüzeyi kapatmaktır.
Kaynaklar
- Hacking GitHub's Auth with Unicode's Turkish Dotless I — jagracey (dev.to)
- The Turkish İ Problem and Why You Should Care — Phil Haack (haacked.com)
- Turkish i18n and the Dotless I — i18nguy.com
- NVIDIA garak PR #1997 — Turkish casefold / case-mapping buff
- Unicode SpecialCasing.txt — locale-specific case mapping (tr/az)
- Unicode Technical Standard #39 — Security Mechanisms (homoglyph/confusables)
- OWASP Top 10 for Large Language Model Applications
