# VC-094 · Tarayıcı bütün tabloyu indirip gerekli kayıtları sonradan süzüyor

Ekranda yalnız birkaç kayıt görünürken ağ yanıtı bütün listeyi taşıyor. İstemci filtresi aktarım maliyetini azaltmıyor ve yanıtın içinde kalan gereksiz alanları kullanıcıdan saklamıyor.

- Önem: YÜKSEK. Etkisi orta. Her gün, sıradan kullanımda tetiklenir.
- Önem notu: Büyüyen veri kümesinin her listede tarayıcıya taşınması esas alınır. Küçük ve herkese açık sabit sözlükte etki düşer. Yetkisiz veri de geliyorsa ayrıca erişim kontrolü maddeleri uygulanır.
- Eksen ve kategori: Sağlamlık, 14 Performans ve ölçek
- Yığın: Supabase, Firebase, Node.js
- Yapay zekâ kodunda: ölçülmedi. Dayanak: uzman görüşü.
- Ne zaman bakılır: İlk yayından önce, Her ay
- CWE: CWE-770
- Checklist ifadesi: Süzme sunucuda yapılıyor, tarayıcı bütün tabloyu indirmiyor.
- Son inceleme: 4 Ekim 2026, Komünite editörlüğü
- Adres: https://vibecheck.komunite.com.tr/madde/tarayici-butun-tabloyu-indirip-kendisi-suzuyor

## 60 saniyelik kontrol

Yalnız kendi uygulamanda ya da yazılı izin aldığın sistemde dene. Bu bir sızma testi değildir.

1. Ağ yanıtındaki kayıt sayısını ekranda görünen sayı ile karşılaştır.
2. Tarayıcıdaki filter ve slice çağrılarından önce indirilen veriyi incele.
3. Gereksiz alanların ham yanıtta bulunup bulunmadığını kontrol et.
4. Sorguda filtre, alan seçimi ve sayfa sınırının birlikte uygulandığını doğrula.

## Ne oluyor

Liste ekranında durum filtresi var. Açık işleri seçtiğinde yalnız açık işler görünüyor. Bu davranış sorgunun da aynı kapsamda çalıştığını düşündürüyor. Ancak tarayıcı bütün kayıtları indirmiş, sonra JavaScript ile süzmüş olabilir. Görünür liste küçülürken ağ yanıtı ve bellekteki veri aynı büyüklükte kalır. Kullanıcı ekranın gösterdiğinden fazlasını indirmiştir.

Burada iki farklı sınır var. Yetki denetimi hangi kayıtların kullanıcıya verilebileceğini belirler. Liste filtresi ise izinli kayıtların hangilerinin o ekran için gerektiğini seçer. Kullanıcı kendi ekibinin bütün işlerini görmeye yetkili olsa bile her sayfa açılışında bütün geçmişi indirmek gerekli olmayabilir. Yetkili aktarım da zaman ve kaynak tüketir.

Örnekte kiracı filtresi iki sürümde de sunucuda kalır. Hatalı sürüm o kiracının bütün durumlarını ve bütün sütunlarını yanıt olarak hazırlar. Tarayıcı bunları süzüp kısa bir liste yapar. Düzeltilmiş sürüm aynı görünür sonucu sorguda seçer. Böylece yetki açığı varsaymadan, gereksiz aktarımın kendisini gösterebiliriz. Ağda gönderilen veriyle ekranda çizilen veri ayrı ayrı değerlendirilmelidir.

## Gerçek olay

Bu maddeye belirli bir AI ürününün kamuya açık olayını bağlamıyoruz. Supabase JavaScript belgesi, alan seçimiyle birlikte filtrelerin sorguya nasıl eklendiğini gösteriyor. Filtrenin veri geldikten sonra bir dizi işlemine dönüşmesi aynı davranış değildir. [Supabase filtreleri](https://supabase.com/docs/reference/javascript/using-filters)

Firestore da koleksiyon sorgularını koşullarla daraltmak için sorgu araçları sunuyor. Ancak Firestore belge modelini SQL sütun seçimiyle aynı saymamak gerekir. Buradaki yerel örnek SQL sonucunu ve tarayıcıya hazırlanacak yanıtı sınar. Firebase ağı veya ücretlendirmesi üzerinde ölçüm yapmaz. [Firestore sorguları](https://firebase.google.com/docs/firestore/query-data/queries) Kaynaklar sorguda süzme olanağını doğruluyor. İndirme hacminin her uygulamada aynı olacağını veya bu desenin AI kodunda belirli sıklıkta görüldüğünü söylemiyoruz.

## Yapay zekâ bunu neden üretiyor

**Model ekran bileşeninden başlar.** İstek bir filtre düğmesi eklemekse ajan mevcut veri dizisine `filter` uygulayabilir. Bu değişiklik görünür davranışı hızla tamamlar ve önceden çalışan veri erişim koduna dokunmaz. Ancak mevcut dizinin nasıl doldurulduğunu izlemeden verilen çözüm, gereksiz aktarımın devam etmesine yol açabilir. İnceleme bileşenin sınırından veri sorgusuna kadar ilerlemelidir.

**Örnek kayıtlar aktarım sorununu gizler.** Küçük geliştirme verisinde bütün listeyi indirmek ucuz görünür. Arayüz hızlı açılır ve elle deneme olumlu sonuç verir. Ajan bu sonucu büyüyen veri için de yeterli kabul edebilir. Kabul ölçütünde yanıt satırları ve alanları yoksa test yalnız ekrandaki doğru başlıkları doğrular. Gereksiz veriyi hiç sorgulamaz.

**Genel seçim yeniden kullanımı kolaylaştırır.** `SELECT *` veya bütün belgeyi taşıyan bir veri katmanı, farklı ekranlara hazır veri sağlar. Model daha sonra gereken alanı bulamama olasılığını bu geniş seçimle azaltmaya çalışabilir. Bedeli her ekranın kullanmadığı içeriği de taşımasıdır. Alanların açıkça seçilmesi bu bağımlılığı görünür kılar ve yanıt sözleşmesini daha kolay incelenebilir hale getirir.

**Görsel gizleme erişimle karışabilir.** Bir alanın bileşende çizilmemesi, yanıt içinde bulunmadığı anlamına gelmez. Ajan ekranı doğru kabul ettiğinde ağ yanıtını atlayabilir. Bu olası mekanizmaları, kaynakların anlattığı sorgu davranışıyla birlikte değerlendiriyoruz. Model eğitimi veya bütün AI araçları hakkında doğrulanmış yaygınlık iddiası kurmuyoruz. Gerçek uygulamada önce aktarım yolunu görmen gerekir.

## Etki

Gereksiz satırlar daha büyük yanıt, daha uzun indirme ve tarayıcıda daha fazla veri işleme anlamına gelebilir. Mobil bağlantıda kullanıcı küçük bir liste için bütün geçmişin gelmesini bekleyebilir. Veri büyüdükçe başlangıçta fark edilmeyen davranış ekranın açılmasını zorlaştırabilir.

Yanıtta gereksiz özel alan bulunuyorsa kullanıcı bunları ağ araçlarından görebilir. Bu durum ayrıca erişim ve veri minimizasyonu değerlendirmesi ister. Buradaki performans önemini otomatik veri sızıntısı kabulüyle yükseltmiyoruz. Örnekteki gereksiz alan sentetiktir ve bütün kayıtlar kullanıcının izinli kiracısındadır.

## Nasıl anlarsın

Tarayıcı ağ panelinde liste yanıtını aç. Ekranda görünen kayıtlarla gelen kayıtları karşılaştır. Arayüz filtresini değiştirince yeni sorgu gidiyor mu, yoksa ilk indirilen dizi mi süzülüyor? Gereksiz sütunların ham yanıtta bulunup bulunmadığını kontrol et. Yanıtı paylaşırken gerçek kullanıcı verisini dışarı taşıma.

Kodda veri alma çağrısından sonraki `filter`, `slice` ve `map` zincirini izle. Bunlar kendi başına hata değildir. Sorun gerekli sorgu sınırlarının yalnız bu zincirde uygulanmasıdır. Yerel test aynı görünür kimlikleri doğrular, ayrıca aktarılacak satır sayısını ve alan listesini denetler. Gerçek HTTP aktarım süresini ölçmez.

## Nasıl düzeltirsin

1. **Filtreyi sorguya taşı.** İzinli durum değerlerini doğrula. Parametreli sorguda durum ve güvenilir kiracı kapsamını birlikte uygula. [SQL seçim koşulları](https://www.sqlite.org/lang_select.html)
2. **Yanıt alanlarını seç.** Liste için gereken kimlik ve başlık gibi alanları açıkça döndür. Başka ekranın ayrıntı ihtiyacını ayrı istekle karşıla.
3. **Sayfayı sınırla.** Sonlu boyut ve kararlı sıralama kullan. Örnek ilk sayfayı gösterir. Devam sayfalarında aynı filtreyi ve yetki kapsamını koru.
4. **İki sonucu karşılaştır.** Ekranın aynı kayıtları gösterdiğini ve ham yanıtın yalnız gerekli veriyi taşıdığını test et. Başka kiracı ve boş sonuç denemelerini ekle.

## Bir daha olmasın

Liste kabulüne ham yanıtın incelenmesini ekle. Yalnız ekran görüntüsüne bakarak veri erişiminin kapsamını onaylama.

## Sınır

Küçük ve üstten sınırlı ülke listesi gibi sözlükleri istemcide süzmek uygun olabilir. Açıkça tasarlanmış çevrimdışı kullanım da farklı bir sözleşmedir. Sorgu filtresi RLS veya Firebase yetki kurallarının yerini tutmaz. SQL alan seçimi örneğini Firestore tarayıcı SDK'sında alan bazlı erişim garantisi gibi uygulama. Bu madde indeks seçimi ve bağlantı kapasitesi sorunlarını ayrıca çözmez.

## Düzeltme kodları

### Node.js: Sorguda filtre ve alan seçimi

Önce:

```js
// liste/veri.js, açıklama amaçlı. Kimliği doğrulanmış kiracı sunucudan gelir.
export function listPage(db, tenant, status) {
  if (!['open', 'closed'].includes(status)) throw new Error('Durum geçersiz');
  // Yetki filtresi var ama ekran filtresi ve alan seçimi tarayıcıya bırakılıyor.
  const payload = db.prepare(
    'SELECT * FROM items WHERE tenant_id = ? ORDER BY id',
  ).all(tenant);
  return { payload, status };
}
export function renderRows({ payload, status }) {
  return payload.filter(row => row.status === status)
    .slice(0, 20).map(({ id, title }) => ({ id, title }));
}
// payload HTTP yanıtını, renderRows tarayıcıdaki listeyi temsil eder.
// Ekran doğru görünse de kullanılmayan veri aktarılmıştır.
```

Sonra:

```js
// liste/veri.js, açıklama amaçlı. Kimliği doğrulanmış kiracı sunucudan gelir.
export function listPage(db, tenant, status) {
  if (!['open', 'closed'].includes(status)) throw new Error('Durum geçersiz');
  // Bu örnek ilk sayfadır. Devam sayfası aynı filtrelerle kurulmalıdır.
  const payload = db.prepare(`
    SELECT id, title FROM items
    WHERE tenant_id = ? AND status = ?
    ORDER BY id LIMIT 20
  `).all(tenant, status);
  return { payload, status };
}
export function renderRows({ payload }) {
  return payload.map(({ id, title }) => ({ id, title }));
}
// Yetkilendirme ayrıca korunur. Kullanıcının gönderdiği tenant güvenilir sayılmaz.
// SQL parametreleri değerlerdir, sorgu metnine birleştirilmez.
```

Düzeltmeyi kanıtlayan test:

```js
// liste/veri.test.mjs, açıklama amaçlı. Yanıt ile görünür sonuç ayrı sınanır.
import test from 'node:test';
import assert from 'node:assert/strict';
import { DatabaseSync } from 'node:sqlite';
const { listPage, renderRows } = await import(process.env.ORNEK_DOSYA);
test('aynı ekran yalnız gerekli satır ve alanlarla oluşur', () => {
  const db = new DatabaseSync(':memory:');
  try {
    db.exec(`CREATE TABLE items(id INTEGER PRIMARY KEY, tenant_id TEXT,
      status TEXT, title TEXT, extra TEXT);
      CREATE INDEX items_filter ON items(tenant_id, status, id);`);
    const insert = db.prepare('INSERT INTO items VALUES (?, ?, ?, ?, ?)');
    for (let id = 1; id <= 60; id++) insert.run(id, 'a', id % 2 ? 'closed' : 'open', `Kayıt ${id}`, 'Gereksiz alan');
    insert.run(61, 'b', 'open', 'Başka ekip', 'Diğer');
    const page = listPage(db, 'a', 'open');
    assert.deepEqual(renderRows(page).map(r => r.id), Array.from({ length: 20 }, (_, i) => (i + 1) * 2));
    assert.equal(page.payload.length, 20);
    assert.deepEqual(Object.keys(page.payload[0]).sort(), ['id', 'title']);
    assert.equal(listPage(db, 'missing', 'open').payload.length, 0);
    assert.throws(() => listPage(db, 'a', 'invalid'));
    assert.equal(renderRows(listPage(db, 'b', 'open'))[0].id, 61);
  } finally { db.close(); }
});
```

## Ajan kuralı (AGENTS.md)

```md
## Tarayıcı gereksiz veri indiriyor (vibecheck VC-094)
- Liste filtresini gerçek veri sorgusuna uygula.
- Yanıtta gereken alanları açıkça seç.
- Sonlu sayfa boyutu kullan.
- İstemci filtresini yetki denetimi sayma.
- Ağ yanıtını görünür listeyle birlikte incele.
```

## Kaynaklar

1. [JavaScript Using Filters](https://supabase.com/docs/reference/javascript/using-filters), Supabase
2. [Perform simple and compound queries in Cloud Firestore](https://firebase.google.com/docs/firestore/query-data/queries), Google Firebase
3. [SQLite SELECT](https://www.sqlite.org/lang_select.html), SQLite
4. [CWE-770 Allocation of Resources Without Limits or Throttling](https://cwe.mitre.org/data/definitions/770.html), MITRE

---

vibecheck · Komünite editörlüğü. Metin CC BY 4.0, prompt ve kural parçaları MIT-0. Kaynak: https://vibecheck.komunite.com.tr/madde/tarayici-butun-tabloyu-indirip-kendisi-suzuyor
