# Güvenlik İncelemesi ve Ürün Kıyaslaması — 2026-10-03
> **Güncelleme (aynı gün): 17 bulgunun hepsi düzeltildi.** Ayrıntılar
> §12'de. §1–§11 incelemenin ilk hâlidir ve kayıt olarak korunuyor.
>
> **Sonraki inceleme:** [SECURITY_REVIEW_2026-10-13.md](SECURITY_REVIEW_2026-10-13.md).
Kapsam: `main` @ `38945b6` (PR #41 dahil), kod tabanının tamamı: web
uygulaması ve 85 API rotası, ws-gateway, plugin worker sandbox'ı,
etkinlik eklentileri, botlar, registry/dizin, Tauri masaüstü, Docker /
nginx / coturn / LiveKit, `install.sh`, `lfctl` güncelleyici ve CI.
Yöntem: altı salt-okunur inceleyici alanlara bölündü (kimlik doğrulama,
yetkilendirme/IDOR, etkinlik-eklenti-bot-gateway, yükleme ve içerik
gösterimi, altyapı-CI-güncelleme, registry-dizin-masaüstü). Hepsi
`security-review` becerisinin kuralına göre çalıştı: yalnızca saldırgan
girdisi uçtan uca izlenmiş, **yüksek güvenli** bulgular raporlandı.
Raporlanan her bulgu ayrıca koda karşı tek tek doğrulandı (dosya:satır
aşağıda). Kod değişikliği yapılmadı. Rakip kıyaslaması ve özellik
önerileri ayrı bir analiz ajanının web araştırmasından geliyor (§8–§10).
## 1. Karar
**Kritik bir uygulama açığı yok; kurulumla gelen bir altyapı açığı
yüksek önemde ve hemen kapatılmalı.** Eylül incelemesindeki
düzeltmelerin (S1–S11) hiçbiri geri gelmemiş, ama üçü yan yoldan
kısmen aşılıyor (S7 → AUTH-001, S9 → INFRA-002, S11 → PLUG-001).
En önemli tek bulgu **INFRA-001**: `install.sh` ile yapılan her Linux
kurulumunda coturn kendi ayar dosyasını okuyamıyor ve **kimlik
doğrulamasız, açık bir TURN relay'i** olarak çalışıyor. Ses yine
çalıştığı ve healthcheck geçtiği için hiçbir şey bunu göstermiyor.
Herkese açık bir VPS kurulumundan önce bu kapatılmalı.
| Önem | Sayı | Bulgular |
|---|---|---|
| Kritik | 0 | — |
| Yüksek | 1 | INFRA-001 |
| Orta | 7 | AUTH-001, AUTHZ-001, AUTHZ-002, AUTHZ-003, PLUG-001, FILE-001, HUB-001 |
| Düşük | 9 | AUTH-002, AUTHZ-004, AUTHZ-005, AUTHZ-006, PLUG-002, HUB-002, HUB-003, FILE-002, INFRA-002 |
## 2. Bulgu özeti
| ID | Önem | Bulgu | Ana konum |
|---|---|---|---|
| INFRA-001 | **Yüksek** | Her `install.sh` kurulumunda coturn kimlik doğrulamasız açık relay | `install.sh:23`, `scripts/render-configs.sh:68-71`, `docker-compose.prod.yml:337` |
| AUTH-001 | Orta | Şifre değişikliğinden önce alınmış masaüstü giriş kodu sonra da oturum açıyor | `api/auth/desktop-session/complete/route.ts:74-94` |
| AUTHZ-001 | Orta | Bir kanalı tek başına kapatan rol silinince kanal herkese açılıyor | `packages/db/src/queries/roles.ts:324-333`, `channelVisibility.ts:87` |
| AUTHZ-002 | Orta | Ayrılıp geri katılmak timeout ve sunucu susturmasını sıfırlıyor; ayrılan kişi banlanamıyor | `members/[userId]/route.ts:80-95`, `lib/member-authorization.ts:123-128` |
| AUTHZ-003 | Orta | MANAGE_MESSAGES sahibi herkesin (sahibin de) mesaj **metnini** değiştirebiliyor | `messages/[messageId]/route.ts:130-138,207-209` |
| PLUG-001 | Orta | Denetim günlüğü Vampire Village rollerini ve anonim ankette kimin neye oy verdiğini sızdırıyor | `activities/[sessionId]/actions/route.ts:434-441` |
| FILE-001 | Orta | Tüm üyelerin avatar/banner data URL'leri her `/lobby` yüklemesine gömülüyor (sayfa şişirme DoS) | `memberships.ts:362-392`, `app/lobby/page.tsx:433` |
| HUB-001 | Orta | Her kurulumun dizin `instanceId`'si aynı sabit; ilk kaydeden herkes adına kapıyor (bugün başka bir hata yüzünden gizli) | `instanceSettings.ts:6,518,551`, `.well-known/lobbyforge-verification/route.ts:54` |
| AUTH-002 | Düşük | İptal edilen oturum sunucu tarafında oluşturulan sayfalarda (lobby, admin) ve tek bir API rotasında 60 dk'ya kadar çalışıyor | `app/lobby/page.tsx:605-606`, `lib/admin-auth.ts:28-40`, `api/admin/bandwidth/route.ts:27-29` |
| AUTHZ-004 | Düşük | Davet kullanımı "ilk katılımda onay" erişim politikasını uygulamıyor | `api/invites/[code]/redeem/route.ts:51`, `invites.ts:240-357` |
| AUTHZ-005 | Düşük | "Profil görünürlüğü" ayarı kaydediliyor ama hiç uygulanmıyor | `api/settings/me/route.ts:26`, `app/lobby/page.tsx:433` |
| AUTHZ-006 | Düşük | Susturulan veya timeout alan üye ekran paylaşımı sesiyle konuşabiliyor | `lib/voice-moderation.ts:49-58` |
| PLUG-002 | Düşük | Atılmış veya banlanmış host kendi etkinliğini bitirebiliyor (üyelik kontrolü yok) | `activities/[sessionId]/end/route.ts:57-84` |
| HUB-002 | Düşük | Listelenmiş dizin kaydı, yalnızca sahibin hub oturumuyla yeniden incelenmeden yeniden adlandırılabiliyor | `registryInstances.ts:176-196`, `api/directory/register/route.ts:154-203` |
| HUB-003 | Düşük | Bayatlamış (alan adı el değiştirmiş olabilecek) kayıtlar detay ve çıkış sayfasından hâlâ erişilebilir | `discover/[instanceId]/page.tsx:64`, `discover/go/page.tsx:49,127` |
| FILE-002 | Düşük | Dizin doğrulama hatası iç DNS adlarını, Docker IP'lerini ve port durumunu yansıtıyor | `api/directory/register/route.ts:76-82`, `change-domain/route.ts:145-150` |
| INFRA-002 | Düşük | Yerel derlenen imaja TURN gizli anahtarı giriyor; marketplace eklentisi okuyabilir | `.dockerignore`, `Dockerfile:6` |
## 3. Bulgular
### INFRA-001 — Her `install.sh` kurulumunda coturn açık relay (Yüksek)
- **Konum:** `install.sh:23` (`umask 077`), `scripts/render-configs.sh:68-71`
(`sed … > "$target"`), `install.sh:393` (`mv`), `infra/docker/docker-compose.prod.yml:337`
(`../turn/turnserver.conf:/etc/coturn/turnserver.conf:ro`), `infra/turn/cert-watcher.sh`
(`exec turnserver -c /etc/coturn/turnserver.conf`).
- **Zincir:**
1. Kurulum betiği `umask 077` ile çalışıyor; render edilen `turnserver.conf`
0600 ve kurulumu yapan kullanıcıya (genelde root) ait oluyor. `mv` izinleri koruyor.
2. Resmî coturn imajı `USER nobody:nogroup` ile çalışıyor (coturn
`docker/coturn/debian/Dockerfile`); compose `user:` vermiyor. Linux bind
mount'ta dosya izinleri aynen geçtiği için `nobody` dosyayı okuyamıyor.
3. coturn ayar dosyasını bulamayınca "default and command-line settings"
ile açılıyor. Belgesine göre hiçbir kimlik doğrulama seçeneği yoksa
varsayılan `--no-auth`: "any user is allowed".
4. Şablondaki her koruma düşüyor: paylaşılan gizli anahtar, tüm
`denied-peer-ip` aralıkları, kotalar ve relay port aralığı. coturn'ün
yerleşik varsayılanı yalnızca loopback / link-local / ULA'yı engelliyor;
10/8, 172.16/12, 192.168/16 açık.
- **Saldırgan:** internetteki herkes, hesap gerekmez (3478 kurulumda açılıyor).
- **Etki:** operatörün IP'si üzerinden anonim proxy (kötüye kullanım şikâyetleri
operatöre gelir). `network_mode: host` olduğu için TCP relay ile Docker
köprü ağına (web:3000, ws-gateway, LiveKit 7880) ve sağlayıcının özel ağına
erişim. `web:3000`'e nginx'i atlayarak `X-Forwarded-For` uydurarak giriş
denemesi yapılabildiği için IP tabanlı hız sınırları da aşılıyor.
- **Neden fark edilmedi:** ses bu durumda da çalışıyor, healthcheck yalnızca
3478'e TCP bağlantısı deniyor, `docs/VOICE_TURN.md` duman testi açık
sunucuda da geçiyor. Eylül provaları Windows Docker Desktop'ta yapıldı;
orada bind mount izinleri uygulanmadığı için dosya okunabiliyordu.
- **Düzeltme:**
- Render'dan sonra `chown 65534:65534 infra/turn/turnserver.conf && chmod 0400`
(ya da grup `nogroup`, 0640).
- `cert-watcher.sh` başında `[ -r /etc/coturn/turnserver.conf ] || { echo …; exit 1; }`
ile fail-closed başlat.
- CI'da `umask 077` ile render edip sabitlenmiş imajı varsayılan kullanıcıyla
başlatan ve kimliksiz bir Allocate'in **401** aldığını doğrulayan bir test.
- Aynı değişiklikte: `nobody` Let's Encrypt özel anahtarını da okuyamıyor,
yani duyurulan `turns:…:5349` hiç çalışmıyor. Sertifika erişimini de düzelt.
- Ek: `no-tcp-relay`, sunucunun kendi genel IP'si ve `192.0.0.0/24`,
`198.18.0.0/15`, `240.0.0.0/4` için `denied-peer-ip`.
### AUTH-001 — Masaüstü giriş kodu şifre değişikliğinden sağ çıkıyor (Orta)
- **Konum:** `apps/web/app/api/auth/desktop-session/route.ts:63-68` (kayıt yalnızca
`{userId, state, used}`), `…/complete/route.ts:74-94` (yalnızca kullanıcı var mı
ve silinmemiş mi bakılıyor), `api/auth/password/route.ts:63` (yalnızca
`revokeOtherSessions`).
- **Saldırgan:** kurbanın şifresini bilen biri (oltalama, yeniden kullanılan şifre).
- **Saldırı:** saldırgan ~4 dakikada bir `POST /api/auth/desktop-session` ile
yeni bir `{code,state}` alır (10/15 dk sınırının içinde). Kurban fark edip
şifresini değiştirir; kayıtlı oturumlar iptal edilir. Saldırgan elindeki son
kodu 5 dakika içinde `/complete`'e gönderir, **yeni ve iptal listesinde
olmayan** bir oturum alır ve bunu `POST /api/auth/guest` ile süresiz uzatır.
- **Etki:** Eylül'deki S7 düzeltmesinin amacı ("şifre değişikliği saldırganı
dışarı atar") bu yoldan boşa çıkıyor.
- **Düzeltme:** kodu üretirken kimlik bilgisinin parmak izini (ör.
`passwordHash`'in hash'i ya da bir `credentialVersion`) kayda yaz, tamamlanırken
eşleşmiyorsa 401 ver. Ayrıca kullanıcı başına açık kodları indeksle ve şifre
rotasında sil. Test: kod al → şifre değiştir → complete 401.
### AUTHZ-001 — Rol silmek gizli kanalı herkese açıyor (Orta)
- **Konum:** `api/servers/[id]/roles/[roleId]/route.ts:313-323` → `packages/db/src/queries/roles.ts:324-333`;
`0028_channel_role_overrides.sql:7` (`role_id … ON DELETE CASCADE`);
`channelVisibility.ts:87` (`if (overrides.length === 0) return true;`).
- **Saldırgan:** `MANAGE_ROLES` sahibi, `MANAGE_CHANNELS` yetkisi olmayan ve
kapı rolünün üstünde olan bir moderatör. Sahip de kazara tetikleyebilir.
- **Saldırı:** `#staff` yalnızca "Staff" rolüne açık. Moderatör `DELETE
/roles/{staffRoleId}` gönderir; override satırları cascade ile silinir; "override
yok = herkese açık" kuralı yüzünden kanal, misafirler dahil herkese görünür,
geçmişi okunur, sesli odasına girilir.
- **Etki:** özel kanal içeriği tüm sunucuya sızar; kanal görünürlüğünü
değiştirmek için gereken `MANAGE_CHANNELS` aşılmış olur.
- **Düzeltme:** `deleteRole` içinde, aynı transaction'da bu rolün **son**
override olduğu kanalları bul; 409 ile reddet (kanalları listele) ya da
`MANAGE_CHANNELS` iste. Route testi ekle.
### AUTHZ-002 — Ayrılıp geri katılmak timeout ve susturmayı sıfırlıyor (Orta)
- **Konum:** timeout ve sunucu susturması yalnızca üyelik satırında
(`schema.ts:155,162`); kendi isteğiyle ayrılmak satırı siliyor
(`members/[userId]/route.ts:80-95`, `memberships.ts:275-287`); yeniden katılma
temiz satır yaratıyor (`invites.ts:327-334`, `app/lobby/page.tsx:630-646`);
ban hedefin üye olmasını istiyor (`lib/member-authorization.ts:123-128`).
- **Saldırgan:** timeout ya da sunucu susturması almış herhangi bir üye
(`CREATE_INVITE` varsayılan olarak `@everyone`'da).
- **Saldırı:** `POST /invites` (timeout kontrol edilmiyor) → `DELETE
/members/{self}` → `POST /invites/{code}/redeem`. Açık kayıtlı kurulumda son adım
yalnızca `/lobby`'yi açmak. Ayrıldığı sürece moderatör onu banlayamaz
(`POST /bans` → 404 "not a member").
- **Etki:** 28 günlük timeout ya da susturma birkaç saniyede kalkıyor.
- **Düzeltme:** moderasyon durumunu üyelikten bağımsız `(serverId, userId)`
anahtarlı bir tabloda tut (ya da `redeemInvite` ve `ensureServerMembership*`
içinde kontrol et). Üye olmayan kullanıcıyı banlamaya izin ver; hiyerarşi
kontrolünü yalnızca hedef hâlâ üyeyse uygula.
### AUTHZ-003 — Moderatör başkasının mesaj metnini yeniden yazabiliyor (Orta)
- **Konum:** `api/servers/[id]/channels/[channelId]/messages/[messageId]/route.ts:130-138`
(`canMutateMessage`: yazar **ya da** `MANAGE_MESSAGES`), `:207-209` (içerik
düzenlemesi bu kapıdan geçiyor).
- **Saldırı:** herhangi bir rütbedeki `MANAGE_MESSAGES` sahibi `PATCH
…/messages/{sahibinMesajı} {"content":"Adminliğimi X'e veriyorum"}` → 200.
Mesaj hâlâ asıl yazara ait görünüyor, yalnızca "düzenlendi" işareti alıyor.
Arayüz bunu sunmuyor (`LobbyLiveRoster.tsx:485` `isOwn ?`); bu bir sunucu açığı.
- **Etki:** moderatör, kendinden üst rütbedekiler ve sahip dahil herkesin ağzına
söz koyabilir. Gerçek düzenleyeni yalnızca denetim günlüğü gösterir.
- **Düzeltme:** içerik düzenlemesini yalnızca `isAuthor` için aç;
`MANAGE_MESSAGES` silme ve sabitleme için kalsın. Yazar olmayana 403 testi.
### PLUG-001 — Denetim günlüğü gizli oyun bilgisini sızdırıyor (Orta)
- **Konum:** `api/servers/[id]/activities/[sessionId]/actions/route.ts:434-441` her
aksiyon için `{actorUserId, metadata:{pluginId, actionType}}` yazıyor;
`api/servers/[id]/audit-logs` bunu `VIEW_AUDIT_LOG` sahiplerine veriyor.
- **Saldırgan:** `VIEW_AUDIT_LOG` sahibi üye (sahip, varsayılan `@admin`; resmî hub'da
sunucu açan herkes) — aynı zamanda oyunun oyuncusu.
- **Saldırı:**
- **Vampire Village:** oyun sırasında günlükte `actionType:'pack-chat'` bir
vampiri, `'night-shield'` hayatta kalanı, `'night-target'` gece rolü olan
birini gösteriyor (köylüler gece aksiyonu göndermez). Reddedilen aksiyonlar
da yazılıyor (log, CAS'tan sonra `stateChanged`'e bakmadan çalışıyor).
- **Anket:** her oy bir revizyon. Paneldeki sayı değişimleri ile sırayla gelen
`vote` satırları eşlenince oy veren → seçenek çıkıyor.
- **Etki:** `projectActivityState`'in koruduğu gizli roller ve S11 ile kapatılan
anket anonimliği pasif olarak sızıyor. `docs/VAMPIRE_VILLAGE.md`'deki "tek yan
kanal bus zamanlaması, kim veya ne asla" ifadesi doğru değil.
- **Düzeltme:** `member` rolündeki oyun aksiyonları için satır yazma; host ve
moderasyon aksiyonlarını (start/end/kick/configure) tut. Ya da
`actionPolicies`'e `audit: false` ekle. Durum değiştirmeyen aksiyonları hiç
yazma. VV `pack-chat`/`night-*` ve Poll `vote` için "aktör+tip içeren satır yok"
testleri.
### FILE-001 — Lobby sayfası tüm üyelerin resimlerini gömüyor (Orta)
- **Konum:** `packages/db/src/queries/memberships.ts:362-392` (`listMemberSummariesForServer`,
LIMIT yok, `avatar_url` + `banner_url` seçiliyor) → `app/lobby/page.tsx:433` →
istemci bileşenleri; `LobbyMembersClient.tsx:405` avatarı SSR'da `
`
olarak bir kez daha basıyor. Sınırlar: avatar ~6 MB, banner ~8 MB data URL
(`users/me/avatar/route.ts:13`, `banner/route.ts:13`), kullanıcı başına 24 MB.
- **Saldırgan:** sunucunun herhangi bir üyesi (açık katılımda misafir dahil).
- **Saldırı:** her saldırgan hesap ~6 MB avatar ve ~8 MB banner yükler (ikisi de
doğrulamadan geçer). Artık sunucunun her üyesinin her `/lobby` açılışı hesap
başına ~14 MB Postgres okuması ve RSC serileştirmesi demek. Beş hesap her
sayfa görüntülemesine 100 MB+ ekler.
- **Etki:** lobby kullanılmaz hâle gelir; Node belleği ve CPU'su her istekte dev
stringler kurar; `web` konteyneri OOM olursa tüm kurulum düşer.
- **Düzeltme:** resimleri gömmeyi bırak. Byte'ları sakla ve
`GET /api/users/{id}/avatar?v=` gibi bir rotadan sun (doğru
`Content-Type`, `nosniff`, `Content-Security-Policy: sandbox`, uzun cache).
Liste payload'ından `bannerUrl`'ü çıkar, popover açılınca yükle. Yüklemede
küçültüp yeniden kodla (avatar 512 px). "Lobby payload'ında `data:` yok" testi.
### HUB-001 — Dizin `instanceId`'si her kurulumda aynı (Orta)
- **Konum:** `packages/db/src/queries/instanceSettings.ts:6` (`DEFAULT_INSTANCE_ID = 'self-host'`,
tüm satırlar), `:518` (aynı adla gölgelenen `'default'`), `:551`
(`setDirectoryVerificationConfig` `'default'` satırını güncelliyor),
`.well-known/lobbyforge-verification/route.ts:54-59`, `registryInstances.ts:150-156`.
- **Durum bugün:** kanıt üreten sorgu hiç var olmayan `'default'` satırını okuyor.
Bu yüzden `.well-known` her kurulumda 404 dönüyor ve
`POST /api/admin/directory/config` `{ok:true}` deyip hiçbir şey yazmıyor.
**Yani dizin kaydı şu an hiçbir kurulumda çalışmıyor.** Bu rotaların testi yok.
- **Açık:** bariz tek satırlık düzeltme (`'self-host'` okumak) tüm kurulumların
aynı `instanceId`'yi yayınlamasına yol açar. Bir hub hesabı
`instanceId=self-host` için kendi alan adıyla kayıt olur; o andan sonra her
gerçek kurulum "başka bir kullanıcı kaydetmiş" 403'ü alır ve o kimliğin
heartbeat'leri saldırganın anahtarıyla doğrulanır. Saldırgan olmadan bile
ikinci dürüst kurulum ilkiyle çakışır.
- **Düzeltme:** her kuruluma rastgele, kalıcı bir dizin kimliği ver (ya da kimliği
ilk kayıtta anahtar parmak izine bağla). Satır sorgusunu düzelt;
`setDirectoryVerificationConfig` 0 satır güncellerse hata versin. Gerçek
sorguya giden route testleri ekle.
### Düşük önemdekiler
- **AUTH-002 — İptal edilen oturum sunucu tarafında oluşturulan sayfalarda çalışıyor.** İptal yalnızca
`withApiSecurity`, ws-gateway ve SSE'de uygulanıyor. `app/lobby/page.tsx:605-606`,
`app/layout.tsx:44` ve `lib/admin-auth.ts:28-40` (`isInstanceAdminAllowed`, tüm
`app/admin/**` sayfaları) çerezi iptal kontrolü olmadan okuyor. Çalınmış ve
sonra iptal edilmiş bir çerez, kendi süresi dolana kadar (en çok 60 dk) kanal,
üye ve son mesajları; sahipse denetim günlüğü ve davet kodlarını görmeye devam
ediyor. Ayrıca `api/admin/bandwidth/route.ts:27-29` oturumu `next/headers`
`cookies()` ile okuyor: Next yinelenen çerezde **sonuncuyu**, sarmalayıcının
`readCookie`'si **ilkini** alıyor. `Cookie: lf_guest=x; lf_guest=` iptal
kontrolünü atlıyor. Düzeltme: `getActiveSession()` = okuma + `isSessionRevoked`
(üretimde fail-closed) yardımcısı, sayfalarda ve `isInstanceAdminAllowed`'da
kullanılsın; bandwidth rotası `req.headers`'tan okusun; birden fazla `lf_guest`
taşıyan istek reddedilsin.
- **AUTHZ-004 — Davet, "ilk katılımda onay" politikasını atlıyor.** `invites.ts:240-357`
`server_access_policies`'i hiç okumuyor (kayıt rotası okuyor:
`api/auth/register/route.ts:72-87`). Arayüz "yeni kimlikleri moderatör onayına
kadar beklet" diyor. Onay kuyruğu olmadığı için ya katılımı reddet ya da ayarı
kuyruk gelene kadar gizle; `/lobby` otomatik katılımında da uygula.
- **AUTHZ-005 — Profil görünürlüğü uygulanmıyor.** `profileVisibility` kaydediliyor
(`settings/me/route.ts:26`) ama hiçbir okuyucu kullanmıyor; "Kimse" seçilse de
bio, banner, avatar ve durum metni lobby payload'ında ve DM listesinde gidiyor.
`applyPresencePrivacy` gibi bir projeksiyonla uygula ya da ayarı kaldır.
- **AUTHZ-006 — Susturulan üye ekran paylaşımı sesiyle konuşabiliyor.**
`lib/voice-moderation.ts:49-58` yalnızca mikrofonu kaldırıyor; token
`screen_share_audio` vermeye devam ediyor (`allowScreenShare: true` ve `STREAM`
varsayılan). "Sistem sesini paylaş" ya da kaynak etiketini değiştiren bir
istemci yeterli. Dinleyicinin yayını açması gerektiği için etkisi sınırlı.
`timedOut || voiceMuted` iken `screen-share-audio`'yu da düşür.
- **PLUG-002 — Atılmış host etkinliği bitirebiliyor.** `activities/[sessionId]/end/route.ts:57-84`
üyelik kontrol etmiyor; override'ı olmayan kanalda `canMemberAccessChannel` üye
olmayana da `true` dönüyor ve `isHost` izin kontrolünü atlatıyor. GET, actions
ve SSE üyelik istiyor, bu rota istemiyor. 71. satırdan önce sahip ya da
`isServerMember` iste; test ekle.
- **HUB-002 — Listelenmiş dizin kaydı yeniden incelemesiz değiştirilebiliyor.**
Kayıt varsa bile kanıtlar `body.publicKey`/`body.domain`'e karşı doğrulanıyor,
saklı anahtar ya da alan adına değil; upsert ad, açıklama, etiket vb. alanları
güncelliyor ve `isListed`/`isVerified`'a dokunmuyor. Yalnızca sahibin hub
oturumuna sahip biri onaylı kaydı "LobbyForge Official Support" yapıp oltalama
metni koyabilir. Kayıt varsa nonce imzasını saklı anahtarla doğrula, belgeyi
saklı alan adından çek; gösterilen alanlar değişince listelemeyi incelemeye al.
- **HUB-003 — Bayat kayıtlar erişilebilir kalıyor.** `discover/[instanceId]` ve
`discover/go` yalnızca `isListed`/`isBlocked`'a bakıyor, heartbeat tazeliğine
bakmıyor. Alan adının süresi dolup başkası alırsa resmî hub "doğrulanmış" rozetli
bir çıkış sayfasıyla onu gösterir. Admin paneli de bayat/bekleyen/engellenmiş
kayıtları göremiyor (`listPublicRegistryInstances`). İki sayfada
`HEARTBEAT_STALE_MS` uygula; adminlere tüm satırları veren bir sorgu ekle.
- **FILE-002 — Dizin doğrulama hatası iç ağı anlatıyor** (iki inceleyici ayrı ayrı
buldu: FILE-002 = HUB-004). `api/directory/register/route.ts:76-82` ve
`change-domain/route.ts:145-150` fetch hatasını aynen dönüyor:
`domain=https://postgres` → "Target resolves to a blocked address: 172.20.0.4";
bilinmeyen adlar `ENOTFOUND`; genel `host:port` hedeflerinde bağlantı/TLS hata
farkları port taraması sağlıyor. SSRF bloğu tutuyor, sızan yalnızca keşif
bilgisi. Dakikada 5 istek; misafir oturumu da yeterli ve rota self-host'ta da
açık. Genel bir hata dön, ayrıntıyı logla; noktasız hostname'leri reddet;
dizin yazma rotalarını resmî moda sınırla.
- **INFRA-002 — TURN gizli anahtarı yerel imaja giriyor.** `.dockerignore` render
edilen `infra/turn/turnserver.conf`'u dışlamıyor; `install.sh` render'dan sonra
`up -d --build` çalıştırıyor, `Dockerfile:6` `COPY . .` ile dosya `/app` altına
`node` sahipliğinde giriyor. Plugin worker aynı imajı kullanıyor. Dinamik
eklentiler açıkken kurulan bir marketplace eklentisi
`/app/infra/turn/turnserver.conf`'u okuyup `static-auth-secret`'ı oyun durumuna
yazabilir; sahibi istediği kullanıcı adı ve süreyle TURN kimliği basar.
Render edilen dosyaları (`infra/turn/turnserver.conf`, `infra/livekit/livekit.yaml`,
`infra/nginx/conf.d/*.conf`, `.install-staging.*`) dışla, tercihen
`.dockerignore`'u izin listesine çevir; CI imajda `static-auth-secret` arasın.
## 4. Doğrulanması gerekenler
Bunlar orta güvenli: desen var ama sömürülebilirlik ortama ya da bir ürün
kararına bağlı.
| ID | Konum | Soru |
|---|---|---|
| INFRA-V-01 | `docker-compose.prod.yml:355-360` | Hedef Docker Engine'de `internal: true` köprüsü host'ta bir gateway adresi alıyor mu? Alıyorsa INFRA-001 relay'i Postgres 5432 ve Redis 6379'a da ulaşır (ikisi de şifreli). |
| PLUG-V-01 | `apps/plugin-worker/src/index.ts:144-150` | Executor çocuğu PID 1 ile aynı UID'de: `/proc/1/environ`'dan `PLUGIN_WORKER_TOKEN` okunabilir; `spawn({detached:true})` `kill(-pid)`'den kaçar. Worker `internal` ağda `web:3000`'e doğrudan ulaşıp `X-Forwarded-For` uydurabilir. (Ön koşul: operatörün kötü niyetli bir eklentiyi onaylaması.) Worker yalnızca depolama ucuna ulaşan kendi ağına alınmalı mı? |
| PLUG-V-02 | `apps/ws-gateway/src/authorize.ts:72-106` | Sohbet topic'leri `READ_MESSAGE_HISTORY` istemiyor, REST istiyor. Bu yetkisi olmayan üye canlı mesajları socket'ten almalı mı? |
| PLUG-V-03 | `activities/[sessionId]/stream/route.ts:107-110` | SSE açılışı IP başına dakikada 30 ile sınırlı ama kullanıcı başına eşzamanlı akış sınırı ve nginx `limit_conn` yok. **Konu kapandı (2026-10-13):** SSE yolu kaldırıldı (ACT-001; hiçbir istemci kullanmıyordu). |
| AUTHZ-V-01 | moderasyon rotaları | Timeout almış bir moderatör kick/ban/timeout/rol atama/mesaj silme yapabiliyor. Timeout moderasyon yetkisini de askıya almalı mı? |
| AUTHZ-V-02 | `api/dm/route.ts:57-75` | Herhangi bir hesap, ortak sunucu olmadan herhangi bir kullanıcı UUID'sine DM açabiliyor; resmî hub'da tek savunma engelleme. Bilinmeyen UUID 500 dönüyor (varlık sorgusu gibi çalışıyor). |
| AUTHZ-V-03 | `lib/turn-credentials.ts:29`, `infra/turn/turnserver.conf.template:53-60` | TURN kimlikleri 12 saat geçerli ve geri alınamıyor; banlanan kullanıcı her genel IP'ye relay tutabilir. `allowed-peer-ip` SFU adresine sabitlenmeli mi? |
| FILE-V-01 | `app/lobby/page.tsx:743,818,822,826` | React 19 Flight, büyük avatar/banner stringlerini birden fazla kez serileştiriyor mu? (FILE-001'in çarpanını belirler; 6 MB avatarlı bir üyeyle `/lobby` boyutu ölçülmeli.) |
| FILE-V-02 | `apps/web/lib/ip-pinned-https.ts:63-67` | `timeout` toplam süre değil, soket boşta kalma süresi. Saniyede bir byte gönderen bir host kayıt isteğini günlerce açık tutabilir mi? |
| HUB-V-01 | `apps/desktop/src-tauri/src/lib.rs:59-80,181-222,296-302` | `begin_desktop_login`'i hiçbir şey çağırmıyor, yani masaüstü giriş devri şu an çalışmıyor. Bağlanmadan önce: `forward_handoff` `{code,state}`'i `'*'` hedefiyle o anki sayfaya gönderiyor ve bekleyen kayıt `connect/disconnect_instance`'ta temizlenmiyor. Webview origin'i `pending.instance_origin` ile karşılaştırılmalı. |
| HUB-V-02 | `lib.rs:306-419` | `on_navigation` / `on_new_window` yok. Bir instance sayfası ana pencereyi yabancı bir origin'e götürebilir mi? Pencere başlığı değişmediği ve adres çubuğu olmadığı için yabancı sayfa instance gibi görünür ve global PTT mesajlarını alır. |
| AUTH-V-01 | `lib/security-headers.ts:235-237` | `LOBBYFORGE_TRUSTED_PROXY=cloudflare` (`docs/WEB_APP.md:185`) paketli nginx ile güvensiz: nginx istemcinin `CF-Connecting-IP`'sini aynen geçiriyor. Belgelenen yol (`docs/DEPLOY_CLOUDFLARE.md`, real_ip + `x-forwarded-for`) doğru; `cloudflare` modu kaldırılmalı ya da nginx bu başlığı silmeli. |
Bağımlılıklar: yerelde `pnpm audit --prod` npm'in audit ucunda ECONNRESET
verdi (iki inceleyicide de). CI'daki üretim bağımlılık denetimi `38945b6`
üzerinde yeşil (2026-09-29). GitHub'da Dependabot **uyarıları kapalı**
(sürüm güncellemeleri açık); açılması önerilir.
## 5. Eylül düzeltmelerinin durumu
| Eylül bulgusu | Durum |
|---|---|
| S1 MANAGE_ROLES → ADMINISTRATOR | ✅ Tutuyor (verilemez yetki kontrolü, rütbe kuralları) |
| S2 Ban erişimi kesmiyor | ✅ Tutuyor. Ama ayrılan kişi banlanamıyor (AUTHZ-002) |
| S3 `/lobby` gizli kanal sızıntısı | ✅ Tutuyor |
| S4 Ses moderasyonu LiveKit'e yansımıyor | ✅ Mikrofon için tutuyor; ekran paylaşımı sesi açık (AUTHZ-006) |
| S5 Presence gizliliği | ✅ Tutuyor |
| S6 WS gateway Redis bağlantı patlaması | ✅ Tutuyor |
| S7 Şifre değişikliği oturumları iptal etmiyor | ⚠️ Kayıtlı oturumlar için tutuyor; önceden alınmış masaüstü kodu yolu açık (AUTH-001) |
| S8 Sesli kanalda ad taklidi | ✅ Tutuyor |
| S9 İmaja gizli dosya girmesi | ⚠️ Anahtar/yedek/sertifika için tutuyor; render edilen TURN ayarı giriyor (INFRA-002) |
| S10 change-domain yeniden onay | ✅ Tutuyor; ama ad/açıklama değişikliği onaysız (HUB-002) |
| S11 Anonim anket | ⚠️ Durum projeksiyonunda tutuyor; denetim günlüğünden aşılıyor (PLUG-001) |
| V6 Masaüstü postMessage | ✅ Tutuyor |
| V7 Build-time `NEXT_PUBLIC_*` | ✅ Tutuyor (CI kontrolü var) |
| coturn IPv6 CIDR | ✅ Şablon doğru; ama dosya okunamıyor (INFRA-001) |
## 6. Sağlam bulunan alanlar
- **Oturum:** HMAC-SHA256 imzalı çerez, sabit zamanlı karşılaştırma, `exp`
zorunlu, ≥32 karakter gizli anahtar ve hiçbir yerde geri dönüş değeri yok.
`lf_guest` HttpOnly, SameSite=Lax, üretimde Secure. Her giriş yolu yeni gid
üretiyor (session fixation yok). Oturumlar çerez verilmeden önce kaydediliyor
ve üretimde fail-closed.
- **CSRF/Origin:** tüm `app/api` rotaları sarılı; eksik Origin, `null` ve
`sec-fetch-site: cross-site` reddediliyor. Durum değiştiren GET yok.
- **Şifreler:** scrypt (N=16384, r=8, p=1), 16 byte tuz, bilinmeyen kullanıcıda
sahte hash, genel hata mesajları; zamanlamaya dayalı hesap tespiti yok.
- **Kurulum, OAuth, admin:** setup token SHA-256 + timingSafeEqual, advisory lock.
Google OAuth 256-bit state, JWKS ile ID token doğrulaması, hesaplar e-postayla
bağlanmıyor. Tüm `/api/admin/*` rotaları `requireInstanceAdmin`'den geçiyor.
Test-sıfırlama rotaları üretimde kapalı.
- **CSP ve başlıklar:** nonce tabanlı `script-src`, üretimde `unsafe-eval` yok,
tek `frame-src` origin'i, `frame-ancestors 'none'`, `object-src 'none'`,
HSTS, `nosniff`.
- **Yetkilendirme:** kick/ban/timeout/susturma/rol atama aynı hiyerarşi
modelinden geçiyor; sunucular arası id karıştırma her yerde reddediliyor;
LiveKit token'ında kimlik, ad ve metadata sunucuda belirleniyor;
`canUpdateOwnMetadata` verilmiyor.
- **Gizli oyun durumu:** her durum yolu (GET, aksiyon yanıtı, SSE, ws-gateway)
`projectActivityState`'ten geçiyor; bus durum taşımıyor; altı eklentinin gizli
alanları doğru projekte ediliyor; aktör alanları oturumdan yazılıyor;
rastgelelik sunucuda; bitmiş oturuma aksiyon gönderilemiyor.
- **Eklenti kurulumu:** yalnızca admin; onaylı katalog + sha256 ve boyut pini;
IP-pinli HTTPS (yönlendirme yok); tar taramasında giriş tipi, boyut, sayı ve
symlink kontrolü; executor çocukları `env:{}` ile çalışıyor; topluluk
eklentilerinin `renderClient`'ı tarayıcıda hiç çalışmıyor.
- **Botlar:** token'lar dinlenimde hash'li ve sabit zamanlı karşılaştırılıyor;
bot başına hız sınırı; yalnızca kendi sunucusunun kısıtsız metin kanalları;
`@everyone`/`@here` engelli; üyeler BOT rozetini taklit edemiyor.
- **İçerik gösterimi:** `dangerouslySetInnerHTML`, `innerHTML`, `srcDoc` yok;
her şey React metni; linkify/markdown/önizleme yok. Görsel yükleme yalnızca
PNG/JPEG/GIF/WebP, magic byte eşleşmesi, boyut sınırı; SVG reddediliyor.
- **SSRF:** dizin doğrulama, eklenti inceleme ve kurulum IP-pinli taşıma
kullanıyor; doctor, güncelleme manifesti, OAuth ve GitHub yıldız isteği
sabit/env URL'leri kullanıyor.
- **Dizin:** hesaba bağlı nonce, `.well-known` kanıtı, Ed25519 heartbeat (±300 sn,
nonce saklama), change-domain ve rotate-key eski anahtar imzası istiyor;
listeleme çıktısında `javascript:` URL'si mümkün değil.
- **Masaüstü:** Tauri 2.11.5 ACL'de `remote` girdisi yok, uzak sayfalar komut
çağıramıyor; yerel CSP `script-src 'self'`; deep link state'i sabit zamanlı
karşılaştırılıyor.
- **Altyapı:** imajlar digest ile sabitli, çalışma zamanı root değil, Postgres ve
Redis yayınlanmıyor ve şifreli, plugin worker salt-okunur + tüm yetkiler
düşürülmüş + yalnızca iç ağ. nginx `X-Forwarded-For`'u `$remote_addr`'a
eşitliyor ve `/api/internal/`'a 404 veriyor. `lfctl` Ed25519 imzası, digest
pini, sürüm düşürme koruması, shell'siz sabit komutlar, her uygulamada
doğrulanmış yedek. CI'da `pull_request_target` yok, release action'ları SHA ile
sabitli, provenance ve SBOM üretiliyor. Git geçmişinde (228 commit) özel anahtar
ya da gerçek `.env` yok.
## 7. Sertleştirme önerileri (açık değil, önceliğe göre)
1. **CI izinleri ve sabitleme:** `ci.yml`, `security.yml`, `desktop-release.yml`
action'ları etiketle (`@v4`, `@v0`, `@stable`) kullanıyor ve `permissions:`
bloğu yok; bu işler release'in okuduğu GHA cache'ine yazıyor. SHA ile sabitle,
`permissions: contents: read` ekle. `release.yml`'deki `desktop` ve
`github-release` işlerine kendi dar izinlerini ver; `LF_RELEASE_SIGNING_KEY`'i
yalnızca `v*` etiketlerine açık korumalı bir Environment'a taşı.
2. **Sürüm imzalama anahtarı** hâlâ `infra/keys/release-ed25519-private.pem`
altında, repo kopyasının içinde (Eylül'den açık). Repo dışına taşı.
3. **Hesap başına giriş sınırı:** login ve masaüstü girişi ayrı IP kovaları
kullanıyor (IP başına 15 dakikada 20 deneme, hesap başına sınır yok). E-postaya
bağlı ortak bir hata sayacı ekle.
4. **Mutlak oturum ömrü:** `POST /api/auth/guest` oturumu sonsuza kadar
uzatabiliyor; örneğin 30 günlük üst sınır koy. `revokeOtherSessions` hata
verirse şifre değişikliği başarılı sayılmasın.
5. **Üretimde güvensiz varsayılanları reddet:** `LOBBYFORGE_TRUSTED_PROXY`
yoksa yalnızca uyarı veriliyor (tüm istemciler tek kova, biri herkesi
kilitleyebilir). Dev/e2e compose'daki bilinen değerler (`devkey`/
`devsecret_please_change`, `dev_session_secret_…`) `NODE_ENV=production`
altında reddedilsin; dev portları `127.0.0.1`'e bağlansın.
6. **Plugin worker:** executor'ları ayrı UID'de çalıştır ve süreç grubu yerine
cgroup ile öldür; worker'ı yalnızca depolama ucuna ulaşan bir ağa al;
`x-lf-worker-token`'ı sabit zamanlı karşılaştır.
7. **Bot ve yetki boşlukları:** bot token'ını döndürmek o botun tüm yetkilerini
devrediyor; döndüreni o yetkileri verebilecek biriyle sınırla.
`PATCH /bots/{id} {enabled:true}` Moderation Bot'u `MANAGE_MESSAGES` kontrolü
olmadan açıyor. Uygulama ayarlarındaki `allowedChannelIds`/`allowedRoleIds`
saklanıyor ama etkinlik başlatılırken uygulanmıyor.
8. **Diğer:**
- ws-gateway istemciye `err.message` dönüyor (Drizzle hataları SQL içerir).
- Hushle çekilişi, takım kimlikleri ve zar `Math.random` kullanıyor; Vampire
Village gibi kripto RNG'ye geç.
- `presence` POST, sahte `bandwidthDeltaBytes` ile admin bant genişliği
alarmını tetikletebiliyor.
- Yüklenen görseller sunucuda yeniden kodlansın (EXIF/GPS, polyglot ve
decoder bombası temizlenir).
- `fetchIpPinned`'a toplam süre sınırı eklensin.
- `isBlockedNetworkIp` `::/96` ve 6to4'ü (`2002::/16`) de engellesin.
- Tar taramasında ustar `prefix` alanı da kontrol edilsin.
- Yedek dizini 0700, dosyaları 0600 olsun.
- `lfctl apply`'da açık anahtar yoksa durulsun.
- `/api/directory/*` yazma rotaları resmî moda sınırlansın.
## 8. Rakip kıyaslaması
LobbyForge hücreleri belgelerden değil koddan okundu; rakip bilgileri
Ekim 2026 itibarıyla web araştırmasından geliyor (kaynaklar bölüm sonunda).
Guilded 19 Aralık 2025'te kapandı ve tablo dışında. Spacebar ve Steam Chat
için güvenilir veri az olduğundan tabloya alınmadı.
### Konumlanma
**Gerçekten farklı olduğu yerler**
- **Sesli odada çalışan oyunlar.** Oyun durumu sunucuda tutuluyor ve her
izleyiciye yalnızca görmesine izin verilen kısmı gidiyor; gizli rol ve kart
hile ile okunamıyor. Tek bir eklenti SDK'sıyla altı bitmiş oyun var. En yakın
karşılığı Discord Activities, o da kendi sunucunda barındırılamıyor. Açık
kaynak tarafta benzeri yok (Matrix'te widget, Zulip'te bir trivia widget'ı var).
- **Kendi sunucunu işletmek alışılmadık derecede iyi çözülmüş:** imzalı ve
digest'e sabitli güncelleme, öncesinde doğrulanmış yedek, geri alma, Doctor
sağlık/kapasite sayfası, CI'da yedekten geri yükleme testi. Karşılaştırma için:
Stoat'un resmî uygulamaları çoğunlukla kendi sunucuna bağlanamıyor; Element
Kubernetes/Helm istiyor ve 2025'ten beri kendi barındıranlar çağrı altyapısını
da kendileri kurmak zorunda.
- **Davetle tek tıkla misafir olarak sesli odaya katılma** ve Türkçe öncelikli
arayüz. Discord Ekim 2024'ten beri Türkiye'de erişime kapalı.
- **Zamanlama:** Discord küresel yaş doğrulamasını 23 Eylül 2026'da açtı;
Guilded kapandı; TeamSpeak 6 sunucu lisansları 1 Aralık 2026'da doluyor.
**Geride olduğu yerler**
- **Temel metin sohbeti:** dosya yükleme, tepki (reaction), Markdown, thread,
bağlantı önizlemesi yok; son 50 mesajın gerisine kaydırılamıyor.
- **Mobil uygulama ve push bildirimi yok.**
- **Şifre sıfırlama, e-posta doğrulama ve 2FA yok.**
- **İnce bot platformu:** webhook, slash komutu ve olay akışı yok; botlar
polling yapıyor.
- **Masaüstü uygulaması** imzasız bir alfa, otomatik güncelleyici yok.
- **Yalnızca iki dil** (en, tr).
- **E2EE yok;** Discord 1 Mart 2026'dan beri sesli/görüntülü aramalarda
zorunlu E2EE kullanıyor.
- **En yakın doğrudan rakip Stoat** (eski Revolt): o da AGPL ve Discord benzeri,
2026'dan beri ses ve görüntü, ~135 dil, mobil uygulamalar. Ses öncelikli en
yakın rakip TeamSpeak 6, ama kapalı kaynak ve sunucusu hâlâ beta.
### Karşılaştırma tablosu
✓ var · kısmi · ✗ yok · ? doğrulanamadı · ᶜ uzun süredir var, yeniden kontrol edilmedi.
L-notları tablonun altında.
| Yetenek | **LobbyForge** | Discord | Stoat | Matrix/Element | Mumble | TS6 (beta) | Rocket.Chat | Zulip | Mattermost |
|---|---|---|---|---|---|---|---|---|---|
| Gürültü engelleme | kısmi L1 | ✓ Krisp | ✓ RNNoise | ✓ tarayıcı | ✓ RNNoise | ✓ | ? | n/a | ? |
| Bas-konuş (PTT) | ✓ L2 | ✓ | ✗ | kısmi | ✓ᶜ | ✓ᶜ | ✗ | n/a | ? |
| Ekran paylaşımı | ✓ (4K60'a kadar) | ✓ | ✓ 720p30 | ✓ | ✗ | ✓ 4K | kısmi | kısmi | kısmi |
| Görüntü | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | kısmi (Jitsi) | kısmi | kısmi |
| Sahne / etkinlik takvimi | ✗ L5 | ✓ | ✗? | ✗? | ✗ | ✗ | ✗? | ✗? | ✗? |
| Thread | ✗ | ✓ | ✗? | ✓ | ✗ | ✗ | ✓ᶜ | ✓ (topic) | ✓ᶜ |
| Yanıt | kısmi L7 | ✓ | ✓ | ✓ | ✗ | kısmi | ✓ | ✓ | ✓ |
| Tepki | ✗ L8 | ✓ | ✓ | ✓ | ✗ | kısmi | ✓ | ✓ | ✓ |
| Arama | kısmi L10 | ✓ | ✓ | kısmi | ✗ | ? | ✓ | ✓ | ✓ |
| Dosya paylaşımı | ✗ L12 | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ |
| Bağlantı önizlemesi | ✗ | ✓ | ✓ | ✓ | ? | ? | ✓ | ✓ | ✓ |
| Markdown | ✗ | ✓ | ? | ✓ | kısmi | ? | ✓ | ✓ | ✓ |
| Anket | ✓ (oyun) | ✓ | ✗? | ✓ | ✗ | ✗ | kısmi | ✓ | ✗ |
| AutoMod | ✓ L16 | ✓ (+AI) | ✗ (planlı) | kısmi (Draupnir) | ✗ | ✗ | ? | kısmi | ✗? |
| Timeout / ban / denetim günlüğü | ✓ | ✓ | ✓ | kısmi | kısmi | kısmi | kısmi | kısmi | kısmi |
| Topluluk içi şikâyet | ✗ L20 | ✓ | ✓ | ✓ | ✗ | ? | ✓ | ✓ | kısmi |
| Yavaş mod | ✗ | ✓ | ✓ | ✗? | ? | kısmi | ? | ✗ | ✗? |
| Kanal başına izin | kısmi L23 | ✓ | ✓ | ✓ | ✓ ACL | ✓ | kısmi | ✓ | kısmi |
| Bot / webhook / slash | kısmi L24 | ✓ | kısmi | ✓ | kısmi | kısmi | ✓ | ✓ | ✓ |
| Oyun / etkinlik | ✓ L25 | ✓ | ✗ | kısmi | ✗ | ✗ | kısmi | kısmi | kısmi |
| Mobil uygulama | ✗ L26 | ✓ | kısmi | ✓ | kısmi (3. taraf) | ✗ | ✓ | ✓ | ✓ |
| Masaüstü uygulama | kısmi L27 | ✓ | ✓ | ✓ | ✓ | ✓ beta | ✓ | ✓ | ✓ |
| Federasyon | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✓ | ✗ | kısmi |
| E2EE | ✗ | ✓ ses/görüntü | ✗ | ✓ varsayılan | ✗ | ✗? | kısmi | ✗ | ✗? |
| Kendi barındırma zahmeti | düşük–orta L30 | n/a | orta | yüksek (Helm) | çok düşük | düşük (Docker) | düşük | düşük | düşük |
| Lisans | AGPL-3.0 | kapalı | AGPL-3.0 | AGPL-3.0 (+ticari) | BSD-3 | kapalı | MIT + ee | Apache-2.0 | AGPL/MIT + ee |
| Dil sayısı | 2 | 31 | ~135* | 98* | ~45 | ? | 65–80 | 25 | 20 |
| Erişilebilirlik | kısmi L33 | ✓ | ? | kısmi | ✓ | ? | ✓ WCAG AA | ? | ? |
| Kullanıcı veri dışa aktarma | ✗ L34 | ✓ | ✗? | ✓ | n/a | ? | ? | ✓ | ? |
| Push bildirimi | kısmi L35 | ✓ | kısmi | ✓ | kısmi | ? | kısmi | kısmi | kısmi (ücretli) |
| Keşif / dizin | ✓ beta L37 | ✓ | ✓ | ✓ | ✓ᶜ | ✓ | n/a | ✓ | n/a |
| Gelir modeli | yok | Nitro, abonelik, mağaza | bağış | ESS / Synapse Pro | yok | lisans | ücretli plan | Cloud/destek | ücretli plan |
\* Kısmen çevrilmiş diller dahil.
**LobbyForge notları (koddan):**
- **L1:** Yalnızca tarayıcının yerleşik gürültü/yankı/kazanç ayarları
(`LobbyVoiceProvider.tsx:207-209`). "Ses izolasyonu" anahtarı kaydediliyor ama
çağrıya hiç uygulanmıyor.
- **L2:** Tarayıcıda sekme odaktayken, masaüstünde global (Ctrl+Space); yalnızca
Windows'ta doğrulandı. Sunucu PTT'yi zorunlu kılabiliyor.
- **L5:** `stage` kanal tipi var ama sesli kanal gibi davranıyor; zamanlanmış
etkinlik yok.
- **L7:** `replyToId` API ve veritabanında var, yanıt arayüzü yok.
- **L8:** `reactions` tablosu ve `add_reactions` izni var, ama API rotası ve
arayüz yok.
- **L10:** Arama yalnızca yüklü 50 mesajı süzüyor (`LobbyMainArea.tsx:316`);
API `before=` sayfalamayı destekliyor ama arayüz kullanmıyor.
- **L12:** `attachments` tablosu ve depolama admin sayfası var; ek, GIF ve hediye
düğmeleri devre dışı. Yalnızca avatar ve banner yüklenebiliyor.
- **L16:** Moderation Bot: Türkçe duyarlı yasaklı kelime (benzer harfleri
yakalıyor), bağlantı, toplu mention, flood ve tekrar. Kanal başına yavaş mod yok.
- **L20:** Yalnızca dizin kaydı şikâyeti var (`instance_reports`); üyeler
birbirini engelleyebiliyor.
- **L23:** ~20 izin, üye başına çoklu rol. Kanal düzeyinde yalnızca görünürlük
var; kanal başına izin/yasak override'ı yok.
- **L24:** Bot API v1 mesaj okuyup yazabiliyor, botlar polling yapıyor; olay,
webhook ve slash komutu yok.
- **L25:** Hushle, Quiz, Vampire Village, Watch Party, Poll, Dice Bot.
Marketplace eklentileri henüz kendi arayüzünü çizemiyor (ADR-002) ve sandbox
düşmanca kod için güvenli değil (ADR-001).
- **L26:** Yalnızca duyarlı web (`MobileNav.tsx`); PWA manifest'i ve service
worker yok.
- **L27:** Tauri 2, imzasız, otomatik güncelleyici yok; macOS/Linux test edilmedi.
- **L30:** `git clone` + `install.sh`, ~9 Docker servisi, UDP 50000–60000 açık
olmalı, yalnızca amd64; güncellemeleri `lfctl` yapıyor.
- **L33:** Azaltılmış hareket, yüksek kontrast, büyük metin, odak halkası
ayarları ve ~320 aria özniteliği; otomatik erişilebilirlik testi yok (axe kurulu
değil).
- **L34:** `lfctl backup` tüm sunucuyu yedekliyor; kullanıcı kendi verisini dışa
aktaramıyor ya da hesabını silemiyor (`softDeleteUser` var ama çağıran yok).
- **L35:** Yalnızca sekme açıkken tarayıcı bildirimi ve okunmamış noktaları.
- **L37:** Alan adı doğrulaması, heartbeat, doğrulanmış rozeti, filtre ve şikâyet
var; henüz gerçek sunucu listelenmiyor (bkz. HUB-001: kayıt şu an çalışmıyor).
## 9. Özellik açıkları ve öneriler
### Etkisi en yüksek açıklar
Hedef kitle: kendi sunucusuna sahip olmak isteyen oyun/ses toplulukları.
Efor tek geliştirici için: S < 1 hafta, M 1–3 hafta, L > 1 ay.
| # | Açık | Neden önemli | Efor | Kodda nerede |
|---|---|---|---|---|
| 1 | **Temel sohbet:** tepki, yanıt arayüzü, Markdown, dosya yükleme, bağlantı önizlemesi, eski mesajları yükleme | Topluluk günün çoğunu metinde geçiriyor; Mumble ve Steam dışında her rakipte var | M (tablolar, izin ve depolama sayfası hazır) | `…/messages/[messageId]/reactions` rotası, yükleme rotası + `lib/upload-quota.ts`, `LobbyLiveRoster.tsx`, Composer. FILE-001 düzeltmesiyle aynı dosya sunma rotası kullanılmalı |
| 2 | **Mobil erişim ve push** | Oyun geceleri telefondan organize ediliyor; iOS'ta web push yalnızca Ana Ekran'a eklenmiş sitelerde çalışıyor | PWA + Web Push M, yerel uygulama L | `app/manifest.ts`, service worker, `packages/db`'de push abonelik tablosu, `lib/chat-bus.ts` / `dm-bus.ts` |
| 3 | **Hesap kurtarma ve güvenlik + yasal kullanıcı hakları:** şifre sıfırlama (e-posta gerekir), e-posta doğrulama, 2FA/passkey, veri dışa aktarma ve hesap silme | Hesap kaybı en sık destek talebi; KVKK/GDPR hakları zorunlu | M | `app/api/auth/*`, yeni `app/api/users/me`, `settings/my-account`; Mailpit dev compose'da var |
| 4 | **Açık topluluklar için moderasyon:** şikâyet düğmesi ve kuyruk, yavaş mod, kanal başına izin/yasak, katılım kapıları (doğrulanmış e-posta, hesap yaşı), baskın kilidi, ban'da mesaj silme | Herkese açık sunucular bunlar olmadan spam'e dayanamaz | M | `channel_role_overrides`'a izin setleri, `message_reports` tablosu, `admin/moderation`, `lib/bots/moderation*.ts` |
| 5 | **Gerçek gürültü engelleme** ve çalışmayan ses izolasyonu anahtarı | Discord, Stoat, Mumble, TS6 hepsinde var. LiveKit'in Krisp filtresi yalnızca LiveKit Cloud'da; kendi sunucuda tarayıcıda RNNoise/DeepFilterNet gerekiyor | S–M | `LobbyVoiceProvider.tsx` track processor, `lib/voice-video-preferences.ts` |
| 6 | **Dağıtıma hazır masaüstü:** kod imzalama ve notarization, `tauri-plugin-updater`, yerel bildirim, macOS/Linux PTT doğrulaması | İmzasız uygulama SmartScreen/Gatekeeper'da duruyor | M (çoğu sertifika ve CI) | `apps/desktop/src-tauri/*`, `desktop-release.yml` |
| 7 | **Bot platformu v2:** olay akışı, gelen webhook (oyun sunucusu, Twitch, GitHub), slash komutları | Oyun toplulukları entegrasyonla yaşıyor | M–L | `app/api/bot/v1/*`, ws-gateway'de bot kimlik doğrulaması, `packages/bot-sdk` |
| 8 | **Sunucu tarafı arama** (Postgres tam metin indeksi) | Mevcut arama yalnızca son 50 mesajı görüyor | S–M | `packages/db` migration + `queries/messages.ts` |
| 9 | **Discord'dan içe aktarma:** kanal, rol, izin (mümkünse mesaj) | Yaş doğrulaması yüzünden Discord'dan ayrılmanın maliyetini düşürür | M | `lfctl import discord` ya da admin sayfası |
| 10 | **Zamanlanmış etkinlik ve gerçek sahne** (konuşmacı/dinleyici) | Turnuva ve topluluk geceleri için | M (sunucu susturması zaten LiveKit yayın iznini yönetiyor) | `lib/voice-moderation.ts`, `livekit/token`, `channels.type='stage'` |
| 11 | **Daha fazla dil** (es, de, pt-BR, ru, fr) ve Weblate | 2 dil, rakiplerin çok gerisinde | Dil başına S (`pnpm i18n:add` hazır) | `apps/web/messages/*`, `plugins/*/locales` |
| 12 | **Önce özel sesli odalar, sonra DM için E2EE** | Discord'da artık varsayılan | Ses M, metin L | LiveKit `e2ee` seçeneği + anahtar dağıtımı |
### Öne çıkaracak fikirler (eşitliğin ötesinde)
1. **Sesi yöneten oyunlar.** Gece vampirlere özel sesli alt oda, Hushle'da takım
sesi, elenen oyuncunun yalnızca dinleyebilmesi. LobbyForge sunucu susturması
için LiveKit izinlerini zaten canlı değiştiriyor; Discord Activities bunu
yapamıyor.
2. **Marketplace'i tamamlamak.** Eklentilerin kendi arayüzünü sandbox'lı bir
iframe'de çizmesi (ADR-002) ve sahte bir ses sunucusuyla yerel eklenti
oyun alanı.
3. **Dizinde "devam eden oyuna katıl".** İsteyen sunucular lobisinde oyun olan
herkese açık odaları ilan eder, herkes misafir olarak katılır (heartbeat ve
misafir erişimi hazır).
4. **Kendi barındırılan oyun sunucuları için kontrol paneli.** Minecraft/CS2/
Valheim durum kanalları, rollerle eşitlenen whitelist, oyun sunucusu açılınca
kendiliğinden açılan sesli oda (Bot API v2 + webhook üzerine).
5. **Geçici sesli odalar ve takım arama (LFG) panosu.** İlan, oyunu seçili bir
sesli oda yaratır; geçici odalar zaten backlog'da.
6. **Topluluğu taşıyabilmek bir satış argümanı.** Doctor'da görünen zamanlanmış,
şifreli, site dışı yedekler ve başka bir LobbyForge sunucusunun içe
aktarabileceği imzalı topluluk dışa aktarımı.
7. **Sunucular arası paylaşılan ban listeleri.** Hub üzerinden imzalı listelere
abonelik (Matrix'in Draupnir politika listelerine benzer); "küçük sunucu
spam'le tek başına baş edemez" sorununa cevap.
8. **Yayıncı modu ve OBS overlay'i.** Konuşanları ve canlı oyun skor tablosunu
gösteren bir tarayıcı kaynağı (backlog'da).
9. **Sunucuda kalan ses kalitesi izleme.** Oda başına jitter ve paket kaybı, TURN
kullanım oranı, Prometheus metrik ucu (TS6 beta13'te ekledi).
## 10. Lansman öncesi kontrol listesi
"Var" repo'nun karşıladığını, "Eksik" eklenmesi ya da kontrol edilmesi
gerekeni gösterir.
**Güvenlik**
- Var: CSP/HSTS, CSRF ve Origin kontrolü, fail-closed hız sınırları, imzalı ve
sabitli güncellemeler, CodeQL/Trivy/RustSec taramaları, branch koruması,
hash'e sabitli eklentiler, hash'li bot token'ları, sesi de kesen ban/timeout.
- Eksik:
- [ ] Bu raporun P0 listesi (§11), özellikle INFRA-001.
- [ ] Sahip ve adminler için 2FA, şifre sıfırlama, açık kayıtta captcha.
- [ ] Sürüm imzalama özel anahtarını repo klasöründen taşımak.
- [ ] Yayından önce GitHub yönetişimini kilitlemek (ADR-004); Dependabot
uyarılarını açmak.
- [ ] `SECURITY.md`'yi güncellemek: hâlâ "henüz etiketli sürüm yok" diyor,
oysa rc.7 çıktı.
- [ ] Dış bir güvenlik incelemesi.
- [ ] `LOBBYFORGE_DYNAMIC_PLUGINS_ENABLED` kapalı kalsın ya da yalnızca
incelenmiş eklentilerle açılsın.
**Gizlilik ve veri koruma (KVKK/GDPR)**
- Var: telemetri yok, arama motoru indekslemesi varsayılan kapalı, yalnızca
zorunlu çerezler, YouTube no-cookie oynatıcı.
- Eksik:
- [ ] Kullanıcının kendi verisini dışa aktarması ve hesabını silmesi.
- [ ] Mesajlar, denetim günlüğü ve `user_sessions`'taki IP adresleri için
saklama süreleri.
- [ ] Verinin gittiği üçüncü tarafların listesi (Google OAuth, YouTube,
kullanılıyorsa Cloudflare, güncelleme kontrolü için GitHub).
- [ ] KVKK aydınlatma metni (VERBİS muafiyeti olsa da zorunlu); hub yurt
dışında barındırılıyorsa yurt dışına aktarım notu.
**Yasal sayfalar**
- Var: Watch Party oynatıcısının altında telif uyarısı.
- Eksik:
- [ ] Kullanım Koşulları, Gizlilik Politikası, Kabul Edilebilir Kullanım,
belirlenmiş temsilci ve tekrar eden ihlal politikasıyla Telif/DMCA
sayfası.
- [ ] Marketplace yayıncı koşulları ve dizin listeleme politikası.
- [ ] İletişim/künye ve `/.well-known/security.txt`.
- Hub altbilgisinde bunların hiçbiri yok, oysa hub artık hesap açılışı ve
şikâyet alıyor.
- YouTube API koşulları yayınlanmış bir gizlilik politikası istiyor (istenen
`strict-origin-when-cross-origin` referrer politikası zaten ayarlı).
- [ ] Her sunucu sahibi veri sorumlusu olduğu için sahiplere şablon yasal
sayfalar.
**Platform düzenlemeleri**
- Eksik:
- [ ] **AB DSA:** bildirim-ve-işlem, gerekçe bildirimi ve iletişim noktası
tüm barındırma hizmetleri için geçerli (küçük şirket muafiyeti yalnızca
ek platform yükümlülüklerini kapsıyor).
- [ ] Koşullarda asgari yaş.
- [ ] Birleşik Krallık kullanıcıları bekleniyorsa Online Safety Act risk
değerlendirmesi.
- [ ] "LobbyForge" için marka araştırması (Revolt, marka ihtarı yüzünden
Stoat'a dönüşmek zorunda kaldı).
**Kötüye kullanım**
- Var: engelleme, ban, timeout, kick, Moderation Bot, admin incelemeli dizin
şikâyetleri, eklenti inceleme kuyruğu, denetim günlükleri.
- Eksik:
- [ ] Topluluk içinde şikâyet.
- [ ] Yükleme açılmadan önce CSAM politikası ve bildirim yolu.
- [ ] Kimin DM atabileceğini seçme (bugün herkes herkese DM açabiliyor;
AUTHZ-V-02).
- [ ] Misafirlere özel hız sınırları ve baskın kilidi.
- [ ] Hub'da kötüye kullanım iletişim adresi.
**Yedekler**
- Var: `lfctl backup`/`restore`, CI'da geri yükleme testi, her güncellemeden
önce otomatik yedek.
- Eksik:
- [ ] Zamanlanmış, site dışı, şifreli ve döngülü yedekler.
- [ ] `.env.prod`, TLS sertifikaları ve (açılınca) yüklenen dosyaların yedeği.
- [ ] Belirtilmiş RPO/RTO. Migration'lar yalnızca ileri gidiyor; güncellemeyi
geri almak yedekten dönmek demek.
**İzleme**
- Var: Doctor, `/api/health`, kapasite tahmini, bant genişliği sayfası.
- Eksik:
- [ ] Prometheus metrik ucu; disk, sertifika süresi, LiveKit/TURN düşmesi
için alarm.
- [ ] Docker log rotasyonu.
- [ ] İsteğe bağlı kendi barındırılan hata izleme.
- [ ] TURN için gerçek bir Allocate denemesi yapan sağlık kontrolü (bugünkü
TCP kontrolü INFRA-001'i göremedi).
**Ölçek sınırları**
- Var: realtime abonelik sınırları, k6 yük testi betiği.
- Eksik:
- [ ] TURN relay port aralığı hâlâ 41 port (49160–49200); ~10 TURN
kullanıcısının üstünde tükenebilir.
- [ ] LiveKit bridge modda 10.001 UDP portu yayınlıyor.
- [ ] Yalnızca amd64 imaj, tek VPS, tek LiveKit düğümü.
- [ ] Yük testini koşup sayıları yayınlamak.
**Erişilebilirlik**
- Var: erişilebilirlik ayarları sayfası, temaya uyan oyun UI kiti, renk tek
başına sinyal olarak kullanılmıyor.
- Eksik:
- [ ] CI'da axe kontrolleri.
- [ ] Ses ve oyunlar için ekran okuyucu ve yalnızca klavye turu.
- [ ] Erişilebilirlik uygunluk beyanı.
**Kayıtlı sürüm engelleri**
- [ ] Gerçek bir VPS'te temiz kurulum (DNS + Let's Encrypt). Bu kurulum
INFRA-001'i de yakalardı.
- [ ] macOS ve Linux'ta masaüstü PTT, kod imzalama.
- [ ] UDP'yi engelleyen bir ağdan TURN testi (`BETA_RELEASE.md`).
**Çalışmayan şey vaat eden arayüz** (yayından önce yap ya da gizle)
- [ ] Devre dışı ek, GIF ve hediye düğmeleri.
- [ ] Hiçbir şey yapmayan ses izolasyonu anahtarı.
- [ ] Giriş tasarımındaki "Şifremi unuttum?" yeri.
- [ ] "Yakında" diye işaretli bot izinleri.
- [ ] Uygulanmayan "Profil görünürlüğü" ayarı (AUTHZ-005) ve onay kuyruğu
olmayan "ilk katılımda onay" seçeneği (AUTHZ-004).
**Eskimiş belgeler**
- [ ] `README.md:60` Vampire Village ve Watch Party'yi hâlâ "planlı" diyor.
- [ ] README kıyaslaması hâlâ "Revolt" diyor ve onu ses öncelikli değil
sayıyor; Stoat'ta 2026'dan beri ses ve görüntü var.
- [ ] `docs/VAMPIRE_VILLAGE.md`'deki "tek yan kanal bus zamanlaması" ifadesi
PLUG-001 düzelene kadar doğru değil.
### Kaynaklar
- Discord: support.discord.com (Krisp 360040843952, Stage 1500005513722,
Etkinlikler 4409494125719, Thread 4403205878423, Anket 22163184112407, AutoMod
4421269296535, Doğrulama seviyeleri 216679607, Yavaş mod 360016150952, Webhook
228383668, Veri paketi 360004957991, Keşif 360030843331);
docs.discord.com/developers (audit-log, application-commands, activities);
discord.com/blog (E2EE, 8 Eylül 2026 sürüm notları); en.wikipedia.org/wiki/Discord
(Türkiye engeli); techcrunch.com 2026/02/09 ve idtechwire.com (yaş doğrulaması).
Not: discord.com bu makineden açılamadı (muhtemelen Türkiye engeli); Discord
bilgileri resmî sayfaların arama özetlerinden.
- Stoat: en.wikipedia.org/wiki/Stoat_(software), github.com/stoatchat/self-hosted,
stoat.chat sürüm notları, github.com/stoatchat/stoatchat CHANGELOG.
- Matrix/Element: element.io/blog (Element X/Call/Server Suite, AGPLv3'e geçiş,
self-host E2EE ses/görüntü), element.io/en/server-suite/community,
github.com/the-draupnir-project/draupnir.
- Mumble: mumble.info/blog (1.6.870-rc, 1.5.634).
- TeamSpeak 6: community.teamspeak.com (beta13, beta süreci),
alternativeto.net/news/2026/8.
- Rocket.Chat: docs.rocket.chat/docs/our-plans, rocket.chat/blog (8.0, 8.5
erişilebilirlik).
- Zulip: blog.zulip.com (12.0, 11.0, Zulip Foundation), zulip.com/help/self-hosted-billing.
- Mattermost: forum.mattermost.com (v11 ücretsiz sürüm değişiklikleri),
docs.mattermost.com.
- Guilded: en.wikipedia.org/wiki/Guilded, alternativeto.net/news/2025/11.
- LiveKit: docs.livekit.io/transport/media/noise-cancellation,
docs.livekit.io/transport/encryption.
- Yasal: OneSignal iOS web push belgesi, YouTube API koşulları
(developers.google.com/youtube/terms), AB DSA SSS
(digital-strategy.ec.europa.eu), KVKK aydınlatma rehberi (kvkk.gov.tr).
- coturn: github.com/coturn/coturn (`docker/coturn/debian/Dockerfile`: `USER
nobody:nogroup`; `README.turnserver`: `--no-auth` varsayılan).
Uyarılar: Stoat'un tek yol haritası dosyası 2021 tarihli; "planlı" denenler o
zamandan beri çıkmış olabilir. Rakip hücrelerindeki "?" doğrulanamayan bilgiyi
gösterir.
## 11. Öncelikli yapılacaklar
**P0 — herkese açık kurulumdan önce**
1. INFRA-001: TURN ayar dosyasının sahipliğini ve iznini düzelt, coturn'ü
fail-closed başlat, CI'a "kimliksiz Allocate → 401" testi ekle.
2. INFRA-002: render edilen ayarları `.dockerignore`'a ekle (tercihen izin listesi).
3. AUTH-001: masaüstü kodunu kimlik bilgisi sürümüne bağla.
4. AUTHZ-003: içerik düzenlemesini yalnızca yazara aç.
5. AUTHZ-001: kanalın son kapı rolünün silinmesini engelle.
**P1 — beta süresince**
6. AUTHZ-002: moderasyon durumunu üyelikten ayır; üye olmayanı banlamaya izin ver.
7. PLUG-001: oyun aksiyonlarını denetim günlüğüne yazma.
8. FILE-001: avatar/banner'ı ayrı bir rotadan sun, liste payload'ından çıkar.
9. HUB-001: kurulum başına benzersiz dizin kimliği ver ve proof sorgusunu düzelt
(dizin kaydı ancak bundan sonra çalışır).
10. AUTH-002: sunucu tarafında oluşturulan sayfalar ve admin için iptal kontrolü.
**P2 — sonraki sürümler**
11. Düşük önemdekiler (AUTHZ-004/005/006, PLUG-002, HUB-002/003, FILE-002) ve
§7'deki sertleştirmeler.
## 12. Düzeltme durumu (2026-10-03)
Dal: `fix/security-review-2026-10`. Her düzeltmenin testi var. Düzeltmelerin
tamamı salt-okunur bir inceleyiciye yeniden denetletildi; onun bulduğu bir
yan yol (FILE-001) ve yedi ek madde de kapatıldı.
| ID | Düzeltme | Doğrulama |
|---|---|---|
| INFRA-001 | TURN konteyneri root başlıyor ama yetkileri `SETUID, SETGID, DAC_READ_SEARCH, KILL, NET_BIND_SERVICE` ile sınırlı ve `no-new-privileges` açık. `proc-user=nobody` ile turnserver ayarı ve sertifikayı okuduktan sonra yetkisiz `nobody`'ye düşüyor. `cert-watcher.sh` ayar okunamazsa ya da REST auth yoksa başlamayı reddediyor. `no-tcp-relay` ve ek özel IP aralıkları eklendi. Kimliksiz Allocate yollayan `scripts/turn-auth-probe.mjs` yazıldı. | Gerçek coturn imajında açık relay **canlı olarak yeniden üretildi** (probe: OPEN RELAY). Düzeltmeden sonra 401 dönüyor, turnserver `nobody` kullanıcısıyla ve sıfır yetkiyle çalışıyor, root'a özel sertifikayla TLS (5349) açılıyor. Root olmayan kullanıcıya ait ayar dosyası okunuyor. `user:` satırı silinirse sunucu açılmayı reddediyor. CI artık `umask 077` ile render edip aynı kontrolleri yapıyor. |
| INFRA-002 | Render edilen ayarlar (`turnserver.conf`, `livekit.yaml`, `nginx/conf.d/*.conf`, `.install-staging*`) `.dockerignore`'a eklendi. CI, imajda render edilmiş TURN anahtarı arıyor. | Gerçek derlemede çalışma dizininde render edilmiş dosyalar varken imajda yoklar. |
| AUTH-001 | Masaüstü kodu, şifre hash'inin SHA-256 parmak iziyle saklanıyor; tamamlanırken eşleşmezse 401 dönüyor. Şifre değişikliği açık kodları siliyor. Diğer oturumlar kapatılamazsa yanıt 200 ve çevrilmiş bir uyarı. | Rota testleri: kod al → şifre değiştir → 401. |
| AUTH-002 | `getActiveSession` (okuma + iptal kontrolü, üretimde fail-closed) tüm sunucu sayfalarında, layout'ta, hub'da ve `isInstanceAdminAllowed`'da kullanılıyor. Bandwidth rotası çerezi istekten okuyor. İki `lf_guest` çerezi taşıyan istek 400 alıyor. | Birim testleri; gerçek stack e2e. |
| AUTHZ-001 | `deleteRole`, rol bir kanalın son kapısıysa 409 `role_gates_channels` döner. Roller sayfası kanalları adıyla gösteriyor. | Gerçek Postgres entegrasyon testi. |
| AUTHZ-002 | Yeni `server_member_sanctions` tablosu (migration 0040): timeout ve susturma ayrılınca da kalıyor, yeniden katılınca geri yükleniyor. Timeout alan davet oluşturamıyor. Üye olmayan kullanıcı banlanabiliyor. | Gerçek Postgres entegrasyon testi; dev veritabanı kopyasında geri doldurma doğrulandı. |
| AUTHZ-003 | Mesaj metnini yalnızca yazarı değiştirebiliyor (ayrıca `SEND_MESSAGES` gerekiyor). `MANAGE_MESSAGES` silme ve sabitleme yetkisini koruyor. | Rota testleri. |
| AUTHZ-004 | Davet ve otomatik katılım onay politikasına uyuyor (403 `approval_required`). Lobby "katılamazsın" mesajı gösteriyor. Ayar açıklamaları, henüz kuyruk olmadığı için yeni üyeleri engellediğini söylüyor. | Rota ve entegrasyon testleri. |
| AUTHZ-005 | Profil görünürlüğü üye listesinde, DM'lerde, engel listesinde, admin üye sayfasında ve resim rotasında uygulanıyor. | Projeksiyon testleri. |
| AUTHZ-006 | Susturmada ekran paylaşımı sesi de, timeout'ta tüm yayın kaynakları düşüyor. Boş kaynak listesi artık `canPublish:false` gönderiyor; LiveKit boş listeyi "her şey serbest" sayıyordu, ek bir açık buydu. Arayüz timeout'u doğru gösteriyor, düğmeler kapalı. | Token ve arayüz testleri. |
| PLUG-001 | Oyun aksiyonları (`member`/`player`) ve reddedilen aksiyonlar denetim günlüğüne yazılmıyor; host aksiyonları yazılıyor. `GamePluginActionPolicy.audit` seçeneği eklendi. | Gerçek VV ve Poll politika tablolarıyla rota testleri. |
| PLUG-002 | `authorizeChannelVisibility` üye olmayanı reddediyor. Ayrıca `constructor` gibi devralınan adlar artık politika sayılmıyor. | Rota testleri. |
| FILE-001 | Listelerde resim verisi yok; resimler `/api/users//avatar\|banner?v=` üzerinden, profil görünürlüğü uygulanarak sunuluyor. `getUserById` resim ve şifre hash'i sütunlarını hiç okumuyor; yazar adları tek bir dar sorguyla geliyor. Resim sürümü ayrı sütunlarda (migration 0041), kota hesabı SQL'de yapılıyor. | Gerçek Postgres betiği: kota doğru, profil düzenlemesi sürümü değiştirmiyor, yeni yükleme değiştiriyor. |
| FILE-002 | Doğrulama hataları genel bir mesaj dönüyor, noktasız hostname reddediliyor, dizin yazma rotaları yalnızca resmî hub'da var. | Rota testleri. |
| HUB-001 | Her kurulum rastgele bir UUID dizin kimliği alıyor (migration 0039). `.well-known` ve admin ayarı çalışıyor; `self-host`/`default` kimlikleri reddediliyor. | Rota testleri; migration gerçek Postgres'te. |
| HUB-002 | Var olan kayıt, saklı anahtar ve alan adıyla doğrulanıyor. Gösterilen alan değişince kayıt yeniden incelemeye düşüyor. | Gerçek Postgres entegrasyon testi. |
| HUB-003 | Bayat kayıtlar detay ve çıkış sayfasında gösterilmiyor; admin tüm kayıtları durumlarıyla görüyor. | Sayfa testleri. |
Toplam doğrulama: build, typecheck ve lint (0 hata) temiz. Tüm paket
testleri geçti (`apps/web` 1574). Veritabanı entegrasyon testleri gerçek
Postgres'te 136/136; migration'lar boş veritabanında ve dev verisinin
kopyasında uygulandı. Uçtan uca sonuçlar için PR açıklamasına bakın.
### Uçtan uca testlerin bulduğu iki ek hata (aynı gün düzeltildi)
- **Avatar/banner rotası her istekte 500 veriyordu.** Tek tabloluk sorguda
Drizzle sütunları nitelemeden yazıyor, bu yüzden alt sorgulardaki
`users.id` çıplak `"id"` olarak çıkıyordu: üyelik alt sorgusunda
belirsiz bir sütundu, ayarlar alt sorgusunda ise sessizce
`user_settings.id`'ye bağlanıyordu ("kimse" ayarı yok sayılırdı). Artık
kullanıcı kimliği parametre olarak geçiyor. Gerçek Postgres testi eski
sorguda kırılıyor, yenisinde geçiyor (`user-images.integration.test.ts`,
CI'a da eklendi).
- **Susturulan üye sesini "kamera" ya da "ekran paylaşımı" etiketiyle
yayınlayınca duyulabiliyordu** (LiveKit v1.13.7'de canlı doğrulandı:
yaklaşık 82 KB ses geçti). LiveKit izin verirken yalnızca etikete
bakıyor, izin ses mi görüntü mü olduğuna bakmıyor. Düzeltme iki katmanlı:
- Uygulamanın dinleyicileri türü etiketiyle uyuşmayan izleri hiç
çalmıyor: ses yalnızca mikrofon ve ekran sesi etiketiyle, görüntü
yalnızca kamera ve ekran etiketiyle kabul ediliyor.
- İmzalı bir LiveKit webhook'u (`/api/livekit/webhook`, nginx dışarıya
kapalı) böyle bir iz yayınlayan katılımcıyı odadan atıyor ve denetim
günlüğüne yazıyor.
Var olan kurulumlarda webhook, ayarlar yeniden render edilince açılıyor
(`bash install.sh`). İstemci katmanı güncellemeyle hemen geliyor.
### Takip turu (2026-10-03, `fix/security-followups`)
Yukarıda açık kalan maddeler ve §7'deki sertleştirmeler:
| Madde | Durum |
|---|---|
| Odadan atılan katılımcı yeniden bağlanabiliyordu | ✅ Atılan kullanıcı o sunucunun sesinden engelleniyor (10 → 30 → 120 dk) ve sunucunun diğer odalarındaki bağlantıları da kapatılıyor. Yeni token verilmiyor (`voice_blocked`). Eski token'la katılırsa `participant_joined` webhook'u onu hemen atıyor. LiveKit token ömrü 1 saatten 10 dakikaya indi; LiveKit bağlı katılımcının token'ını kendisi yeniliyor. |
| Eski oyun denetim satırları | ✅ Migration 0042, gizli bilgi sızdıran satırları siliyor: Vampire Village oyuncu aksiyonları ve anket oyları. Host aksiyonları, diğer eklentiler ve eski Watch Party'nin host kayıtları korunuyor; gerçek Postgres'te doğrulandı. |
| Onay kuyruğu yoktu | ✅ `server_join_requests` (0043). Davet ve otomatik katılım istek oluşturuyor; `KICK_MEMBERS` ya da `MANAGE_SERVER` yetkisi olan onaylıyor veya reddediyor. Ban her zaman öncelikli, yaptırımlar onayda uygulanıyor. Ret sonrası 7 gün bekleme, günde en fazla 5 istek. |
| Varsayılan erişim politikası tutarsızlığı | ✅ Kayıtlı politika yokken gösterilen ve uygulanan değer artık aynı (`public_self_register`). Gösterilen değerleri kaydetmek davranışı değiştirmiyor. |
| CI action sabitleme ve izinler | ✅ 57 `uses:` satırının hepsi SHA'ya sabitlendi. Varsayılan izin `contents: read`, yayın işlerine yalnızca gereken izinler veriliyor. İmzalama anahtarı `release` ortamına bağlandı (repo ayarlarında koruma kuralı sahibin işi). |
| Hesap başına giriş sınırı | ✅ E-posta başına 15 dakikada 10 hata; login ve masaüstü girişi ortak sayaç kullanıyor. Bilinmeyen e-posta da aynı şekilde sayıldığı için hesap tespiti yapılamıyor. Şifre değişikliğinde kullanıcı başına 15 dakikada 5 deneme. |
| Mutlak oturum ömrü | ✅ Çerezdeki `auth_time` alanından itibaren 30 gün (`LOBBYFORGE_SESSION_MAX_AGE_DAYS`). Eski çerezler ilk yenilemede saymaya başlıyor. |
| Diğer §7 maddeleri | ✅ Bot token döndürme ve Moderation Bot PATCH yetki kontrolleri. Gateway hata mesajı sızıntısı ve UUID doğrulaması. Hushle, zar ve Quiz'de kripto rastgelelik. Sahte bant genişliği raporlarına sınır. `fetchIpPinned` toplam süre sınırı; `::/96`, 6to4, Teredo ve yerel NAT64 engeli. Tar `prefix` alanı. `lfctl` açık anahtar zorunluluğu ve 0700/0600 izinler. Uygulamaların kanal/rol izin listelerinin uygulanması. Google adı doğrulaması. Proxy ayarı için Doctor uyarısı. |
| Marketplace eklenti yolu | ✅ `migrateState` artık bekleniyor; önceden state aksiyonlar arasında kayboluyordu. Kurulum ve worker aynı dizini kullanıyor. Yalnızca aktif olarak kaydedilen sürüm ve digest yükleniyor. İç içe paket reddediliyor. Bkz. `docs/EXTENDING.md`. |
Hâlâ açık:
- E-postayı bilen biri, yanlış şifre deneyerek o hesabın girişini 15
dakikalık aralıklarla kilitleyebilir. Hesap başına sınırın bilinen
ödünleşimi.
- Kural dışı sayılan dürüst bir istemci (beklenmedik bir tarayıcı hatası)
10 dakika sesten engellenir.
- Plugin worker hâlâ tek UID'de çalışıyor (ADR-001); marketplace eklentileri
düşmanca koda karşı tam izole değil.
- `style-src 'unsafe-inline'` CSP'de duruyor.
- Repo ayarlarında yapılacaklar: `release` ortamına `v*` koruma kuralı,
`LF_RELEASE_SIGNING_KEY`'i ortama taşımak, Dependabot uyarılarını açmak.
Ayrıca `infra/keys/release-ed25519-private.pem` dosyasını repo
klasöründen taşımak.