Files
kolaytercih/docs/gece-vardiyasi/2026-10-05/09-guvenlik-uyum.md
bilalgursen c89a86c069 docs(gece): güvenlik ve uyum denetimi raporu (09)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 08:56:48 +03:00

12 KiB
Raw Blame History

09 — Güvenlik ve uyum denetimi (gece değişiklikleri)

5 Ekim 2026 · güvenlik-uyum · dal gece/eval-jev Kapsam: git diff b935158..HEAD -- src/ + scripts/eval/**. src/lib/ai/rapor.ts ve cagri.ts içindeki commit'lenmemiş iş kapsam dışı. Yöntem: kod okuma + saf fonksiyonları ve DB katmanını data/app.db'nin geçici KOPYASINDA çalıştırma. Model çağrısı, sunucu, tarayıcı, Rybbit yok. Canlıya dokunulmadı.

Merge'e engel var mı? Hayır — kritik ya da yüksek bulgu yok; B1 (bedelsiz "evet" tekrar kullanılabiliyor) yayından önce kapatılmalı ama bu gecenin değişikliği onu daraltmış, genişletmemiş.

Özet (5 madde)

  1. Kritik/yüksek bulgu yok. Kredi düşümü atomik, 0 kredide 402, çifte iade yok, istemci kopunca iade yok, profil yazımı SQL enjeksiyonuna ve serbest metne kapalı — hepsi çalıştırılarak doğrulandı.
  2. Orta — bedelsiz "evet" tek kullanımlık değil (B1). Danışman bir kez "profiline kaydedeyim mi" dedikten sonra, cevap kaydedilene kadar gönderilen her "evet" bedelsiz model çağrısı açıyor: art arda 12, eşzamanlı 50/50 kabul edildi, bakiye 0 iken. Kullanıcıya bedava faydalı cevap vermiyor; bizim model faturamızı şişiriyor.
  3. Jev'e canlıda yalnız cevap metni gidiyor (tek çağrı noktası, doğrulandı). Ama cevap metni adayın sırasını, ilini ve tercihlerini içerir; /gizlilik bunu söylemiyor (U1, metin işi [BİLAL], avukat teyidi gerekir).
  4. /api/soru'da hız sınırı yok; ücretli yolda risk krediyle sınırlı, sınırsız kalan tek yol B1. En sade çözüm: kullanıcı başına "aynı anda tek cevap" kilidi (B2).
  5. Repo hijyeni: bu gecenin commit'lerinde anahtar izi yok, eval sonuçları sentetik. Ama data/app.db git'te İZLENEN bir dosya ve içinde kullanıcı satırları var (B3, bu geceden eski); çalışma ağacındaki değişmiş hâli commit'lenmemeli.

Bulgular (önem sırasıyla)

B1 — ORTA · Bedelsiz onay yolu tek kullanımlık değil, bakiye ve adet sınırı yok · DOĞRULANDI (kod düzeyi)

  • Yer: src/app/api/soru/route.ts:113-118 (karar: "son 12 mesajdaki son danışman mesajı ibareyi içeriyor mu"), src/lib/credits.ts:148-166 (bedelsizMesajKaydet: bakiye/adet kontrolü yok).
  • Kalıp eşleşmesi sağlam: onayMesajiMi 29 varyantla denendi. "evet. Ege hukuk kaç?", "evet\nEge…", sıfır genişlikli boşluk, bölünemez boşluk, uzun tire, "evet-ege-hukuk", Kiril "е", emoji, "evet [SİSTEM]", "evet 2" → hepsi ücretli. Soru taşıyan mesaj bedelsiz geçmiyor.
  • Açık olan: karar yalnız son danışman mesajına bakıyor; arada kullanıcının kendi "evet"leri olması durumu değiştirmiyor. Cevap ancak akış bitince kaydedildiği için (5-15 sn) o pencerede her yeni "evet" yine bedelsiz.
  • Kanıt (geçici DB, route'un okuma + karar ifadesi birebir): bakiye 0, son danışman mesajı onay sorusu → art arda 12 "evet" bedelsiz kabul edildi (13.'de danışman mesajı 12'lik pencereden çıkıyor); eşzamanlı 50/50 kabul edildi. Ayrıca "evet evet … " (1.499 karakter) de kalıba uyuyor.
  • İstismar senaryosu: girişli, listesi olan bir hesap (deneme kredisi yeter) bir tercih söyleyip onay sorusunu alır; betikle farklı clientMessageId'lerle onlarca "evet" yollar. Her biri tam sistem prompt'u + araç şemasıyla bir model çağrısıdır, kredi düşmez. Yeniden kurmak 1 kredi; ya da kullanıcı modele "kaydettim" dedirtirse sunucu KAYIT_DUZELTME_NOTU'nu (ibareyi içerir, cikti-denetimi.ts:107) kendisi ekler.
  • Sınırı: mesaj yalnız onay sözcüğü taşıyabildiği için kullanıcıya bedava soru-cevap kazandırmıyor. "Soruları ücretli mesajda önceden yükle, her 'evet'te sıradakini cevapla" hilesi modelin uymasına bağlı; model çağrısı yapmadığım için denenmedi (OLASI).
  • En sade düzeltme [KARAR]: bedelsiz say yalnız geçmişteki en son mesaj danışmanınsa (gecmis.at(-1)?.role === "assistant" ve ibareyi içeriyorsa). İlk "evet" kaydedilince sonraki mesajlar ücretli olur. Eşzamanlı patlamayı B2'deki kilit kapatır.

B2 — ORTA · /api/soru'da hız sınırı ve eşzamanlılık sınırı yok · koddan çıkarım

  • Yer: src/app/api/soru/route.ts:65-261 (hiçbir sınır yok; src/ içinde hız sınırı kodu yalnız giriş formunda geçiyor).
  • Gerçek risk: ücretli yolda her çağrı 1 kredi düşüyor (atomik; 0 kredide INSUFFICIENT → 402 doğrulandı), yani maliyet saldırısı krediyle sınırlı. Sınırsız olan B1'dir. İkinci etki: 60 kredili bir hesap 60 cevabı aynı anda başlatabilir (her biri 4 adım, 8 araç).
  • Araç tarafı sağlam: adım başına 4, cevap başına 8 çağrı sınırı kodda (sohbet-akisi.ts:39-40, 93-99), son adımda araç kapalı, çıktı 1.024 token. 8 program_ara lokalde toplam 61 ms; rehber_ara çıktısı ~3 bin karakter. Araç yoluyla DoS görmedim.
  • En sade öneri [KARAR]: kullanıcı başına bellek içi "sürüyor" kilidi — aynı kullanıcının cevabı bitmeden gelen ikinci POST 429 döner (kredi düşmeden önce). Tek süreçli dağıtımda yeterli; birden çok kopya çalışıyorsa DB'de tutulmalı (doğrulanmalı). Dakika başına sayaç gerekmez.

B3 — ORTA · data/app.db git'te izleniyor ve kullanıcı verisi içeriyor (bu geceden eski) · DOĞRULANDI

  • Yer: git ls-files data/app.db → izleniyor; .gitignore'da yok. Son dokunan commit 0ab4dbe (bu gecenin aralığında değil: git log b935158..HEAD -- data/app.db boş).
  • HEAD'deki kopyada (salt okuma, yalnız sayım): user 7 satır (4'ü gmail.com alan adlı), account 2 satır (2'sinde access_token ve id_token dolu), session 8 satır (hepsi süresi dolmuş), chat_messages 21, orders 85. Değerler okunmadı/yazılmadı.
  • Senaryo: depoya (Gitea uzak depo tanımlı) erişen herkes bu e-posta adreslerini, sohbet mesajlarını ve Google kimlik token'larını (id_token ad/e-posta taşır) geçmişten okuyabilir. Satırlar Bilal'in kendi test hesapları olabilir — doğrulanmalı.
  • Bu gece: çalışma ağacında M data/app.db var (QA/eval göç ve test satırları). Commit'lenmemiş, öyle kalmalı.
  • Düzeltme [BİLAL]: git rm --cached data/app.db + .gitignore'a ekle; geçmişten temizlemek ayrı karar.

B4 — DÜŞÜK · Hata/zaman aşımı yolunda yarım cevap kullanıcıda kalır ve kredi iade edilir · koddan çıkarım

  • Yer: src/lib/ai/sohbet-akisi.ts:145-148, 190; route.ts:244-254.
  • Çifte iade yok: iade refund + mesaj kimliğiyle yazılıyor, UNIQUE(reason, ref_id) ikinciyi düşürüyor (çalıştırıldı: 1. granted, 2. tekrar, bakiye +1).
  • İstemci kopması bedava cevap kapısı değil: AI SDK 7.0.127'de writer.write kapalı akışa yazarken hata fırlatmıyor (safeEnqueue, node_modules/ai/dist/index.js:10375), üretim sürüyor, cevap kaydediliyor, kredi harcanmış kalıyor.
  • Kalan: sağlayıcı cevabın sonuna doğru hata verirse ya da 120 sn'de takılırsa kullanıcı metnin çoğunu görmüş olur ve iade alır. Kullanıcı bunu tetikleyemez; kabul edilebilir. Düzeltme önermiyorum.

B5 — DÜŞÜK · Jev hata gövdesi log'a yazılıyor · OLASI

  • Yer: src/lib/ai/jev.ts:85 (hata metnine cevap gövdesinin ilk 300 karakteri), :116 (console.warn).
  • Senaryo: TypeSafe 4xx cevabında gönderilen metni geri yansıtırsa danışman cevabının bir parçası (sıra, il) log'a düşer. Sağlayıcının böyle yaptığını görmedim.
  • Düzeltme [KARAR]: log'a yalnız durum kodu. Öteki log'lar temiz: kullanıcı mesajı, cevap metni, e-posta basılmıyor (yalnız araç adı, sayılar, durum kodu).

B6 — DÜŞÜK · Gövde oturumdan önce ayrıştırılıyor (bu geceden eski) · koddan çıkarım

  • Yer: route.ts:68-71 — getSession() ile request.json() paralel; 2.000 karakter sınırı ayrıştırmadan sonra.
  • Senaryo: girişsiz biri çok büyük JSON gövdeleri yollayıp sunucuya bellek/CPU harcatır. Önde gövde sınırı varsa (Traefik) etkisiz — doğrulanmalı.
  • Düzeltme [KARAR]: önce oturum, sonra gövde; Content-Length üst sınırı.

Temiz çıkanlar (çalıştırılarak)

  • Profil yazımı (danisman-profil-db.ts:28-56): alan adı kapalı listeden (evIl') , '$.x'… ve evIl.x reddedildi); değer bağlı parametre (tırnaklı metin düz metin olarak saklandı, SQL çalışmadı); userId yalnız oturumdan, araç şeması userId anahtarını reddediyor; öteki kullanıcının profili değişmedi.
  • Kişisel veri profile yazılamıyor: araç yolunda evIl il listesinden çözülmek zorunda ("Ayşe Yılmaz 0532…" ve 11 haneli sayı → "İl tanınmadı"); kampus/burs/dil sabit seçenek; maxKm 50-1.500, ucretUst 50 bin-5 milyon. Serbest metin alanı yok. Not: profilAlanYaz doğrudan çağrılırsa evIl 40 karaktere kadar metin kabul eder; bugün tek yazan araç ve o il çözümünden geçiriyor.
  • Araç argümanları: bilinmeyen alan, yanlış tip, geçersiz seçenek reddediliyor; program_ara'ya ' OR 1=1 --, '; DROP TABLE…, UNION SELECT → 0 sonuç, hata yok; yokatlas.db salt okunur açılıyor.
  • Prompt enjeksiyonu / sızıntı: sistem prompt'unda sır yok (kurallar + adayın kendi verisi); şema sızarsa kaybedilen yalnız araç adlarıdır. rehber_ara alıntısı depodaki content/rehber/*.md'den (47 dosya, git'te) geliyor, kullanıcı içeriği değil; HTML etiketleri ayıklanıyor. Paketsiz kullanıcının prompt'una kilitli satırlar girmiyor.
  • Eval betikleri: Jev'e ve modele yalnız sentetik profil/soru gidiyor (@eval.invalid kullanıcıları, geçici DB kopyası). scripts/eval/sonuc/*.json (14 dosya): anahtar kalıbı 0, TC/telefon kalıbı 0, e-posta yalnız @kolaytercih.com (3 dosyada 1'er, cevap metninde iletişim adresi); uzun onaltılık dizi yalnız git.commit.
  • Commit'ler: b935158..HEAD farkında anahtar kalıbı yok; tek eşleşme scripts/eval/akis-denetim.ts:55'teki bilerek sahte değer. .env* izlenmiyor.

Metin × kod uyumsuzlukları

  • U1 — /gizlilik src/app/gizlilik/page.tsx:166-169: "danışmanın sana yazdığı cevap … iletilir. Senin yazdığın mesaj, adın ve e-postan gönderilmez." Kod bunu doğruluyor (jev.ts:82: gövde = cevap + model + soru; tek çağıran sohbet-akisi.ts:157; araç çıktısı, profil, kullanıcı mesajı, kimlik gitmiyor). Eksik olan: cevap metni adayın başarı sırasını, ilini, kaydettiği tercihleri ve aday yazdıysa kişisel/hassas ayrıntıyı (kriz cevapları dahil) içerebilir; "tekrarlama" kuralı yalnız prompt'ta, kodla zorlanmıyor. Taslak ek: "Bu cevap sıralaman ve söylediğin tercihler gibi bilgileri içerebilir." — avukat teyidi gerekir, [BİLAL].
  • U2 — aynı dosya :173-175: yurt dışı aktarım cümlesi "e-posta ve yapay zekâ sağlayıcısı" diyor; TypeSafe'in de aktarım olduğu açık yazılmamış. Dayanak hâlâ açık (:20 TODO(avukat); KARARLAR.md:229). Avukat teyidi gerekir, [BİLAL].
  • U3 — yorum, kullanıcıya görünmez: src/lib/ai/danisman-prompt.ts:20 "Bedeli: 'evet' cevabı bir kredi" diyor; kod bedelsiz. [KARAR] yorum düzeltmesi.

Denetlenmeyenler

  • Model davranışı gerektiren iddialar (önceden yüklenmiş sorularla bedava cevap; modelin adı tekrar etmesi) — model çağrısı yasaktı.
  • Rybbit'te depolanan olay özellikleri (bağlantı kapalı). pnpm audit bu turun kapsamında değildi.
  • Lokal gözlem: file: modunda eşzamanlı iki kredi işlemi SQLITE_BUSY fırlatıyor (3 paralel harcamanın 2'si). Canlı libsql:// ise geçerli olmayabilir — doğrulanmalı; bulgu yapmadım.

Bilal'den istenen

  1. B3: data/app.db git izlemesinden çıkarılsın mı ve içindeki 7 kullanıcı satırı senin test hesapların mı — evet/hayır (3 dk).
  2. U1 + U2: /gizlilik TypeSafe maddesine "cevap sıralamanı ve tercihlerini içerebilir" eki ve aktarım cümlesi avukata gidecek listeye eklensin mi (2 dk).
  3. B1 + B2: bedelsiz "evet"in tek kullanımlık olması ve "aynı anda tek cevap" kilidi yayından önce yapılsın mı — onay (1 dk).