docs/SECURITY_REVIEW_2026-10-13.md
Rama main · réplica de lectura del repositorio original.
# Güvenlik İncelemesi — 2026-10-13
Kapsam: `main` @ `7798270` (PR #105 dahil): web uygulaması ve API rotaları,
ws-gateway, plugin worker sandbox'ı, etkinlikler ve eklenti etkileri
(zamanlayıcı, depo, uygulama mesajı), botlar ve webhook'lar, pazar yeri
kurulumu ve çerçeve (aşama 1–3), resmi hub, Tauri masaüstü, Docker / nginx /
coturn / LiveKit, `install.sh`, `lfctl` ve CI.
Önceki incelemeden (2026-10-03, [SECURITY_REVIEW_2026-10.md](SECURITY_REVIEW_2026-10.md))
bu yana gelenler özellikle incelendi:
- Bot API v2 ve olay akışı;
- pazar yeri sandbox'ı ve eklenti kilidini açma aşamaları 1–3;
- CAPTCHA ve e-posta doğrulama;
- sohbet anketleri;
- hesap dili;
- kopma dayanıklılığı (P0).
**Yöntem:**
- **Altı salt-okunur inceleyici:**
- kimlik doğrulama;
- yetkilendirme ve topluluk;
- botlar;
- etkinlikler ve gerçek zamanlı akış;
- pazar yeri ve sandbox;
- yükleme, altyapı, masaüstü, CI ve hub.
- Hepsi `security-review` becerisinin kuralıyla çalıştı. Saldırgan girdisi
uçtan uca izlendi; yalnızca **yüksek güvenli** bulgular raporlandı.
- Emin olunamayanlar "doğrulanmalı" diye ayrıldı.
- Önceki raporun §12'de düzeltildi denen maddeleri yeniden kontrol edildi.
Hiçbiri geri gelmemiş.
- Her bulgu düzeltilirken koda karşı yeniden doğrulandı.
## 1. Karar
**Kritik ya da yüksek önemde açık yok.** Bulgular çoğunlukla:
- servis dışı bırakma (DoS);
- yetki sınırlarında ince boşluklar;
- sertleştirme.
İnceleme bir de **işlevsel bir hata** çıkardı: giriş yapan kullanıcı bir
saat sonra oturumdan düşüyordu (§3). O da düzeltildi.
| Önem | Sayı | Bulgular |
|---|---|---|
| Kritik | 0 | — |
| Yüksek | 0 | — |
| Orta | 7 | BOT-001 (= AUTHZ-007), BOT-002, BOT-003, PLUG-001, PLUG-002, FILE-001, AUTHZ-008 |
| Düşük | 11 | BOT-004, PLUG-003, PLUG-004, AUTHZ-009, AUTHZ-010, ACT-001, ACT-002, AUTH-001 (= INFRA-V-02), DESK-V-01, FILE-V-01, HUB-V-01 |
## 2. Bulgular
| ID | Önem | Bulgu | Düzeltme |
|---|---|---|---|
| BOT-001 / AUTHZ-007 | Orta | "Topluluğu yönet" yetkisi olup "kanalları yönet" yetkisi olmayan biri, gizli (rolle kapalı) bir kanala erişimi olan botun token'ını yenileyip o kanalı okuyabiliyor ve oraya yazabiliyor | #112 |
| BOT-002 | Orta | Sızan bir token yenilense ya da iptal edilse bile saldırganın kaydettiği olay adresi çalışmaya devam ediyor; senkron etkileşim cevabıyla bot adına yazabiliyor | #112 |
| BOT-003 | Orta (resmi hub) | Bot olay teslim kuyruğunda bot başına adalet yok; yavaş ama başarılı cevap veren adresler diğer kiracıların teslimlerini düşürtüyor | #112 |
| PLUG-001 | Orta | İnceleme anında paket sınırsız açılıyor (gzip bombası); web süreci bellek yüzünden düşebiliyor | #112 |
| PLUG-002 | Orta | Herkese açık eklenti dosya rotası her istekte paketi yeniden doğruluyor ve dosyayı baştan sona belleğe okuyor; hız sınırı yok | #112 |
| FILE-001 | Orta | Misafir hesaplar (IP başına saatte 10) her biri ~14 MiB resim saklayabiliyor; kurulum geneli üst sınır yok; disk dolabiliyor | #112 |
| AUTHZ-008 | Orta | Üyeler her mesaja ~1 MB'a kadar keyfi `metadata` ekleyebiliyor (depo, bant genişliği, bellek DoS) | #112 |
| BOT-004 | Düşük | Olay adresi kaydında çözümleyici hatası iç IP ve ana bilgisayar adlarını yansıtıyor | #112 |
| PLUG-003 | Düşük | Tar ön taraması GNU tar'dan farklı sınırlar okuyabiliyor (sağlama toplamı yok, gevşek sayı ayrıştırma) | #112 |
| PLUG-004 | Düşük | "Çerçevenin ağı yok" iddiası CSP ile sağlanamıyor (WebRTC kapsam dışı) | #112 |
| AUTHZ-009 | Düşük | Engellemeler canlı sohbette uygulanmıyor (bildirimler dahil) | #112 |
| AUTHZ-010 | Düşük | Denetim kaydı davet kodlarını ve gizli kanal ayrıntılarını denetim kaydı yetkisi olanlara gösteriyor | #112 |
| ACT-001 | Düşük | Kullanılmayan SSE akışı, iptal edilmiş oturumu ilk 30 sn kabul ediyor | #112 |
| ACT-002 | Düşük | Bir eylem, vadesi gelmiş bir zamanlayıcının önüne geçebiliyor | #112 |
| AUTH-001 / INFRA-V-02 | Düşük | Geliştirme stack'i herkesin bildiği oturum sırrıyla tüm arayüzlerde açılıyor; uygulama yer tutucu sırları kabul ediyor | #112 |
| DESK-V-01 | Düşük | Masaüstü pencere, bağlanılan kurulumun dışına (her adrese) götürülebiliyor | #112 |
| FILE-V-01 | Düşük | GIF doğrulaması yalnız mantıksal ekran boyutuna bakıyor; büyük bir kare tarayıcıyı kilitleyebilir | #112 |
| HUB-V-01 | Düşük | Eklenti dosyaları `Vary`'yi yok sayan bir CDN'de (Cloudflare) uygulamanın kendi kökeninde önbelleğe girebilir | #112 |
**"Doğrulanmalı" listesinden düzeltilenler:**
- **Yetkilendirme:**
- Rol silme hatası gizli kanalların adını veriyordu.
- `@everyone`'ın sırası değiştirilebiliyordu.
- Sohbet aboneliği mesaj geçmişi yetkisi istemiyordu.
- Ortak sunucu olmadan DM açılabiliyordu; bilinmeyen kullanıcı 500 dönüyordu.
- Yazıyor göstergesi engellemelere ve gizliliğe bakmıyordu.
- Susturulmuş biri moderasyon yapabiliyordu.
- **Etkinlikler:**
- Susturulmuş biri etkinliğe katılabiliyordu.
- Kaldırılan uygulamanın oturumları sürüyordu.
- Uygulama mesajı sınırı oturum başınaydı.
- Zamanlayıcı birikmesi artış yaratıyordu.
- Panel takılı vadeler yüzünden sürekli yeniden okuyordu.
- Gizli kanala uygulama mesajı gönderilebiliyordu.
- **Botlar:** Yeniden denemeler yetkiyi yeniden kontrol etmiyordu; DNS iş parçacığı havuzu tıkanabiliyordu.
- **Eklentiler:**
- Resmî eklenti adlarıyla pazar yeri paketi kurulabiliyordu.
- İnceleme onayı sürüme bağlı değildi.
- Manifest okuma sınırı sonradan uygulanıyordu.
- **Kimlik doğrulama:** Google giriş durumu çerezi `__Host-` önekini almadı; profil adı kontrol karakterlerini kabul ediyordu.
- **Masaüstü:** Giriş devri `'*'` hedefine mesaj gönderiyordu.
## 3. İşlevsel hata: oturum bir saatte bitiyordu
Oturum çerezi bir saat geçerliydi ve geçerli bir çerezi hiçbir şey
yenilemiyordu. `POST /api/auth/guest` yalnızca çerez zaten yokken
çağrılıyordu.
Sonuç olarak giriş yapan kullanıcı, ne kadar etkin olursa olsun, bir saat
sonra oturumdan düşüyordu. Misafir de kimliğini kaybediyordu.
Düzeltme (#108): **kayan oturum.**
- Çerez istek gelmeden 7 gün geçerli.
- Her API isteği (iptal kontrolünden sonra) ve her sayfa yüklemesi,
ömrünün yarısı geçmiş geçerli bir çerezi aynı oturum kimliği ve
`auth_time` ile yeniden imzalıyor.
- 30 günlük kesin ömür ve sunucu tarafı oturum iptali değişmedi.
## 4. Bilerek bırakılanlar
- **Bilinçli davranış:**
- **Bot token'ları veren kişinin yetki kaybından sonra da geçerli kalıyor.**
Discord'da da böyle; belgelendi.
- **`member_leave.reason` (atma/ban), üye okuma yetkisi olan botlara gidiyor.**
Belgelendi.
- **Dağıtım ayarına bağlı:**
- **`LOBBYFORGE_TRUSTED_PROXY=cloudflare`:** Cloudflare arkasında değilken
istemci kendi hız sınırı anahtarını seçebiliyor. Doctor bunu anlatıyor
(önceki AUTH-V-01).
- **Resmi hub'ın üst alan adının güvenilmeyen kardeş alt alan adları varsa**
`lf_guest` çerezine de `__Host-` öneki gerekir. Bu, herkesin oturumunu
kapatan bir değişiklik olduğu için ayrı planlanacak.
- **IPv6:** Varsayılan Docker'da IPv6 istemcileri köprü ağ geçidi adresiyle
görünebilir ve hız sınırlarını paylaşır. Doctor uyarısı ve dağıtım belgesi
eklendi (#112); kalıcı çözüm, uç ağda IPv6'yı açmak.
- **Önceki incelemeden açık kalanlar:**
- plugin worker'ın aynı UID ile çalışması (ADR-001);
- `style-src 'unsafe-inline'`;
- `braces` güvenlik bildirimi. Yalnız ESLint zincirinde, production imajında
yok ve yaması yok.
## 5. Kontrol edilen ve sağlam bulunanlar (özet)
Her inceleyicinin "sağlam" listesi ayrıntılıdır; öne çıkanlar:
- **Oturum çerezi ve CSRF:**
- HMAC, sabit zamanlı karşılaştırma.
- Her girişte yeni oturum kimliği.
- İptal kontrolü API'de, sayfalarda ve gateway'de.
- Her değiştirici istekte köken denetimi.
- **Şifreler, sıfırlama ve doğrulama:**
- Şifreler scrypt ile saklanıyor.
- Hesap kilidi atomik.
- Sıfırlama ve doğrulama belirteçleri 256 bit; tüketme tek bir koşullu UPDATE ile yapılıyor.
- OAuth yönlendirmesi temizleniyor.
- **Yetkilendirme:**
- Rol yükseltme kuralları ve moderasyon hiyerarşisi uygulanıyor.
- Sunucular arası IDOR yok.
- İstemci sunucuya ait `metadata` anahtarlarını taklit edemiyor.
- `dangerouslySetInnerHTML` kullanılmıyor.
- **Botlar:**
- Token 256 bit, karma ile saklanıyor.
- Kanal erişimi tek kuralda.
- Gateway içeriği veritabanından yeniden yüklüyor.
- SSRF engelleri (IP sabitleme, tüm özel aralıklar, yönlendirme yok) yerinde.
- İmza HMAC; webhook token'ları karma ile saklanıyor.
- **Etkinlikler:**
- İstemci sistem eylemi ve etki gönderemiyor.
- Etkiler izin ve sınırla doğrulanıyor.
- Depo anahtar alanı sunucu tarafından belirleniyor.
- Gizli durum her yolda izleyici başına projekte ediliyor.
- LiveKit token'ı en dar yetkiyle veriliyor.
- **Pazar yeri:**
- Onay istenenle birebir eşleşmeli.
- Tarayıcı çerçevesi kendi kökeni dışına çıkamıyor (kaçış yok).
- Worker RPC'si sabit zamanlı token ve özet (digest) ile korunuyor.
- Dış arayüzün kökeni uygulamanınkinden farklı olmak zorunda.
- **Altyapı:**
- Production compose iç servisleri yayınlamıyor.
- Güncelleme Ed25519 imzalı; sürüm düşürme engelli.
- Yedekler 0600.
- CI'da aksiyonlar SHA ile sabitli; enjeksiyon yok.
- Tauri IPC uzak sayfalardan erişilemiyor.
## 6. Düzeltme durumu
Bulguların hepsi düzeltildi ve `main`'e birleşti. Her alanın düzeltmesi kendi
commit'inde; testleri ve ayrıntıları özgün PR'larda duruyor (#107, #109, #110,
#111 ve `fix/sec-infra`). Tek bir CI turundan geçmeleri için #112'de
birleştirildiler.
| PR | Kapsam | Durum |
|---|---|---|
| #106 | Bağımlılıklar: Next 16.4, ioredis 6, TypeScript 6; `middleware.ts` → `proxy.ts`. Next 16.4'ün katı rota denetimi gerçek bir hata buldu: `/admin` ve bilinmeyen alt adresler doğrudan açılınca 404 veriyordu. ioredis 6'nın üstel yeniden bağlanma beklemesi, Redis kapalıyken komutları ~70 sn bekletiyordu; eski bekleme geri kondu | birleşti |
| #108 | Kayan oturum (§3), Google giriş durum çerezi (`__Host-`), profil adında kontrol karakterleri, Hushle `set-explainer` niyet anahtarı, ses yeniden bağlanma sınırı | birleşti |
| #112 | Alan 2–6'nın düzeltmeleri: pazar yeri ve sandbox, etkinlikler ve eklenti etkileri, botlar ve webhook'lar, yetkilendirme ve gizlilik, yükleme, altyapı ve masaüstü | birleşti (20/20 CI) |
**Yükseltme notu:** Yayımlanmış bir yer tutucu oturum sırrıyla çalışan kurulum
artık başlamaz (`requireSessionSecret`). Gerçek bir sır gerekir; sırrı
değiştirmek herkesin oturumunu kapatır.