İçindekiler
- Problem
- Commit commit ne ters gitti
- Tasarım kararları ve alternatifler
- Kılık değiştirmeye dayanması gereken bir tasarımı test etmek
- Kullanım
- Sınırlar
Problem
iOSSecurityTools, iOS, macOS, tvOS, watchOS ve visionOS uygulamaları için güvenlik yapı taşlarından oluşan bir Swift paketi: keychain'de saklama, doğrulanmış şifreleme, PIN hash'leri, Secure Enclave anahtarları, sertifika pinning'i, tek kullanımlık parolalar, cihaz bütünlüğü kontrolleri ve log redaction. Bağımlılığı yok. 15 Ekim 2026'da PR #20 ile yayımlanan 2.0.0, paketi sıfırdan yeniden kurdu; Şubat 2026'daki 1.0.0 kod tabanı Haziran 2026'da placeholder kodla değiştirilmişti ve ondan hiçbir şey kalmadı.
Bu yazı o yapı taşlarından birini, SensitiveDataRedactor'ı anlatıyor, çünkü ters giden o oldu. Görevi dar: bir geliştirici bir request'i, response'u, header'ı ya da kullanıcı metnini logluyor ve içinde yanlışlıkla bir parola, token ya da kart numarası var. Redactor bu metni log'a, trace'e ya da analytics event'ine ulaşmadan temizliyor.
2.0.0'daki redactor çoğu redactor gibi çalışıyordu: password, token ya da api_key gibi gizli alan adlarını arıyor ve arkalarındaki değeri gizliyordu. 2.0.0 changelog'u eşleştirmeden önce sıfır genişlikli karakterleri, birleşen işaretleri, tam genişlik biçimlerini ve ASCII dışı rakamları normalize ettiğini ve gizli kelimeleri daha uzun alan adlarının içinde de bulduğunu yazıyor. Bu yetmedi. 2.1.0'ın güvenlik notu açık konuşuyor: "SensitiveDataRedactor in 2.0.0 could be bypassed, and 2.0.0 should not be relied on to keep secrets out of logs." Yani 2.0.0'daki redactor atlatılabiliyordu ve sırları loglardan uzak tutmak için 2.0.0'a güvenilmemeli.
Commit commit ne ters gitti
2.1.0 çalışması PR #25'te yapıldı: 2.0.0'ın üstüne 242 commit. Geçmişi, bir tasarımın terk edilişini gösteriyor; işin öğretici kısmı da bu.
15 Ekim sabahındaki ilk düzeltmeler, bir deny-list'in davet ettiği türdendi. 26ee844 :=, ?= ve += sonrasındaki değeri gizledi; 9d91ea8 == ve != sonrasını; 99166c6 ===, :== ve diğer ayırıcı dizilerini. Her commit birkaç yazımı kapattı, sonrakileri açık bıraktı.
Sonra benzer harfler geldi. 2433c23 değerini yazdıran girdileri listeliyor: Kiril а ile pаssword=hunter2, Yunan ο ile tοken=hunter2, aynı kelimenin matematiksel kalın harflerle yazılışı, daire içinde ⓟ, önceden birleşmiş bir aksan. Çözüm, Kiril, Yunan, Ermeni ve Çeroki benzer harflerini Latin harfi olarak okuyan bir tabloya sahip yeni bir LatinFolding tipiydi. Dört dakika sonra c1c5467 açığı kendi mesajında kabul etti: benzer harfleri Latin olarak okumak yalnız tablodaki çiftlere yardım ediyordu. passwдrd'deki Kiril д'nin Latin ikizi yok; ad ikiye bölündü ve iki yarı da eşleşmedi.
Tablo geldikten doksan dakika sonra silindi. 7fc0753 o branch'te "23 of 51 probe inputs still got a secret through", yani 51 deneme girdisinin 23'ünün hâlâ bir sırrı sızdırdığını kaydediyor ve sonucu çıkarıyor: benzer harf tablosu hiçbir zaman tamamlanmaz, bu yüzden kural tersine çevrildi. Aynı akıl yürütme 20 Ekim'de 7a615e8 ile serbest metne ulaştı. Commit mesajına göre serbest metnin grameri yok, bu yüzden içinde gizli ad aramak her zaman atlatılabilirdi: her benzer harf, ayırıcı ve escape turu yeni bir açık buldu. Varsayılan artık güvenli: redactor'ın tam anlamadığı şey gizleniyor, asla gösterilmiyor.
Düzeltmeler önce etiketlenmemiş bir 2.0.1 altında toplanmıştı. Yeniden tasarım API eklediği ve davranışı değiştirdiği için f6719aa o bölümü 2.1.0'a kattı. Ders, ilk sürümde hata olması değil. Ders şu: bir gizli adın yazılabileceği her biçimi tahmin etmeye çalışmak bir yere varmıyor ve daha ucuz cevap tahmin etmeyi bırakmaktı.
Tasarım kararları ve alternatifler
Yalnız düz ASCII anahtarları karşılaştır, diğer her değeri gizle. JSON'daki, formdaki, multipart body'deki ya da query'deki bir anahtar gizli adlarla yalnız A–Z, a–z, 0–9, _, -, . ve boşluktan oluşuyorsa karşılaştırılıyor. Başka bir alfabeden harfler, işaretler, format karakterleri, köşeli parantezler ya da escape'ler içeren her anahtarın değeri, anahtarın ne dediğine bakılmadan gizleniyor. Alternatif tabloydu; yukarıdaki geçmiş neden kaybettiğini gösteriyor. Bedeli rehberde yazılı: статус, user[id], passwordHint ve token_type de değerlerini kaybediyor.
Yapılandırılmış girdiyi parse et, parse edilemeyeni atla. redact(body:contentType:) parser'ı content type'a göre seçiyor: JSON, application/x-www-form-urlencoded, multipart form data ve düz metin. Paketin kendi JSON okuyucusu her değerin yerini kaydediyor ve sonucu yalnız JSONSerialization aynı ağacı okuduğunda kullanılıyor; anahtarlar escape'leri çözüldükten sonra karşılaştırılıyor ve çıktı geçerli JSON olarak kalıyor. UTF-8 olmayan, XML gibi başka türde olan, tam parse edilemeyen ya da maximumBodySize'ı aşan bir body [body omitted: N bytes, <type>] olarak yazılıyor. Yaygın bir sunucu parser'ının (PHP, Werkzeug, Rack, Go ya da busboy) alanlara farklı bölebileceği bir body de öyle, çünkü redactor'ın hiç görmediği bir alan yine bir değer taşıyabilir. Varsayılan sınır 256 KiB; changelog'a göre denenen en kötü biçimler body'nin her byte'ı için yaklaşık 40 byte bellek kullanıyor.
URL'leri de yapı olarak ele al. redact(url:) URL'yi URLComponents ile okuyor ve kinds ne olursa olsun kullanıcı adını, parolayı ve fragment'i atıyor. Query değerleri yalnız varsayılanda boş olan allowedQueryParameters listesindeki adlar için kalıyor; kimlik gibi görünen path segment'leri :id oluyor. URLDetail.hostOnly yalnız scheme, host ve port'u yazıyor.
Serbest metni ilk tetikleyicide kes. Bir log satırının grameri yok, bu yüzden redactor bir değerin nerede bittiğini bulmaya çalışmıyor. Serbest metinde bir değer yalnız allowedTextKeys listesindeki bir anahtardan sonra kalıyor (varsayılan olarak status, method, path, duration_ms ve count). Güvenli alfabenin dışındaki ilk karakter, bir escape, 256 karakterden uzun bir kelime, izin verilmeyen bir anahtar, bir markup etiketi ya da bir gizli ad, ardından gelen her şeyi gizliyor. README bunu açıkça söylüyor: fazla gizlemek tasarımın parçası.
Paketin geri kalanı tek bir kurala uyuyor: Apple'ın framework'lerini (Security, CryptoKit, CommonCrypto, LocalAuthentication) doğrudan çağır, yalnız onlarda eksik olanı yaz. Hatalar typed, örneğin throws(KeychainError) ve throws(CipherError). ptrace deny-attach ya da method swizzling yok, çünkü bunlar OS sürümleri arasında bozuluyor ve kolay atlatılıyor.
Kılık değiştirmeye dayanması gereken bir tasarımı test etmek
Önceki tasarımlarda bir sırrı sızdıran girdiler test olarak tutuluyor. 2.1.0 changelog'u ikinci incelemenin 51 girdisini, 386 adversarial probe'u ve yeniden tasarımın incelemesinden 453 probe'u sayıyor. Commit mesajları incelemeleri yirmi altıncıya kadar numaralandırıyor; probe'ları tek bir kaynakta, 7.866 kayıtlık Tests/iOSSecurityToolsTests/Resources/ReviewProbes.jsonl dosyasında toplanıyor. Property testleri kılık değiştirmiş gizli adlar ve izin verilmeyen rastgele anahtarlar üretiyor ve hiçbir değerin görünmediğini kontrol ediyor.
Yeniden tasarım, kılık değiştirmiş adların okunuşunu da değiştirdi. c986d38 bir kelimeyi gizli adlarla ASCII iskeletinden karşılaştırıyor: ASCII biçimi olmayan harfler dışarıda bırakılıyor, böylece piꞏn pin olarak okunuyor. Mesajı yan etkiyi de açıkça kabul ediyor: Türkçe yapın kelimesi pin olarak okunuyor ve ardından geleni gizliyor.
Kullanım
Paketi Swift Package Manager ile ekleyin (from: "2.1.0") ve iOSSecurityTools'u import edin. Aşağıdaki örnek README'den; README onu Examples/SecurityLab/SecurityLab/ReadmeSamples.swift dosyasından kopyalıyor, bu dosyayı hem örnek uygulama hem paketin testleri derliyor ve bir test iki çıktıyı da kontrol ediyor:
swift
1struct LoginRequest: Codable {2 var username: String3 @Redacted var password: String4}5 6func redactionSample() -> (request: String, log: String) {7 let request = LoginRequest(username: "alice", password: "hunter2")8 let printed = "\(request)" // LoginRequest(username: "alice", _password: <redacted>)9 10 let redactor = SensitiveDataRedactor()11 let log = redactor.redact("charged 4242 4242 4242 4242 to alice@example.com, token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.c2ln")12 // "charged •••• 4242 to a•••@example.com, token=[redacted]"13 return (printed, log)14}@Redacted bir property'yi açıklamaların dışında tutuyor. Redactor kartın son dört hanesini, e-posta adresinin ilk harfini ve alan adını bırakıyor, token'ı gizliyor. URL'ler için logging rehberi şu örneği veriyor; bunu da bir test kontrol ediyor:
swift
1redactor.redact(url: URL(string: "https://bob:pw@example.com/users/12345678/reset?code=x#y")!)2// https://example.com/users/:id/reset?code=[redacted]Sınırlar
Tehdit modeli bilerek dar. Redactor yanlışlıkla loglanan sırlar için yapıldı; bir sırrı bilerek kaçırmak için log satırı yazan birine karşı değil. README onu bir güvenlik ağı olarak tanımlıyor, garanti olarak değil. Serbest metinde, ayırıcısız yazılmış ve iki ya da daha fazla harfi benzer harflerle değiştirilmiş bir adın (pɑsswoʀd hunter2) bulunması garanti değil. Yapılandırılmış anahtar adları gösteriliyor; bu yüzden bir anahtarın içine yazılmış bir sır ({"password hunter2":…}) görünüyor.
2.1.0 üç bilinen sınırla geliyor ve hepsi issue olarak izleniyor. #22: kontrol karakteri içeren bir ad yanındaki üyeleri gizliyor. #23: token ve secret yalnız ayrı bir kelime olarak ya da anahtarın sonunda sayılıyor, bu yüzden tokenvalue değerini gösteriyor; böyle property'leri @Redacted ile işaretleyin. #24: yalnız kontrol karakterlerinden oluşan kısa bir string yanındaki her üyeyi gizliyor. İkisi gereğinden fazla gizliyor; #23 ise bir değeri gösteriyor.
Varsayılan-güvenli olmanın bir boyut bedeli de var. Redaction klasörü 2.0.0'daki 718 satırdan 33 dosyada 5.697 satıra çıktı; bu, paketin 8.488 satırlık Swift kaynağının yaklaşık üçte ikisi. Uygulama maximumBodySize'ı yükseltmedikçe 256 KiB'ı aşan body'ler atlanıyor; yükseltmenin de bellek bedeli var.
Redaction dışında, IntegrityInspector kanıt değil sinyal bildiriyor ve rehberi App Attest'i öneriyor; paket App Attest'i henüz sarmalamıyor, bir App Attest wrapper'ı yol haritasında. BiometricAuthenticator tvOS ve watchOS'ta yok. Araç zinciri Swift 6.2 ve Xcode 26 ya da üstünü istiyor. Redactor'a güvenen 2.0.0 kullanıcıları yükseltmeli.
Kaynak kod: github.com/muhittincamdali/iOSSecurityTools
