---
title: 'Güvenli Kodlama'
source: 'https://academia.sh/tr/kurslar/guvenli-kodlama'
language: tr
updated: '2026-08-17T04:38:38+00:00'
license: 'CC BY-SA 4.0'
lessons: 17
outcomes:
    - 'Güvenilmeyen veri sınırını kodda açıkça uygular ve kapsanan nokta sayısını ölçer'
    - 'Yaygın açık sınıflarını dil ve çerçeve bağımsız savunmalarla kapatır'
    - 'Kriptografi ve sır yönetiminin kötüye kullanımını somut sayıyla gösterir'
---

# Güvenli Kodlama

Bir savunma kalıbının kod tabanındaki kaç noktayı kendiliğinden kapattığı ve kaç noktanın dışarıda kaldığı: izin listesi ile ret listesi temelli doğrulamanın yeni girdi türü karşısındaki farkı, bağlama göre kaçışlamanın dört bağlamda yanlış seçilmesi, parametreli sorgu arayüzünün kapsamadığı nokta, şablon ve ifade enjeksiyonu, dosya yükleme denetimleri, giden isteklerin iç ağa ulaşması, güvensiz seri durumdan çıkarma, kimlik doğrulama gerçekleştiriminde zamanlama ve numaralandırma sızıntıları, oturum sabitleme, nesne düzeyinde yetki atlaması, iş mantığı açıkları, eşzamanlı isteklerle kural atlatma, kriptografi kötüye kullanımı, kriptografik olarak güvenli üreteçler, depo geçmişinde kalan sırlar, hata mesajı sızıntıları ve geçişli bağımlılık derinliği.

## Girdi ve Çıktı Güvenliği

Dışarıdan gelen verinin kod tabanındaki noktalarının sayılması ve savunmanın kapsamasının ölçülmesi: giriş noktası envanteri, izin listesi temelli doğrulamanın kendiliğinden kapattığı nokta, dört çıktı bağlamında kaçışlama, parametreli sorgu arayüzünün kapsamadığı tanımlayıcı yeri, şablona ulaşan kullanıcı verisi, dosya yükleme denetim katmanları, giden istek hedeflerinin iç ağa ulaşması ve seri durumdan çıkarmada güvenilir tür kümesi.

### Girdi Doğrulama Kalıpları

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/girdi-dogrulama-kaliplari

Doğrulamanın kapsadığı nokta sayısının ölçülmesi: bölgesel ölçüm ağının beş giriş noktasındaki on beş alan noktasının her birine kendi bildirimini bozan bir sonda gönderilir, izin listesi temelli doğrulamanın, ret listesinin ve uç başına elle yazılmış denetimin kaç noktayı kapattığı sayılır ve envantere yeni bir uç ile yeni bir alan türü eklendiğinde kapsamanın nasıl değiştiği ölçülür.

Güvenli Geliştirme Yaşam Döngüsü kursu tasarımda hangi tehdidin doğduğunu ve hangi karşı
önlemin planlandığını saydı. O karşı önlemlerin ortak bir özelliği vardı: her biri bir
karardı, hiçbiri bir yer değildi. Karar koda dönüştüğünde tek bir yerde durmaz; kod
tabanındaki her giriş noktasında ayrı ayrı ya durur ya durmaz.

Bu ders o yerleri sayar. Ölçülen şey doğrulamanın nasıl yazıldığı değildir — Sunucu
Güvenliği ve Üretime Alma kursu doğrulama işlevinin kendisini ölçtü. Burada ölçülen şey
**kapsamadır**: kaç nokta var, savunma kaçını örtüyor, kaçı dışarıda kalıyor. Kursun ölçü
birimi son sayıdır.

#### Nokta Envanteri

Bölgesel ölçüm ağı kurgudur: abone portalı, sayaç okuma alım servisi, saha uygulaması ve
rapor üretici. Envanterin birimi uç değil **nokta**: bir ucun bir alanı. Bir uçta üç alan
varsa o uç üç nokta taşır, çünkü savunma alan başına ya vardır ya yoktur.

**GC1 (varsayım):** envanter eksiksizdir. Bütün sayılar bu envantere görelidir ve
envanterde olmayan bir giriş noktası hakkında hiçbir şey söylemez.

```js
// noktalar.mjs — bolgesel olcum aginin giris noktalari (kurgu model)
// Nokta = bir ucun bir alani. Bildirim her noktanin kisitini tek yerde tutar.
export const BILDIRIM = {
  "POST /abone":   { aboneNo: ["kod", /^A-\d{6}$/], ad: ["metin", 60],
                     tarife: ["kume", ["mesken", "ticari", "tarim"]] },
  "GET /fatura":   { aboneNo: ["kod", /^A-\d{6}$/], donem: ["kod", /^\d{4}-\d{2}$/],
                     sayfa: ["sayi", [1, 200]] },
  "POST /okuma":   { sayacNo: ["kod", /^S-\d{7}$/], deger: ["sayi", [0, 999999]],
                     kaynak: ["kume", ["saha", "uzaktan"]] },
  "POST /is-emri": { aboneNo: ["kod", /^A-\d{6}$/], tur: ["kume", ["kesme", "acma", "olcum"]],
                     aciklama: ["metin", 200] },
  "GET /rapor":    { donem: ["kod", /^\d{4}-\d{2}$/], bolge: ["kume", ["kuzey", "guney"]],
                     bicim: ["kume", ["ozet", "ayrintili"]] },
};

export const noktalar = (b = BILDIRIM) =>
  Object.entries(b).flatMap(([uc, a]) => Object.keys(a).map((ad) => [uc, ad]));

const GECERLI = { kod: (k) => (k.test("A-100432") ? "A-100432" : k.test("S-2200145") ? "S-2200145" : "2026-03"),
  metin: () => "olcum notu", kume: (k) => k[0], sayi: ([a]) => a, liste: (k) => [k[0]] };
export const gecerliKayit = (uc, b = BILDIRIM) =>
  Object.fromEntries(Object.entries(b[uc]).map(([ad, [t, k]]) => [ad, GECERLI[t](k)]));

// Sonda: yalniz tek noktanin bildirimini bozar, ayni kayittaki diger noktalar gecerli kalir.
const IHLAL = { kod: () => "A-1", metin: (k) => "x".repeat(k + 1), kume: () => "bilinmeyen",
  sayi: ([, u]) => u + 1, liste: () => ["bilinmeyen"] };
export const sonda = (uc, alan, b = BILDIRIM) =>
  ({ ...gecerliKayit(uc, b), [alan]: IHLAL[b[uc][alan][0]](b[uc][alan][1]) });
```

Sonda kavramı ölçünün tamamını taşır. Bir noktanın kapsanıp kapsanmadığını sormanın tek
yolu, o noktanın **kendi bildirimini** bozan bir değer göndermek ve kaydın reddedilip
reddedilmediğine bakmaktır. Sondadaki değerler zararsızdır: kısa bir tanımlayıcı, bir
karakter fazla metin, kümede olmayan bir sözcük, üst sınırın bir üstündeki sayı.

**GC2 (varsayım):** bir nokta, sondası reddedildiğinde kapsanmış sayılır. Sonda yalnız tek
noktayı bozduğu için ret kararı o noktaya yazılabilir.

#### Üç Savunma Biçimi

Aynı envanterin üzerine üç biçim konur. **İzin listesi temelli doğrulama** kararını
bildirimden türetir: bildirimi olmayan uç da, bildirimde geçmeyen alan da reddedilir.
**Ret listesi temelli doğrulama** adı konmuş biçimleri reddeder ve ucun bildirimini hiç
okumaz. Üçüncüsü dağınık savunmadır: uç başına elle yazılmış denetim.

```js
// dogrulayici.mjs — ayni nokta kumesine bakan uc savunma bicimi
import { BILDIRIM } from "./noktalar.mjs";

// Tur cizelgesi: izin listesinin tanidigi kisit turleri. Yeni tur buraya eklenir.
export const TUR = {
  kod:   (v, s) => typeof v === "string" && s.test(v),
  metin: (v, s) => typeof v === "string" && v.length <= s,
  kume:  (v, s) => s.includes(v),
  sayi:  (v, s) => Number.isInteger(v) && v >= s[0] && v <= s[1],
};

// 1) Izin listesi: karar bildirimden turer. Bildirimsiz uc, bildirimsiz alan ve
//    cizelgede olmayan tur -- ucu de rettir.
export function izinListesi(uc, kayit, b = BILDIRIM, tur = TUR) {
  const alanlar = b[uc];
  if (!alanlar) return "ret";
  for (const [ad, v] of Object.entries(kayit)) {
    const k = alanlar[ad];
    if (!k || !tur[k[0]] || !tur[k[0]](v, k[1])) return "ret";
  }
  return Object.keys(alanlar).every((a) => a in kayit) ? "kabul" : "ret";
}

// 2) Ret listesi: adi konmus bicimler reddedilir; ucun bildirimi hic okunmaz.
const YASAK_ALAN = ["yetki", "rol", "tutar"], EN_COK = 200;
export function retListesi(uc, kayit) {
  for (const [ad, v] of Object.entries(kayit)) {
    if (YASAK_ALAN.includes(ad)) return "ret";
    const m = typeof v === "string" ? v : String(v ?? "");
    if (m.length === 0 || m.length > EN_COK) return "ret";
  }
  return "kabul";
}

// 3) Dagink savunma: uc basina elle yazilmis denetim; uce yazilmis, ikisi bos kalmis.
export function elleYazilmis(uc, kayit) {
  if (uc === "POST /abone") return /^A-\d{6}$/.test(kayit.aboneNo ?? "") ? "kabul" : "ret";
  if (uc === "GET /fatura") return /^A-\d{6}$/.test(kayit.aboneNo ?? "") &&
    Number.isInteger(kayit.sayfa) && kayit.sayfa >= 1 ? "kabul" : "ret";
  if (uc === "POST /okuma") return /^S-\d{7}$/.test(kayit.sayacNo ?? "") ? "kabul" : "ret";
  return "kabul";
}
```

Üç biçim de aynı on beş sondayla yoklanır. Dördüncü sütun bedeli tutar: geçerli bir kayıtla
çağrıldığında hangi ucun durduğu.

```js
// kapsama.mjs — her nokta, kendi bildirimini bozan bir sonda ile yoklanir
import { BILDIRIM, noktalar, sonda, gecerliKayit } from "./noktalar.mjs";
import { izinListesi, retListesi, elleYazilmis } from "./dogrulayici.mjs";

const N = noktalar(), UC = Object.keys(BILDIRIM);
console.log(`${UC.length} giris noktasi, ${N.length} alan noktasi, ${N.length} sonda\n`);
console.log(`${"savunma bicimi".padEnd(24)}${"kapsanan".padStart(9)}${"disarida".padStart(9)}${"duran mesru uc".padStart(16)}`);
for (const [ad, f] of [["izin listesi", izinListesi], ["ret listesi", retListesi],
    ["elle yazilmis denetim", elleYazilmis]]) {
  const disarida = N.filter(([uc, a]) => f(uc, sonda(uc, a)) === "kabul");
  const duran = UC.filter((uc) => f(uc, gecerliKayit(uc)) !== "kabul").length;
  console.log(ad.padEnd(24) + String(N.length - disarida.length).padStart(9) +
    String(disarida.length).padStart(9) + String(duran).padStart(16));
  console.log("  uc basina disarida: " +
    UC.map((uc) => `${uc.split(" ")[1]} ${disarida.filter(([u]) => u === uc).length}`).join("  "));
}
```

```
5 giris noktasi, 15 alan noktasi, 15 sonda

savunma bicimi           kapsanan disarida  duran mesru uc
izin listesi                   15        0               0
  uc basina disarida: /abone 0  /fatura 0  /okuma 0  /is-emri 0  /rapor 0
ret listesi                     1       14               0
  uc basina disarida: /abone 3  /fatura 3  /okuma 3  /is-emri 2  /rapor 3
elle yazilmis denetim           3       12               0
  uc basina disarida: /abone 2  /fatura 2  /okuma 2  /is-emri 3  /rapor 3
```

Ret listesi on beş noktanın **birini** kapatıyor. Kapattığı nokta da tesadüfidir: iş emri
açıklamasının üst sınırı iki yüz karakterdir ve listenin genel uzunluk kuralı da iki yüzdür,
o yüzden sonda takılıyor. Kalan on dört noktada liste hiçbir şey bilmiyor — tanımlayıcı
biçimini, küme üyeliğini ve sayı aralığını tanımıyor, çünkü bunlar noktaya özgüdür ve
listede yazılı değildir. **Ret listesinin bedeli sıfır çıktı; darlığı tam da kapsamasının
düşük olma nedenidir.**

Elle yazılmış denetim üç noktayı kapatıyor ve üçü de aynı sınıftan: abone ve sayaç
tanımlayıcısı. Denetimi yazan kişi her uçta ilk alanı görmüş, ikinci ve üçüncü alanı
görmemiş. İki uçta ise hiçbir denetim yok; oradaki altı nokta baştan dışarıda.

İzin listesinin sıfırı, doğrulamanın daha dikkatli yazılmış olmasından gelmiyor. Bildirim
zaten noktanın kısıtını taşıyordu; doğrulama o bildirimi okuyor. **Kapsama, savunmanın
bildirimle aynı yerden türemesinin sonucudur.**

#### Envanterin Dışı

On beşte on beş, GC1'e bağlı bir sayıdır: envanter eksiksiz sayılmıştı. Bu varsayım
kaldırıldığında kapsama başka bir yerden sızar. Kodda kayıtlı uçların listesi yönlendirme
tablosundadır; bildirimlerin listesi ayrıdır ve ikisi eşit olmak zorunda değildir.

**GC3 (varsayım):** yönlendirme tablosu koddan çıkarılmıştır ve kayıtlı bütün uçları
içerir. Bildirim kümesi ise elle yazılmıştır.

```js
// envanter-farki.mjs — kodda kayitli uclar ile bildirimi olan uclarin farki
import { BILDIRIM } from "./noktalar.mjs";
import { izinListesi } from "./dogrulayici.mjs";

// Yonlendirme tablosu (model): kodda gercekten kayitli uclar ve isleyicinin okudugu alanlar.
const ROTA = {
  "POST /abone": ["aboneNo", "ad", "tarife"], "GET /fatura": ["aboneNo", "donem", "sayfa"],
  "POST /okuma": ["sayacNo", "deger", "kaynak"], "POST /is-emri": ["aboneNo", "tur", "aciklama"],
  "GET /rapor": ["donem", "bolge", "bicim"],
  "POST /okuma/toplu": ["dosyaNo", "satir", "kaynak"],     // bildirimi yok
  "GET /abone/gecmis": ["aboneNo", "baslangic", "bitis"],  // bildirimi yok
};

const bildirimsiz = Object.keys(ROTA).filter((uc) => !BILDIRIM[uc]);
const nokta = (l) => l.reduce((t, uc) => t + ROTA[uc].length, 0);
const SONDA = "x".repeat(5000);   // hicbir alan bildiriminin kabul etmeyecegi uzunluk
const kayit = (uc, bozuk) => Object.fromEntries(ROTA[uc].map((a) => [a, a === bozuk ? SONDA : "deger"]));

console.log(`yonlendirme tablosu ${Object.keys(ROTA).length} uc / ${nokta(Object.keys(ROTA))} nokta, ` +
  `bildirim ${Object.keys(BILDIRIM).length} uc / ${nokta(Object.keys(BILDIRIM))} nokta`);
console.log(`bildirimsiz uc: ${bildirimsiz.join(", ")} -> ${nokta(bildirimsiz)} nokta\n`);

// Bildirimsiz uc icin iki ilke.
const gecir = (uc, k) => (BILDIRIM[uc] ? izinListesi(uc, k) : "kabul");
console.log(`${"bildirimsiz uc ilkesi".padEnd(24)}${"disarida nokta".padStart(15)}${"duran mesru uc".padStart(16)}`);
for (const [ad, f] of [["varsayilan ret", izinListesi], ["varsayilan gecis", gecir]]) {
  const dis = bildirimsiz.flatMap((uc) => ROTA[uc].map((a) => [uc, a]))
    .filter(([uc, a]) => f(uc, kayit(uc, a)) === "kabul").length;
  const duran = bildirimsiz.filter((uc) => f(uc, kayit(uc, null)) !== "kabul").length;
  console.log(ad.padEnd(24) + String(dis).padStart(15) + String(duran).padStart(16));
}
```

```
yonlendirme tablosu 7 uc / 21 nokta, bildirim 5 uc / 15 nokta
bildirimsiz uc: POST /okuma/toplu, GET /abone/gecmis -> 6 nokta

bildirimsiz uc ilkesi    disarida nokta  duran mesru uc
varsayilan ret                        0               2
varsayilan gecis                      6               0
```

Yirmi bir noktanın altısının bildirimi yok. Merkezî kalıp bu altı noktayı **kendi başına
kapatamaz**; kapatabildiği tek şey, bildirimi olmayan bir uca ne yapılacağıdır. Varsayılan
ret altı noktayı da kapatır ve karşılığında iki ucu durdurur — biri toplu okuma yükleme
ucu, biri abone geçmişi. Varsayılan geçiş hiçbir meşru işi durdurmaz ve altı noktayı elde
bırakır.

İki satırın farkı sessizlikte. Üstteki ilkede eksik bildirim ilk çağrıda görülür; alttakinde
hiçbir belirti yoktur. **Kapsamanın kendisi ölçülebilir olduğu için de bu fark yazılabilir:**
iki listenin karşılaştırılması tek bir çıkarma işlemidir ve her değişiklikte tekrarlanabilir.

#### Yeni Nokta Eklendiğinde

Kapsamanın asıl sınavı envanter büyüdüğünde başlar. Sayaç değişim bildirimi için yeni bir
uç açılıyor: önce yalnız bilinen türlerle üç alan, sonra dördüncü alan olarak yeni bir tür
— birden çok değer taşıyan bir liste.

**GC4 (varsayım):** yeni ucun bildirimi elle yazılır ve doğru yazıldığı kabul edilir.
Ölçülen şey bildirimin doğruluğu değil, bildirimin ardından savunmanın kendiliğinden
davranışıdır.

```js
// yeni-nokta.mjs — envantere yeni uc ve yeni alan turu eklenince kapsama ne oluyor
import { BILDIRIM, noktalar, sonda, gecerliKayit } from "./noktalar.mjs";
import { izinListesi, retListesi, elleYazilmis, TUR } from "./dogrulayici.mjs";

const YENI = "POST /sayac/degisim";
const A = { ...BILDIRIM, [YENI]: { sayacNo: ["kod", /^S-\d{7}$/], yeniNo: ["kod", /^S-\d{7}$/],
  neden: ["kume", ["ariza", "sure", "talep"]] } };                      // yalniz bilinen turler
const B = { ...A, [YENI]: { ...A[YENI], ekipman: ["liste", ["muhur", "kapak"]] } };  // yeni tur

// Turu taniyan tek merkezi degisiklik: tur cizelgesine bir satir.
const TUR2 = { ...TUR, liste: (v, s) => Array.isArray(v) && v.every((x) => s.includes(x)) };
const izinV2 = (uc, kayit, b) => izinListesi(uc, kayit, b, TUR2);

const olc = (b, f) => {
  const N = noktalar(b);
  const disarida = N.filter(([uc, a]) => f(uc, sonda(uc, a, b), b) === "kabul").length;
  const duran = Object.keys(b).filter((uc) => f(uc, gecerliKayit(uc, b), b) !== "kabul").length;
  return [N.length, disarida, duran];
};

console.log(`${"asama".padEnd(34)}${"nokta".padStart(6)}${"izin".padStart(6)}${"ret".padStart(5)}${"elle".padStart(6)}${"duran mesru uc".padStart(16)}`);
for (const [ad, b, f] of [["mevcut envanter", BILDIRIM, izinListesi],
    ["yeni uc, bilinen turler", A, izinListesi], ["yeni alan turu eklendi", B, izinListesi],
    ["turu taniyan tek degisiklik", B, izinV2]]) {
  const [n, iz, du] = olc(b, f);
  console.log(ad.padEnd(34) + String(n).padStart(6) + String(iz).padStart(6) +
    String(olc(b, retListesi)[1]).padStart(5) + String(olc(b, elleYazilmis)[1]).padStart(6) +
    String(du).padStart(16));
}
console.log(`\nyeni uc icin yazilan dogrulama kodu: izin listesi 0 satir (3 satir bildirim), ` +
  `elle yazilmis ${Object.keys(B[YENI]).length} denetim`);
```

```
asama                              nokta  izin  ret  elle  duran mesru uc
mevcut envanter                       15     0   14    12               0
yeni uc, bilinen turler               18     0   17    15               0
yeni alan turu eklendi                19     0   18    16               1
turu taniyan tek degisiklik           19     0   18    16               0

yeni uc icin yazilan dogrulama kodu: izin listesi 0 satir (3 satir bildirim), elle yazilmis 4 denetim
```

Orta iki sütun üç aşamada da artıyor: envanter on beşten on dokuza çıkarken ret listesinin
dışarıda bıraktığı nokta on dörtten on sekize, elle yazılmış denetiminki on ikiden on altıya
çıkıyor. **Her yeni nokta, bu iki biçimde doğrudan bir açık adayıdır** — çünkü savunma
noktayı ancak biri onu elle yazarsa görür.

İzin listesi sütunu üç aşamada da sıfır. Yeni uç eklendiğinde tek satır doğrulama kodu
yazılmadı; yalnız bildirim yazıldı ve doğrulama o bildirimi okudu. Kapsama sabit kaldı.

Üçüncü satır bu dersin tek dolu bedel hücresidir. Yeni **tür** eklendiğinde durum farklıdır:
tür çizelgesinde karşılığı olmayan bildirim reddedilir, dolayısıyla o ucun geçerli kaydı da
reddedilir. Kapsama on dokuzda kalır, ama bir uç durur. Bu, güvenli olan yöndeki hatadır ve
gürültülüdür — meşru istek reddedildiği için hemen görülür. Dördüncü satır düzeltmeyi
gösteriyor: tür çizelgesine bir satır, dokunulan başka yer yok.

**Yeni nokta ile yeni tür arasındaki fark kalıcıdır.** Nokta eklemek sık ve dağınık bir
iştir; her uç, her alan, her sürüm. Tür eklemek seyrektir ve merkezî tek bir yerde olur.
Kapsamayı sabit tutan kalıp, sık olan işi hiçbir koda bağlamayan kalıptır.

#### Özet

- Envanterin birimi uç değil noktadır: beş giriş noktası on beş alan noktası taşıyor. Her
  nokta, kendi bildirimini bozan bir sondayla yoklandı; sondaların hiçbiri zararlı değil,
  hepsi sınır değeri.
- Aynı on beş sondada izin listesi 15 noktayı kapattı, ret listesi 1, uç başına elle
  yazılmış denetim 3. Dışarıda kalan nokta sayısı sırasıyla 0, 14 ve 12.
- Ret listesinin kapattığı tek nokta tesadüfidir: genel uzunluk kuralı bir alanın üst
  sınırıyla çakıştı. Elle yazılmış denetimin kapattığı üç nokta da tek sınıftandır ve iki
  uçta hiçbir denetim yoktur.
- Yönlendirme tablosunda 21 nokta var, bildirimde 15; altı nokta envanterin dışında. Merkezî
  kalıp bunları ancak bildirimsiz uca uyguladığı ilkeyle kapatır: varsayılan ret 6 noktayı
  kapatıp 2 ucu durdurur, varsayılan geçiş 6 noktayı elde bırakır ve hiçbir belirti vermez.
- Envanter on beşten on dokuza çıktığında izin listesinin dışarıda bıraktığı nokta 0'da
  kaldı, ret listesininki 18'e, elle yazılmışınki 16'ya çıktı. Yeni uç için yazılan doğrulama
  kodu izin listesinde 0 satırdır.
- Yeni bir alan **türü** eklemek farklıdır: çizelgede karşılığı olmayan tür reddedilir, bir
  uç durur ve bu görülür. Düzeltme tür çizelgesinde tek satırdır. Nokta eklemek sıktır ve
  koda dokunmaz, tür eklemek seyrektir ve tek merkezde olur.

#### Sonraki Adım

Doğrulamayı geçen değer sözleşmeye uygundur; noktanın bildirimi ne diyorsa onu taşır. Bu
değerin sistemden **çıkarken** ne olacağı ayrı bir sorudur. Abone adı bir sayfa gövdesine,
bir öznitelik tırnağının içine, bir betik değişkenine ve bir adres bileşenine yazılıyor;
dördü de aynı değeri alıyor, dördü de farklı bir dilin kuralına tabi. Sonraki ders çıkış
noktalarını sayar ve tek soruyu ölçer: bağlam yanlış seçildiğinde kaç durum bozuluyor ve
tek bir kodlayıcı kaç çıkış noktasını kendiliğinden kapatabiliyor.

### Çıktı Kodlama

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/cikti-kodlama

Bağlama göre kaçışlamanın kapsamasının ölçülmesi: altı meşru abone notu dört çıktı bağlamına dört ayrı kodlayıcıyla yerleştirilir, on altı eşlemede geri okunamayan yerleştirme sayılır, on dört çıkış noktalı bir envanterde çağrı yerinde kodlayıcı seçen biçim ile bağlamdan türeten biçim karşılaştırılır ve iç içe iki bağlamda kodlayıcı sırasının kaç değeri bozduğu ölçülür.

Önceki ders giriş noktalarını saydı ve bildirimden türeyen doğrulamanın on beş noktayı
kendiliğinden kapattığını gösterdi. Doğrulamayı geçen değer artık sözleşmeye uygundur:
abone notu iki yüz karakteri aşmıyor, tarife kümede, dönem biçimli. Bu değerin sistemden
**çıkarken** ne olacağı hakkında doğrulama hiçbir şey söylemez.

Bu ders çıkış noktalarını sayar. Aynı abone notu bir sayfa gövdesine, bir öznitelik
tırnağının içine, bir betik değişkenine ve bir adres bileşenine yazılıyor. Dördü de aynı
değeri alıyor; dördü de o değeri farklı bir kuralla okuyor. Ölçülen şey **bağlamın doğru
seçilip seçilmediği ve kaç çıkış noktasının bu seçimi kendi başına yapmak zorunda
kaldığıdır**.

#### Dört Çıktı Bağlamı

Bir bağlam üç parçadan oluşur: değeri saran **kap**, kabın kendi **kodlayıcısı** ve kabın
kendi **geri okuyucusu**. Ölçü tek sorudur — değer kaba konup kabın kuralıyla geri
okunduğunda aynısı mı çıkıyor.

**GC5 (varsayım):** geri okuma, kabı çözen tarafın kuralını temsil eder. Bir yerleştirme,
geri okuma değeri birebir vermediğinde bozulmuş sayılır; bozulmanın ne kadar zararlı olduğu
ölçülmez.

```js
// baglam.mjs — dort cikti baglami: kap, kendi kodlayicisi ve kabin kendi geri okuyucusu
// Olcu: deger kaba konup kabin kuraliyla geri okundugunda ayni mi cikiyor?
export const DEGER = ["sayac kapagi kirik", 'abone "acele" notu dusmus', "raf 3 & kat 2",
  "olcum < 5 m3", "ilk satir\nikinci satir", "yuzde 5 artis %"];

const govde = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
export const KODLAYICI = {
  govde,
  oznitelik: (s) => govde(s).replace(/"/g, "&quot;").replace(/\n/g, "&#10;"),
  betik: (s) => JSON.stringify(s).slice(1, -1),
  adres: (s) => encodeURIComponent(s),
};

const coz = (s) => s.replace(/&quot;/g, '"').replace(/&#10;/g, "\n")
  .replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");

export const BAGLAM = {
  govde: [(x) => `<not>${x}</not>`, (k) => {
    const i = k.indexOf("<", 5);
    return k.startsWith("<not>") && k.slice(i) === "</not>" ? coz(k.slice(5, i)) : null;
  }],
  oznitelik: [(x) => `<kayit not="${x}"/>`,
    (k) => { const m = /not="([^"]*)"/.exec(k); return m ? coz(m[1]) : null; }],
  betik: [(x) => `const not = "${x}";`, (k) => {
    try { return JSON.parse(k.slice(k.indexOf('"'), k.lastIndexOf('"') + 1)); } catch { return null; }
  }],
  adres: [(x) => `/rapor?not=${x}&bicim=ozet`, (k) => {
    try {
      const a = k.split("?")[1].split("&").find((p) => p.startsWith("not="));
      return a === undefined ? null : decodeURIComponent(a.slice(4));
    } catch { return null; }
  }],
};

// Tek yerlestirme: deger kodlanir, kaba konur, geri okunur.
export const geriOkunur = (baglam, kodlayiciAdi, deger) => {
  const [kap, oku] = BAGLAM[baglam];
  return oku(kap(KODLAYICI[kodlayiciAdi](deger))) === deger;
};
```

Altı değerin hepsi meşru abone ve saha notudur: bir tırnak, bir ve imgesi, bir küçüktür
imgesi, bir satır sonu, bir yüzde imgesi. Hiçbiri saldırı girdisi değildir; hepsi bir
görevlinin yazabileceği metinlerdir.

#### Bağlam Yanlış Seçildiğinde

Dört bağlam ve dört kodlayıcı on altı eşleme verir. Dördü doğru eşlemedir; kalan on ikisi
bağlamın yanlış seçilmesidir.

```js
// secim.mjs — dort baglam x dort kodlayici: baglam yanlis secildiginde kac durum bozuluyor
import { DEGER, KODLAYICI, BAGLAM, geriOkunur } from "./baglam.mjs";

const B = Object.keys(BAGLAM), K = Object.keys(KODLAYICI);
console.log(`${DEGER.length} deger, ${B.length} baglam, ${K.length} kodlayici, ` +
  `${B.length * K.length} esleme, ${DEGER.length * B.length * K.length} yerlestirme\n`);
console.log("bozulan deger sayisi (satir: baglam, sutun: secilen kodlayici)");
console.log("baglam".padEnd(12) + K.map((k) => k.padStart(11)).join(""));
let dogru = 0, yanlis = 0;
for (const b of B) {
  const satir = K.map((k) => DEGER.filter((d) => !geriOkunur(b, k, d)).length);
  K.forEach((k, i) => (k === b ? (dogru += satir[i]) : (yanlis += satir[i])));
  console.log(b.padEnd(12) + satir.map((n) => String(n).padStart(11)).join(""));
}
console.log(`\ndogru baglam secilen ${B.length} eslemede bozulan: ${dogru}`);
console.log(`yanlis baglam secilen ${B.length * K.length - B.length} eslemede bozulan: ${yanlis}`);
```

```
6 deger, 4 baglam, 4 kodlayici, 16 esleme, 96 yerlestirme

bozulan deger sayisi (satir: baglam, sutun: secilen kodlayici)
baglam            govde  oznitelik      betik      adres
govde                 0          0          3          6
oznitelik             1          0          2          6
betik                 4          4          0          6
adres                 3          5          4          0

dogru baglam secilen 4 eslemede bozulan: 0
yanlis baglam secilen 12 eslemede bozulan: 44
```

Köşegen sıfırdır: her bağlam kendi kodlayıcısıyla altı değeri de geri veriyor. Köşegen
dışındaki yetmiş iki yerleştirmenin kırk dördü bozuluyor.

Son sütun bu tablonun en öğretici yeridir. Adres kodlayıcısı hangi bağlama konursa konsun
**altı değerin altısını da** bozuyor. Eksik kaçışlama değil, fazla kaçışlama bu: boşluklar
ve noktalama yüzde imgeli dizilere dönüşüyor ve sayfada okunan not artık yazılan not
değil. Yanlış bağlam iki yönde birden hata üretir; sıkı kodlayıcının zararsız olduğu
varsayımı ölçümde tutmuyor.

Betik satırı ikinci noktadır. Gövde kodlayıcısı betik bağlamında dört değeri bozuyor,
çünkü tırnak ve satır sonu gövde kuralında özel değildir — orada özel olan üç imge
başkadır. Kodlayıcının "daha güvenli" olması yetmez; **kabın kuralıyla aynı kural olması
gerekir**.

#### Çıkış Noktası Envanteri

Bağlam seçimi bir yerde değil, her çıkış noktasında ayrı ayrı yapılır. Bölgesel ölçüm
ağının çıkış noktaları sayılıyor: abone portalı, faturalama, saha uygulaması ve rapor
üretici.

**GC6 (varsayım):** her çıkış noktasının gerçek bağlamı bilinir ve envantere doğru
yazılmıştır. Ölçülen şey çağrı yerinde seçilen kodlayıcının bu bağlamla uyuşup uyuşmadığıdır.

```js
// cikis-noktalari.mjs — cikis noktasi envanteri (kurgu model) ve iki yazma bicimi
import { DEGER, KODLAYICI, BAGLAM } from "./baglam.mjs";

const KOD = { ...KODLAYICI, ham: (s) => s };
const okunur = (b, k, d) => { const [kap, oku] = BAGLAM[b]; return oku(kap(KOD[k](d))) === d; };

// [servis, cikis noktasi, gercek baglam, cagri yerinde secilen kodlayici]
const CIKIS = [
  ["abone portali", "profil basligi", "govde", "govde"],
  ["abone portali", "not listesi", "govde", "govde"],
  ["abone portali", "form onizleme", "oznitelik", "govde"],
  ["abone portali", "arama baglantisi", "adres", "ham"],
  ["abone portali", "durum betigi", "betik", "govde"],
  ["fatura", "dokum satiri", "govde", "govde"],
  ["fatura", "yazdirma basligi", "oznitelik", "oznitelik"],
  ["fatura", "indirme baglantisi", "adres", "adres"],
  ["saha uygulamasi", "is emri karti", "govde", "govde"],
  ["saha uygulamasi", "harita etiketi", "oznitelik", "ham"],
  ["saha uygulamasi", "sunucu cagrisi", "adres", "adres"],
  ["rapor uretici", "ozet govde", "govde", "govde"],
  ["rapor uretici", "baglanti ureteci", "adres", "govde"],
  ["rapor uretici", "gomulu veri betigi", "betik", "betik"],
];
// Yeni cikis noktalari: cagri yerinde kimse kodlayici secmemis.
const YENI = [["saha uygulamasi", "fotograf basligi", "oznitelik", "ham"],
  ["rapor uretici", "dis baglanti", "adres", "ham"], ["abone portali", "bildirim betigi", "betik", "ham"]];

const olc = (liste, secim) => {
  let bozukNokta = 0, bozukYer = 0;
  for (const [, , baglam, elle] of liste) {
    const n = DEGER.filter((d) => !okunur(baglam, secim === "merkezi" ? baglam : elle, d)).length;
    if (n) bozukNokta++;
    bozukYer += n;
  }
  return [bozukNokta, bozukYer];
};

console.log(`${"asama".padEnd(28)}${"nokta".padStart(6)}${"yerlestirme".padStart(12)}` +
  `${"dagink nokta".padStart(14)}${"dagink yer".padStart(12)}${"merkezi".padStart(9)}`);
for (const [ad, l] of [["mevcut envanter", CIKIS], ["uc yeni nokta eklendi", [...CIKIS, ...YENI]]]) {
  const [dn, dy] = olc(l, "elle"), [, my] = olc(l, "merkezi");
  console.log(ad.padEnd(28) + String(l.length).padStart(6) + String(l.length * DEGER.length).padStart(12) +
    String(dn).padStart(14) + String(dy).padStart(12) + String(my).padStart(9));
}
const say = (b) => CIKIS.filter((c) => c[2] === b).length;
console.log("\nbaglam dagilimi: " + Object.keys(BAGLAM).map((b) => `${b} ${say(b)}`).join("  "));
console.log("cagri yerinde secim: dogru " + CIKIS.filter((c) => c[2] === c[3]).length +
  ", yanlis " + CIKIS.filter((c) => c[3] !== "ham" && c[2] !== c[3]).length +
  ", hic yok " + CIKIS.filter((c) => c[3] === "ham").length);
```

```
asama                        nokta yerlestirme  dagink nokta  dagink yer  merkezi
mevcut envanter                 14          84             5          11        0
uc yeni nokta eklendi           17         102             8          16        0

baglam dagilimi: govde 5  oznitelik 3  betik 2  adres 4
cagri yerinde secim: dogru 9, yanlis 3, hic yok 2
```

On dört çıkış noktasının beşi bozuk yazıyor: üçünde yanlış kodlayıcı seçilmiş, ikisinde
hiçbir kodlayıcı yok. Merkezî biçimde bozulma sıfırdır, çünkü kodlayıcı çağrı yerinde
seçilmiyor — noktanın bağlam bildiriminden türüyor. Aradaki fark bilgi farkı değil, **kararın
yeri** farkıdır: dokuz çağrı yeri doğru seçmiş, beşi seçmemiş; merkezî biçimde seçilecek bir
şey yok.

İkinci satır kapsamanın yeni noktada nasıl davrandığını veriyor. Üç yeni çıkış noktası
eklendiğinde dağınık biçimde bozuk nokta beşten sekize çıkıyor — **her yeni çıkış noktası
doğrudan bir bozulma adayıdır**, çünkü kodlayıcıyı yazan biri olmadıkça yoktur. Merkezî
biçim aynı üç noktada sıfırda kalıyor.

#### İç İçe Bağlam

Envanterdeki her nokta tek bir bağlama yazmıyor. Bir öznitelik tırnağının içine bir betik
parçası konduğunda değer iki kabın içindedir ve iki kuralla okunur.

```js
// ic-ice.mjs — bir baglamin icine gomulu ikinci baglam: oznitelik icinde betik baglami
import { DEGER, KODLAYICI } from "./baglam.mjs";

const kap = (x) => `<kayit islem="const not = &quot;${x}&quot;;"/>`;
const coz = (s) => s.replace(/&quot;/g, '"').replace(/&#10;/g, "\n")
  .replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
const oku = (k) => {                    // once dis kap, sonra ic kap kendi kuraliyla okunur
  const m = /islem="([^"]*)"/.exec(k);
  if (!m) return null;
  const kod = coz(m[1]);
  try { return JSON.parse(kod.slice(kod.indexOf('"'), kod.lastIndexOf('"') + 1)); } catch { return null; }
};

console.log(`${DEGER.length} deger, ic ice iki baglam: oznitelik icinde betik\n`);
console.log(`${"kodlayici secimi".padEnd(32)}${"geri okunamayan".padStart(16)}`);
for (const [ad, f] of [["yalniz dis baglam (oznitelik)", KODLAYICI.oznitelik],
    ["yalniz ic baglam (betik)", KODLAYICI.betik],
    ["once dis sonra ic", (d) => KODLAYICI.betik(KODLAYICI.oznitelik(d))],
    ["once ic sonra dis", (d) => KODLAYICI.oznitelik(KODLAYICI.betik(d))]])
  console.log(ad.padEnd(32) + String(DEGER.filter((d) => oku(kap(f(d))) !== d).length).padStart(16));
```

```
6 deger, ic ice iki baglam: oznitelik icinde betik

kodlayici secimi                 geri okunamayan
yalniz dis baglam (oznitelik)                  2
yalniz ic baglam (betik)                       1
once dis sonra ic                              2
once ic sonra dis                              0
```

Tek kodlayıcı iki yönde de yetmiyor: dış bağlamınki iki değeri, iç bağlamınki bir değeri
bozuyor. İki kodlayıcı birlikte kullanıldığında sonucu **sıra** belirliyor. Dıştan içe
gidilirse iki değer bozuk kalır; içten dışa gidilirse altısı da geri okunur.

Bunun kapsama karşılığı doğrudandır. Çıkış noktası başına **tek bir bağlam** tutan bir
bildirim, iç içe noktayı ifade edemez; o noktalar merkezî kalıbın dışında kalır ve karar
yine çağrı yerine döner. Kapsamayı sabit tutmak isteyen bildirim, bağlamı değil **bağlam
dizisini** taşımak zorundadır.

Bağlam dağılımı satırı ayrı bir şey söylüyor: on dört noktanın beşi gövde, dördü adres, üçü
öznitelik, ikisi betik bağlamındadır. Dört bağlamın hepsi kullanılıyor ve hiçbiri baskın
değil. Tek kodlayıcıyla çalışan bir kod tabanının bu dağılımda doğru sonuç vermesi olanaksızdır;
köşegen dışı hücrelerin sayısı doğrudan bu dağılımdan gelir.

#### Kalıbın Yetmediği İkinci Yer

Merkezî kalıp her noktada doğru kodlayıcıyı seçse bile bir varsayıma dayanır: depodan gelen
değer hamdır. Bu varsayım kaldırıldığında kalıp elinde olmayan bir nedenden bozar.

```js
// saklama.mjs — deger depoya kodlanmis girerse merkezi kalip ne yapiyor
import { DEGER, KODLAYICI, BAGLAM } from "./baglam.mjs";

const B = Object.keys(BAGLAM);
const yerlestir = (b, d) => { const [kap, oku] = BAGLAM[b]; return oku(kap(KODLAYICI[b](d))); };
console.log(`${DEGER.length} deger x ${B.length} baglam; merkezi kalip her noktada dogru kodlayiciyi seciyor\n`);
console.log(`${"saklama karari".padEnd(28)}${"bozulan".padStart(9)}  baglam basina`);
for (const [ad, sakla] of [["ham saklanan deger", (d) => d],
    ["giriste bir kez kodlanmis", KODLAYICI.govde]]) {
  const satir = B.map((b) => DEGER.filter((d) => yerlestir(b, sakla(d)) !== d).length);
  console.log(ad.padEnd(28) + String(satir.reduce((a, c) => a + c, 0)).padStart(9) +
    "  " + B.map((b, i) => `${b} ${satir[i]}`).join("  "));
}
```

```
6 deger x 4 baglam; merkezi kalip her noktada dogru kodlayiciyi seciyor

saklama karari                bozulan  baglam basina
ham saklanan deger                  0  govde 0  oznitelik 0  betik 0  adres 0
giriste bir kez kodlanmis           8  govde 2  oznitelik 2  betik 2  adres 2
```

Değer girişte bir kez kodlanıp öyle saklandığında dört bağlamın dördü de ikişer değeri
bozuyor. Kodlayıcı seçimi doğrudur, kap doğrudur, geri okuyucu doğrudur; bozan şey değerin
depoya girerken uğradığı işlemdir. Ve imgesi ile küçüktür imgesi taşıyan iki not, çıkışta
ikinci kez kodlandığı için okuyucuya kaçış dizisiyle varıyor.

Bu bozulmanın kapsama karşılığı ilkinden ağırdır. Çağrı yerindeki yanlış seçim **tek**
noktayı bozar; depodaki yanlış saklama kararı o değeri okuyan **her** noktayı bozar — bu
envanterde on dördünü birden. Kodlamanın yeri çıkıştır ve bunun nedeni ilke değil, sayıdır:
çıkışta karar bağlam başına verilir, girişte tek karar bütün bağlamlara dayatılır.

#### Özet

- Dört bağlam ve dört kodlayıcı on altı eşleme verdi. Doğru eşlemelerdeki 24 yerleştirmenin
  hiçbiri bozulmadı; yanlış bağlam seçilen on iki eşlemedeki 72 yerleştirmenin 44'ü geri
  okunamadı.
- Yanlış bağlam iki yönde birden bozar: adres kodlayıcısı diğer üç bağlamda altı değerin
  altısını da bozdu — eksik kaçışlama değil, fazla kaçışlama. Gövde kodlayıcısı betik
  bağlamında dört değeri bozdu.
- On dört çıkış noktasının 9'unda kodlayıcı doğru seçilmiş, 3'ünde yanlış, 2'sinde hiç
  seçilmemiş; dağınık biçimde 5 nokta ve 11 yerleştirme bozuk. Kodlayıcı bağlam
  bildiriminden türetildiğinde bozulma 0.
- Üç yeni çıkış noktası eklendiğinde dağınık biçimde bozuk nokta 5'ten 8'e çıktı, merkezî
  biçimde 0'da kaldı. Yeni çıkış noktası, kodlayıcısını yazan biri olmadıkça baştan
  bozuktur.
- Değer girişte bir kez kodlanıp öyle saklandığında merkezî kalıp doğru çalıştığı hâlde 8
  yerleştirme bozuldu. Çağrı yerindeki yanlış seçim tek noktayı, depodaki yanlış saklama
  kararı o değeri okuyan 14 noktayı birden bozar.
- İç içe bağlamda tek kodlayıcı yetmedi: dış bağlamınki 2, iç bağlamınki 1 değeri bozdu.
  Sıra belirleyicidir — içten dışa 0, dıştan içe 2. Nokta başına tek bağlam tutan bir
  bildirim bu noktaları kapsayamaz.

#### Sonraki Adım

Çıktı kodlaması değerin **okunacağı** kabı hedef alır. Değerin sistem içinde bir sorgu
metnine konması ayrı bir sorundur: orada kap bir belge değil, bir sorgu dilidir ve o dili
okuyan taraf veriyle komutu aynı metinden ayırmak zorundadır. Sonraki ders sorgu
noktalarını sayar ve tek soruyu ölçer: kaç sorgu değeri ayrı bir işlenen olarak taşıyan bir
arayüzden geçiyor, kaç sorgu değeri metnin kendisine katıyor ve bu iki biçimden hangisi
yeni bir sorgu eklendiğinde noktayı kendiliğinden kapsıyor.

### Enjeksiyon Savunması

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/enjeksiyon-savunmasi

Sorgu noktalarında kapsamanın ölçülmesi: on altı sorgu noktası aynı değer kümesiyle koşturulup değerin sorgu metnine ulaştığı nokta sayılır, parametreli sorgu arayüzünün yapısal olarak kapattığı yer ile tanımlayıcı yerinin neden kapatılamadığı ayrılır ve dört yeni sorgu eklendiğinde katı katmanla esnek katmanın kapsaması karşılaştırılır.

Önceki ders değerin okunacağı kabı hedef aldı: gövde, öznitelik, betik, adres. Dördünde de
kap bir belgeydi ve okuyan taraf bir görüntüleyiciydi. Değerin bir **sorgu metnine** konması
farklı bir sorundur, çünkü orada okuyan taraf veriyle komutu ayırmak zorundadır ve ayrımı
yapacağı tek şey metnin kendisidir.

Bu ders sorgu noktalarını sayar. Sunucu Güvenliği ve Üretime Alma kursu iki kurulumun
ürettiği sonuç farkını ölçmüştü; burada ölçülen şey sonuç değil **kapsamadır**: kaç sorgu
değeri metnin dışında tutuyor, kaçı metne katıyor ve yeni bir sorgu yazıldığında hangisi
noktayı kendiliğinden kapsıyor.

#### Sorgu Metnine Ulaşan Değer

Ölçü tek sorudur: sorgu metni kurulduğunda değerin kendisi o metinde görünüyor mu. Bu soru
bir veritabanı motoru gerektirmez; metin bir dizgedir ve değer o dizgede ya vardır ya yoktur.

**GC7 (varsayım):** sorgu metni modeldir, çalıştıran motor yoktur. Bir nokta, değerlerinden
hiçbiri metinde görünmüyorsa kapsanmış sayılır. Motorun metni nasıl ayrıştırdığı ölçülmez.

```js
// arayuz.mjs — iki sorgu olusturma bicimi. Sorgu metni model, calistiran motor yok.
// Olculen sey tek sorudur: deger sorgu metnine ulasti mi?

// Parametreli sorgu arayuzu: yalniz etiketli sablon kabul eder; dizge kabul etmez.
export function sor(parcalar, ...degerler) {
  if (!Array.isArray(parcalar?.raw)) throw new TypeError("sorgu metni dizge olarak verilemez");
  let metin = parcalar[0];
  const islenen = [];
  degerler.forEach((d, i) => {
    if (d && d.tanimlayici !== undefined) {
      if (d.tanimlayici === null) throw new RangeError("izin listesinde olmayan tanimlayici");
      metin += d.tanimlayici;                    // yer degil, ad: islenen olarak gecirilemez
    } else { metin += "?"; islenen.push(d); }
    metin += parcalar[i + 1];
  });
  return { metin, islenen, denetimli: true };
}

// Elle birlestirme: her sey metne katilir, islenen kalmaz.
export function elle(parcalar, ...degerler) {
  let metin = parcalar[0];
  degerler.forEach((d, i) => {
    metin += String(d && d.tanimlayici !== undefined ? d.ham : d) + parcalar[i + 1];
  });
  return { metin, islenen: [], denetimli: false };
}

// Tanimlayici yeri: alan ve tablo adlari icin izin listesi.
const ALAN = { okuma_zamani: "okuma_zamani", donem: "donem", tutar: "tutar" };
export const ad = (x) => ({ tanimlayici: ALAN[x] ?? null, ham: x });

// Sizinti denetcisi: verilen degerlerden kaci sorgu metninde gorunuyor.
export const sizinti = (s, degerler) =>
  degerler.filter((d) => s.metin.includes(String(d))).length;
```

İki biçimin yazılışı neredeyse aynıdır — ikisi de etiketli şablon alır, ikisi de aynı sorguyu
kurar. Ayrıştıkları tek yer değerin nereye gittiğidir: biri metne yer tutucu koyup değeri
ayrı bir diziye yazar, öteki değeri metnin içine katar.

#### Sorgu Noktası Envanteri

Bölgesel ölçüm ağının sorgu noktaları sayılıyor: abone, faturalama, okuma, iş emri ve rapor
servisleri. Her nokta bir sorgu şeklini bir arayüzle yazar.

```js
// kapsama.mjs — sorgu noktasi envanteri (kurgu model) ve iki bicimin kapsamasi
import { sor, elle, ad, sizinti } from "./arayuz.mjs";

// Dort sorgu sekli; her sekil ayni degerleri alir, tek fark hangi arayuzle yazildigi.
const SEKIL = {
  ara:    (b, v) => b`SELECT no FROM abone WHERE bolge = ${v.bolge} AND tarife = ${v.tarife}`,
  getir:  (b, v) => b`SELECT * FROM fatura WHERE abone = ${v.abone} AND donem = ${v.donem}`,
  sirala: (b, v) => b`SELECT * FROM okuma ORDER BY ${ad(v.alan)} LIMIT ${v.limit}`,
  say:    (b, v) => b`SELECT count(*) FROM is_emri WHERE tur = ${v.tur}`,
};
const V = { bolge: "kuzey", tarife: "mesken", abone: "A-100432", donem: "2026-03",
  alan: "okuma_zamani", limit: 50, tur: "kesme" };
const DEGERI = { ara: ["kuzey", "mesken"], getir: ["A-100432", "2026-03"], sirala: [50], say: ["kesme"] };

// [servis, sorgu noktasi, sekil, arayuz]
const NOKTA = [
  ["abone", "bolgeAra", "ara", sor], ["abone", "tarifeAra", "ara", sor],
  ["abone", "gecmisAra", "ara", elle], ["fatura", "donemGetir", "getir", sor],
  ["fatura", "borcGetir", "getir", sor], ["fatura", "arsivGetir", "getir", elle],
  ["okuma", "sonOkuma", "sirala", sor], ["okuma", "sayacGecmisi", "sirala", sor],
  ["okuma", "toplamaRaporu", "sirala", elle], ["okuma", "aykiriOkuma", "ara", sor],
  ["is-emri", "acikSay", "say", sor], ["is-emri", "bolgeSay", "say", sor],
  ["is-emri", "gecmisSay", "say", elle], ["rapor", "ozetGetir", "getir", sor],
  ["rapor", "dokumSirala", "sirala", elle], ["rapor", "aylikSay", "say", sor],
];

const olc = (liste) => {
  let ulasan = 0, tanimlayiciYeri = 0, denetimsiz = 0;
  for (const [, , sekil, b] of liste) {
    const s = SEKIL[sekil](b, V);
    ulasan += sizinti(s, DEGERI[sekil]);
    if (sekil === "sirala") { tanimlayiciYeri++; if (!s.denetimli) denetimsiz++; }
  }
  return [ulasan, tanimlayiciYeri, denetimsiz];
};

const P = NOKTA.filter((n) => n[3] === sor), E = NOKTA.filter((n) => n[3] === elle);
console.log(`${NOKTA.length} sorgu noktasi, ${Object.keys(SEKIL).length} sorgu sekli\n`);
console.log(`${"bicim".padEnd(22)}${"nokta".padStart(6)}${"deger yeri".padStart(11)}` +
  `${"metne ulasan".padStart(13)}${"tanimlayici yeri".padStart(17)}${"denetimsiz".padStart(11)}`);
for (const [adi, l] of [["parametreli arayuz", P], ["elle birlestirme", E]]) {
  const yer = l.reduce((t, n) => t + DEGERI[n[2]].length, 0);
  const [u, t, d] = olc(l);
  console.log(adi.padEnd(22) + String(l.length).padStart(6) + String(yer).padStart(11) +
    String(u).padStart(13) + String(t).padStart(17) + String(d).padStart(11));
}
console.log("\nornek metin (parametreli): " + SEKIL.getir(sor, V).metin);
console.log("ornek metin (elle)       : " + SEKIL.getir(elle, V).metin);
```

```
16 sorgu noktasi, 4 sorgu sekli

bicim                  nokta deger yeri metne ulasan tanimlayici yeri denetimsiz
parametreli arayuz        11         17            0                2          0
elle birlestirme           5          7            7                2          2

ornek metin (parametreli): SELECT * FROM fatura WHERE abone = ? AND donem = ?
ornek metin (elle)       : SELECT * FROM fatura WHERE abone = A-100432 AND donem = 2026-03
```

On altı noktanın on biri parametreli arayüzden geçiyor ve o on birde on yedi değer yerinin
**hiçbiri** metne ulaşmıyor. Beş nokta elle birleştiriyor ve orada yedi değer yerinin yedisi
de metnin içinde. Dışarıda kalan nokta sayısı beştir.

Bu beşin ortak bir özelliği var: üçü arşiv ve geçmiş sorgusu, ikisi rapor. Ana akış
korunmuş, kenar akışlar korunmamış — çünkü karar nokta başına verilmiş ve kenar akışları
yazan kişi başka bir kişi ya da başka bir zaman.

Aynı ölçü komut noktaları için de geçerlidir: değer alt sürece ayrı bir argüman olarak mı
geçiyor, yoksa tek bir komut dizgesine mi katılıyor. Soru aynı sorudur; ölçüsü de aynıdır —
değer metne ulaştı mı.

Sayının nasıl elde edildiği de kapsamanın bir parçasıdır. Sızıntı denetçisi burada metni
tarıyor, ama arayüzün döndürdüğü `denetimli` bayrağı aynı bilgiyi taramadan veriyor: her
sorgu sonucu hangi biçimle kurulduğunu kendisi taşıyor. Kapsamanın sayılabilir olması,
savunmanın kendisinin sayı üretmesine bağlıdır — dışarıdan bakan bir tarayıcı sorgu metnini
görür, o metnin nasıl kurulduğunu görmez.

#### Kapatılamayan Yer

Tabloda parametreli arayüzün sıfırlamadığı tek sütun `tanimlayici yeri`. İki nokta bir alan
adını sorgunun içine koyuyor ve bir alan adı yer tutucuyla geçirilemez: yer tutucu bir
**değeri** temsil eder, sorgunun yapısını değil. Sıralama alanı, tablo adı ve yön sözcüğü
metnin kendisine yazılmak zorundadır.

Parametreli arayüz burada kapsama sağlayamaz; sağladığı tek şey bu yerin **görünür** olmasıdır.
Kod `ad()` çağrısıyla işaretlendiği için tanımlayıcı yerleri sayılabilir hâle gelir ve orada
tek savunma ilk dersin kalıbıdır: izin listesi. Aşağıdaki blok üç kararı yan yana koyuyor.

```js
// tanimlayici.mjs — tanimlayici yerinde uc karar: elle, izin listesi, yer tutucu denemesi
import { sor, elle, ad } from "./arayuz.mjs";

const GIRDI = ["okuma_zamani", "donem", "tutar", "abone_no", "okuma_zamani desc", "ic_not"];
const kuyruk = (f) => { try { return f().metin.split("ORDER BY ")[1]; } catch (e) { return e.constructor.name; } };

console.log(`${GIRDI.length} siralama alani girdisi, tek tanimlayici yeri\n`);
console.log(`${"girdi".padEnd(20)}${"elle".padStart(20)}${"izin listesi".padStart(14)}${"yer tutucu".padStart(12)}`);
let ham = 0, kabul = 0, bozuk = 0;
for (const g of GIRDI) {
  const e = kuyruk(() => elle`SELECT * FROM okuma ORDER BY ${ad(g)}`);
  const i = kuyruk(() => sor`SELECT * FROM okuma ORDER BY ${ad(g)}`);
  const y = kuyruk(() => sor`SELECT * FROM okuma ORDER BY ${g}`);
  if (e === g) ham++;
  if (i === g) kabul++;
  if (y !== g) bozuk++;
  console.log(g.padEnd(20) + e.padStart(20) + i.padStart(14) + y.padStart(12));
}
console.log(`\nelle: ${ham}/${GIRDI.length} girdi metne oldugu gibi girdi`);
console.log(`izin listesi: ${kabul} kabul, ${GIRDI.length - kabul} ret`);
console.log(`yer tutucu ile gecirme: ${bozuk}/${GIRDI.length} sorgunun yapisi bozuldu`);
```

```
6 siralama alani girdisi, tek tanimlayici yeri

girdi                               elle  izin listesi  yer tutucu
okuma_zamani                okuma_zamani  okuma_zamani           ?
donem                              donem         donem           ?
tutar                              tutar         tutar           ?
abone_no                        abone_no    RangeError           ?
okuma_zamani desc      okuma_zamani desc    RangeError           ?
ic_not                            ic_not    RangeError           ?

elle: 6/6 girdi metne oldugu gibi girdi
izin listesi: 3 kabul, 3 ret
yer tutucu ile gecirme: 6/6 sorgunun yapisi bozuldu
```

Son sütun kapatılamazlığın kanıtıdır: girdi bir değer gibi geçirildiğinde altı sorgunun
altısında da metin `ORDER BY ?` oluyor ve sıralama alanı sorgudan siliniyor. Yer tutucu bu
yerde savunma değil, hatadır.

Ortadaki sütun tek çözümü veriyor. Altı girdinin üçü izin listesinde ve olduğu gibi geçiyor;
üçü reddediliyor — biri şemada olmayan bir alan, biri yön sözcüğü eklenmiş bir alan, biri
listede bulunmayan bir ad. Elle birleştirmede altısı da sorguya giriyor. **Tanımlayıcı
yerinin kapsaması parametreli arayüzden değil, izin listesinin uzunluğundan gelir** ve
listede olmayan meşru bir alan da reddedilir — bu yerin bedeli budur.

#### Yeni Sorgu Eklendiğinde

Kapsamanın sınavı yine envanter büyüdüğünde başlar. Dört yeni sorgu noktası açılıyor ve iki
ayrı erişim katmanında yazılıyor: biri yalnız parametreli arayüzü sunuyor, öteki sorgu
metnini dizge olarak da kabul ediyor.

**GC8 (varsayım):** esnek katmanda yeni sorguyu yazan kişinin hangi biçimi seçeceği
bilinmez; ölçülen şey katmanın **izin verdiği** en kötü durumdur. Katı katmanda böyle bir
durum yoktur, çünkü seçenek yoktur.

```js
// yeni-sorgu.mjs — yeni sorgu eklendiginde hangi bicim noktayi kendiliginden kapsiyor
import { sor, elle, ad, sizinti } from "./arayuz.mjs";

const dene = (f) => { try { return "gecti -> " + f().metin; } catch (e) { return e.constructor.name + ": " + e.message; } };
console.log("dizge olarak verilen sorgu   : " + dene(() => sor("SELECT no FROM abone WHERE bolge = 'kuzey'")));
console.log("izin listesi disi tanimlayici: " + dene(() => sor`SELECT * FROM okuma ORDER BY ${ad("ic_not")}`));
console.log("ayni tanimlayici, elle       : " + dene(() => elle`SELECT * FROM okuma ORDER BY ${ad("ic_not")}`));

// Dort yeni sorgu noktasi; ayni sekiller, yalniz arayuz degisiyor.
const YENI = [
  ["eksikOkuma", (b, v) => b`SELECT no FROM okuma WHERE kaynak = ${v.kaynak}`, ["saha"]],
  ["borcluAra", (b, v) => b`SELECT abone FROM fatura WHERE tutar > ${v.tutar}`, [1200]],
  ["isEmriFiltre", (b, v) => b`SELECT * FROM is_emri WHERE tur = ${v.tur} AND bolge = ${v.bolge}`, ["acma", "guney"]],
  ["gunlukDokum", (b, v) => b`SELECT * FROM okuma ORDER BY ${ad(v.alan)} LIMIT ${v.limit}`, [100]],
];
const V = { kaynak: "saha", tutar: 1200, tur: "acma", bolge: "guney", alan: "donem", limit: 100 };
const yer = YENI.reduce((t, [, , d]) => t + d.length, 0);

console.log(`\n${YENI.length} yeni sorgu noktasi, ${yer} deger yeri\n`);
console.log(`${"katman bicimi".padEnd(34)}${"metne ulasan".padStart(13)}${"denetimsiz nokta".padStart(18)}`);
for (const [baslik, b] of [["kati katman (yalniz parametreli)", sor], ["esnek katman (dizge de kabul)", elle]]) {
  const ulasan = YENI.reduce((t, [, f, d]) => t + sizinti(f(b, V), d), 0);
  const denetimsiz = YENI.filter(([, f]) => !f(b, V).denetimli).length;
  console.log(baslik.padEnd(34) + String(ulasan).padStart(13) + String(denetimsiz).padStart(18));
}
```

```
dizge olarak verilen sorgu   : TypeError: sorgu metni dizge olarak verilemez
izin listesi disi tanimlayici: RangeError: izin listesinde olmayan tanimlayici
ayni tanimlayici, elle       : gecti -> SELECT * FROM okuma ORDER BY ic_not

4 yeni sorgu noktasi, 5 deger yeri

katman bicimi                      metne ulasan  denetimsiz nokta
kati katman (yalniz parametreli)              0                 0
esnek katman (dizge de kabul)                 5                 4
```

İlk üç satır arayüzün kendi sınırlarını gösteriyor. Sorgu metni dizge olarak verilemez;
verilirse çağrı ilk satırda durur. İzin listesinde olmayan bir alan adı da geçmez. Aynı alan
adı elle birleştirmede sorgunun içine giriyor ve hiçbir belirti vermiyor.

Alt tablo asıl sayıyı veriyor. Katı katmanda dört yeni sorgunun beş değer yerinden hiçbiri
metne ulaşmıyor ve bunun nedeni dikkat değil, **başka bir yol olmaması**. Esnek katmanda beş
değer yerinin beşi de metne ulaşabiliyor; katman bunu engellemiyor, yalnız izin veriyor.

Fark burada bir alışkanlık farkı değildir. Katı katmanda kapsama **kod yazılmadan** sabittir;
esnek katmanda her yeni sorgu, doğru biçimi seçen bir kişiye bağlıdır ve on altı noktalık
envanterde bu kişi beş kez başka bir şey seçti.

#### Özet

- Ölçü tek sorudur: sorgu metni kurulduğunda değer o metinde görünüyor mu. Bu soru
  veritabanı motoru gerektirmez; sorgu metni bir dizgedir.
- On altı sorgu noktasının 11'i parametreli arayüzden geçiyor ve orada 17 değer yerinin 0'ı
  metne ulaşıyor; 5 nokta elle birleştiriyor ve orada 7 değer yerinin 7'si metnin içinde.
  Dışarıda kalan nokta sayısı 5'tir ve beşi de kenar akıştır.
- Parametreli arayüzün kapatamadığı bir yer var: tanımlayıcı yeri. Alan adı bir yer
  tutucuyla geçirilemez, çünkü yer tutucu değeri temsil eder, yapıyı değil. Envanterde 2
  böyle nokta var. Altı sıralama girdisi değer gibi geçirildiğinde 6 sorgunun yapısı bozuldu;
  izin listesi 3'ünü kabul, 3'ünü ret etti; elle birleştirmede 6'sı da sorguya girdi.
- Arayüz sorgu metnini dizge olarak kabul etmiyor ve izin listesi dışı tanımlayıcıyı
  reddediyor; ikisi de çağrı anında durur. Elle birleştirmede aynı tanımlayıcı sorguya girer
  ve hiçbir belirti vermez.
- Dört yeni sorguda katı katman 0 değer yeri sızdırdı, esnek katman 5. Katı katmanda kapsama
  kod yazılmadan sabittir; esnek katmanda her yeni sorgu bir karara bağlıdır.

#### Sonraki Adım

Parametreli arayüz değeri metnin dışında tutar; tanımlayıcı yerinde ise izin listesi devreye
girer. İkisi de metni **sabit** kabul eder: sorgunun iskeleti geliştiricinin yazdığı şeydir.
Bu varsayımın tutmadığı bir yer var. Bildirim metinleri, rapor başlıkları ve fatura açıklama
satırları şablonlardan üretiliyor ve bu şablonların bir kısmı çalışma zamanında oluşturuluyor
— kimi yalnız değer alıyor, kimi gövdesine kullanıcı verisi alıyor. Sonraki ders şablonları
sayar ve tek soruyu ölçer: kaç şablon kullanıcı verisini yalnız değer olarak alıyor, kaçı o
veriyi sunucuda değerlendirilen bir ifadenin parçası hâline getiriyor.

### Şablon ve İfade Enjeksiyonu

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/sablon-ve-ifade-enjeksiyonu

Sunucu tarafı değerlendirme riskinin yüzey olarak ölçülmesi: değerlendirme ortamında çözülebilen ad sayısı ortamın kendisinden üretilen sondalarla sayılır, on iki şablonlu bir envanterde kaçının kullanıcı verisini yalnız değer olarak, kaçının ifade olarak aldığı ayrılır ve gövdesi dışarıdan gelen şablonların toplam değerlendirme yüzeyi üç kurulumda karşılaştırılır.

Önceki ders sorgu metnini sabit kabul etti: iskeleti geliştirici yazar, değer dışarıdan
gelir. Bildirim metinleri, rapor başlıkları ve saha etiketleri bu varsayımı taşımıyor.
Bunların gövdesi bir şablondur ve şablonların bir kısmı çalışma zamanında bir yapılandırma
kaydından ya da bir kullanıcı alanından okunur.

Bu ders şablonları sayar. Ölçülen büyüklük metnin bozulup bozulmadığı değil, **değerlendirme
yüzeyidir**: şablon işlenirken kaç ad çözülebiliyor. Bir şablon kullanıcı verisini yalnız
**değer** olarak alıyorsa yüzey bildirilen alanlarla sınırlıdır; veriyi **ifade** olarak
alıyorsa yüzey ortamın tamamıdır.

#### İki Motor, Tek Ortam

Model iki motor içerir. **Değer motoru** yalnız bildirilen adları yerine koyar ve sonucu bir
daha taramaz. **İfade motoru** noktalı bir yolu ortamda çözer; bildirim diye bir şeyi yoktur.
Ortam, şablon işlenirken erişilebilen nesne kümesidir.

**GC9 (varsayım):** ortam ve motorlar modeldir. Yüzey, ortamda noktalı yolla çözülebilen
yaprak ad sayısıdır; adın taşıdığı değerin ne kadar duyarlı olduğu ölçülmez.

```js
// motor.mjs — iki sablon motoru ve degerlendirme ortami (kurgu model)
// Deger motoru: yalniz bildirilen adlari yerine koyar, sonuc bir daha taranmaz.
export const degerMotoru = (sablon, ortam, bildirilen) =>
  sablon.replace(/\{([\w.]+)\}/g, (t, yol) =>
    bildirilen.includes(yol) ? String(yol.split(".").reduce((o, k) => o?.[k], ortam)) : t);

// Ifade motoru: noktali yolu ortamda cozer; bildirim diye bir sey yoktur.
export const ifadeMotoru = (sablon, ortam) =>
  sablon.replace(/\{([\w.]+)\}/g, (t, yol) => {
    const v = yol.split(".").reduce((o, k) => o?.[k], ortam);
    return v === undefined || typeof v === "object" ? t : String(v);
  });

// Erisilebilir ad: ortamda noktali yolla cozulebilen yaprak adlarin listesi.
export const erisilebilir = (o, onek = "") =>
  o === null || typeof o !== "object" ? [onek]
    : Object.entries(o).flatMap(([k, v]) => erisilebilir(v, onek ? `${onek}.${k}` : k));

export const GENIS = {
  abone: { ad: "A. Yilmaz", no: "A-100432", tarife: "mesken" },
  fatura: { donem: "2026-03", tutar: 412, sonGun: "2026-04-15" },
  ayar: { bolge: "kuzey", surum: "3", gunlukSeviye: "bilgi", sunucuAdi: "olcum-1" },
  baglanti: { depoAdi: "olcum", rolAdi: "rapor_okuyucu", zamanAsimi: 30 },
  sistem: { calismaDizini: "/srv/olcum", islemNo: 4120 },
};
export const DAR = { abone: GENIS.abone, fatura: GENIS.fatura };
export const BILDIRILEN = ["abone.ad", "abone.no", "fatura.donem", "fatura.tutar", "fatura.sonGun"];
```

Geniş ortam bir uygulamanın şablon işleyicisine elinin altındaki her şeyi vermesini temsil
eder: abone ve fatura verisinin yanında ayarlar, bağlantı bilgileri ve süreç bilgisi. Dar
ortam yalnız bildirimin gerektirdiği iki nesneyi taşır.

İfade motorunun varlığı bir kusur değil, bir istektir. Bildirim metnini yöneticinin
düzenleyebilmesi, rapor başlığına bölge adının girebilmesi ve saha etiketinin şablondan
üretilmesi işin gerçek gereksinimleridir. Değerlendirme yüzeyi bu gereksinimin sayısal
karşılığıdır: metni dışarıdan düzenlenebilir yapmak, o metni işleyen ortamı da dışarıdan
sorgulanabilir yapar. Ölçülmesi gereken şey bu takasın büyüklüğüdür.

#### Değerlendirme Yüzeyi

Yüzey sayılırken sonda kümesi elle yazılmaz; **ortamın kendisinden üretilir**. Her erişilebilir
ad için tek bir yerleştirme denenir ve kaçının çözüldüğü sayılır. Böylece ölçüm, ortam
değiştiğinde kendiliğinden güncellenir.

```js
// yuzey.mjs — degerlendirme yuzeyi: ortamda kac ad cozulebiliyor
import { degerMotoru, ifadeMotoru, erisilebilir, GENIS, DAR, BILDIRILEN } from "./motor.mjs";

// Sonda kumesi ortamin kendisinden uretilir: her erisilebilir ad icin tek yerlestirme.
const sondalar = (ortam) => erisilebilir(ortam).map((yol) => [yol, `{${yol}}`]);

console.log(`bildirilen ad: ${BILDIRILEN.length}\n`);
console.log(`${"ortam / motor".padEnd(22)}${"erisilebilir ad".padStart(16)}${"cozulen".padStart(9)}${"bildirim disi".padStart(15)}`);
for (const [adi, ortam, motor] of [["genis / ifade", GENIS, "ifade"], ["genis / deger", GENIS, "deger"],
    ["dar / ifade", DAR, "ifade"], ["dar / deger", DAR, "deger"]]) {
  const s = sondalar(ortam);
  const uygula = (x) => (motor === "ifade" ? ifadeMotoru(x, ortam) : degerMotoru(x, ortam, BILDIRILEN));
  const cozulen = s.filter(([, x]) => uygula(x) !== x);
  const disi = cozulen.filter(([yol]) => !BILDIRILEN.includes(yol)).length;
  console.log(adi.padEnd(22) + String(s.length).padStart(16) + String(cozulen.length).padStart(9) +
    String(disi).padStart(15));
}
const dis = erisilebilir(GENIS).filter((y) => !BILDIRILEN.includes(y));
console.log(`\ngenis ortamda bildirim disi kalan ad: ${dis.length}`);
console.log(`ust duzey ad kumesi: genis ${Object.keys(GENIS).join(" ")} / dar ${Object.keys(DAR).join(" ")}`);
```

```
bildirilen ad: 5

ortam / motor          erisilebilir ad  cozulen  bildirim disi
genis / ifade                       15       15             10
genis / deger                       15        5              0
dar / ifade                          6        6              1
dar / deger                          6        5              0

genis ortamda bildirim disi kalan ad: 10
ust duzey ad kumesi: genis abone fatura ayar baglanti sistem / dar abone fatura
```

Dört satır iki bağımsız savunmayı ayırıyor. **Ortamı daraltmak** erişilebilir adı on beşten
altıya indiriyor; ifade motoru hâlâ hepsini çözüyor, ama çözülecek ad kalmıyor. **Motoru
değere sabitlemek** ortamdan bağımsız olarak çözülen adı bildirilen kümeye indiriyor: geniş
ortamda bile bildirim dışı çözülen ad sıfır.

Son satır ikisinin birlikte anlamını veriyor. Dar ortamda değer motoru beş ad çözüyor,
ifade motoru altı — aradaki tek ad `abone.tarife`, bildirimde geçmeyen bir alan. Yüzey
sıfırlanmıyor; **daralıyor ve sayılabilir kalıyor**.

#### Şablon Envanteri

Yüzey şablon başına ölçülür ve tek bir şey belirler: şablonun **gövdesi** nereden geliyor.
Gövde sabitse çözülecek adları geliştirici yazmıştır ve dışarıdan gelen bir ad yoktur.
Gövde bir yapılandırma kaydından ya da kullanıcı alanından okunuyorsa çözülebilen her ad o
şablonun yüzeyidir.

**GC10 (varsayım):** gövdesi sabit olan şablonun yüzeyi sıfır sayılır. Ölçülen şey şablonun
kim tarafından yazıldığı değil, **nereden okunduğudur**.

```js
// envanter.mjs — sablon envanteri (kurgu model) ve toplam degerlendirme yuzeyi
import { erisilebilir, GENIS, DAR, BILDIRILEN } from "./motor.mjs";

const ORTAM = { genis: GENIS, dar: DAR };
// [servis, sablon, govde kaynagi, motor, ortam]
const SABLON = [
  ["bildirim", "faturaKesildi", "sabit", "deger", "dar"],
  ["bildirim", "sonGunYaklasti", "sabit", "deger", "dar"],
  ["bildirim", "okumaEksik", "sabit", "deger", "dar"],
  ["fatura", "dokumBasligi", "sabit", "deger", "dar"],
  ["fatura", "aciklamaSatiri", "sabit", "deger", "dar"],
  ["is-emri", "gorevMetni", "sabit", "deger", "dar"],
  ["is-emri", "kapanisNotu", "sabit", "deger", "dar"],
  ["bildirim", "kampanyaMetni", "yapilandirma", "ifade", "genis"],
  ["rapor", "ustBilgi", "yapilandirma", "ifade", "genis"],
  ["rapor", "altBilgi", "yapilandirma", "deger", "dar"],
  ["rapor", "ozetBasligi", "kullanici", "ifade", "genis"],
  ["saha", "etiketMetni", "kullanici", "ifade", "genis"],
];
const YENI = [["rapor", "bolgeBasligi", "yapilandirma"], ["bildirim", "hatirlatma", "yapilandirma"],
  ["saha", "formBasligi", "yapilandirma"]];

// Yuzey: govde sabitse cozulecek ad kumesi zaten sablonun icindedir.
const yuzey = ([, , kaynak, motor, o]) =>
  kaynak === "sabit" ? 0 : motor === "ifade" ? erisilebilir(ORTAM[o]).length : BILDIRILEN.length;
const topla = (l) => l.reduce((t, s) => t + yuzey(s), 0);
const say = (l, i, v) => l.filter((s) => s[i] === v).length;

console.log(`${SABLON.length} sablon, hepsi kullanici verisi aliyor`);
console.log(`  motor: deger ${say(SABLON, 3, "deger")}, ifade ${say(SABLON, 3, "ifade")}`);
console.log(`  govde kaynagi: sabit ${say(SABLON, 2, "sabit")}, yapilandirma ` +
  `${say(SABLON, 2, "yapilandirma")}, kullanici ${say(SABLON, 2, "kullanici")}\n`);

const KURULUM = [
  ["mevcut durum", (s) => s],
  ["kalip: deger motoru + dar ortam", ([a, b, k]) => [a, b, k, "deger", "dar"]],
  ["kalip: govde de sabit", ([a, b]) => [a, b, "sabit", "deger", "dar"]],
];
console.log(`${"kurulum".padEnd(34)}${"sablon".padStart(8)}${"disaridan govde".padStart(17)}${"toplam yuzey".padStart(14)}`);
for (const [adi, d] of KURULUM) {
  const l = SABLON.map(d), y = YENI.map(([a, b, k]) => d([a, b, k, "ifade", "genis"]));
  console.log(adi.padEnd(34) + String(l.length).padStart(8) +
    String(l.length - say(l, 2, "sabit")).padStart(17) + String(topla(l)).padStart(14));
  console.log(`  uc yeni sablon eklendikten sonra: ${l.length + y.length} sablon, yuzey ${topla([...l, ...y])}`);
}
```

```
12 sablon, hepsi kullanici verisi aliyor
  motor: deger 8, ifade 4
  govde kaynagi: sabit 7, yapilandirma 3, kullanici 2

kurulum                             sablon  disaridan govde  toplam yuzey
mevcut durum                            12                5            65
  uc yeni sablon eklendikten sonra: 15 sablon, yuzey 110
kalip: deger motoru + dar ortam         12                5            25
  uc yeni sablon eklendikten sonra: 15 sablon, yuzey 40
kalip: govde de sabit                   12                0             0
  uc yeni sablon eklendikten sonra: 15 sablon, yuzey 0
```

On iki şablonun hepsi kullanıcı verisi alıyor; bu tek başına bir sayı taşımıyor. Ayrım
sekizinin veriyi yalnız değer olarak, dördünün ifade olarak almasındadır. Ve asıl ayrım
gövde kaynağındadır: yedi şablonun gövdesi sabit, üçü yapılandırmadan, ikisi doğrudan
kullanıcı alanından okunuyor.

Toplam yüzey altmış beş. Bu sayının tamamı beş şablondan geliyor; kalan yedisi hiç katkı
yapmıyor. **Yüzey şablon sayısıyla değil, gövdesi dışarıdan gelen şablon sayısıyla artıyor.**

Beş şablonun ikisi ayrı bir sınıftır. Rapor özet başlığı ve saha etiket metni gövdelerini
bir yapılandırma kaydından değil, doğrudan bir kullanıcı alanından alıyor: kullanıcının
yazdığı başlık, şablonun gövdesi olarak işleniyor. Yapılandırmadan gelen gövdeyi düzenleyen
kişi sayısı sınırlıdır ve o kayıt bir onay akışından geçebilir; kullanıcı alanından gelen
gövde için böyle bir sınır yoktur. İkisinin yüzeyi aynı — on beşer ad — ama o yüzeye
ulaşabilen kişi sayısı aynı değildir.

İkinci kurulum kalıbın kazancını veriyor: bütün şablonlar değer motoruna ve dar ortama
alındığında yüzey altmış beşten yirmi beşe iniyor. Sıfıra inmiyor, çünkü gövde hâlâ
dışarıdan okunuyor ve bildirilen beş ad her birinde çözülebiliyor. Üçüncü kurulumda gövde de
sabitleniyor ve yüzey sıfırlanıyor — ama bu kurulumda yönetici bildirim metnini artık
düzenleyemez. Bedel budur ve yazılması gerekir.

Alt satırlar üçüncü sütunu veriyor. Üç yeni şablon eklendiğinde mevcut durumda yüzey
altmış beşten yüz ona çıkıyor: **her yeni şablon on beş ad ekliyor**. Kalıp altında aynı üç
şablon yüzeyi yirmi beşten kırka çıkarıyor — beşer ad. Gövdesi sabit kalıpta üç yeni şablon
yüzeyi hiç değiştirmiyor.

Üç kurulumun karşılaştırılması bir şeyi daha söylüyor. İkinci kurulumda yüzey yirmi beşe
inerken şablonların hiçbiri işlevini yitirmiyor: bildirim metni hâlâ yönetici tarafından
düzenlenebiliyor, yalnız düzenlenen metnin çözebileceği ad kümesi daralıyor. Üçüncü kurulumda
kazanılan yirmi beş birim, kaybedilen bir yetenekle ödeniyor. **Yüzeyi sıfırlayan tek yol,
metni dışarıdan düzenlenebilir olmaktan çıkarmaktır** ve bu bir güvenlik kararı değil, bir
ürün kararıdır.

#### Gövde Denetimi

Gövdesi sabitlenemeyen şablonlar için üçüncü bir savunma kalır: gövdenin kendisini bir izin
listesinden geçirmek. Bu, gövdeyi okuyup içinde geçen adları çıkarmak ve listede olmayan bir
ad varsa şablonu reddetmek demektir. Kazancı ve bedeli aynı sayıdan okunur.

```js
// govde-denetimi.mjs — disaridan gelen sablon govdesinin izin listesinden gecirilmesi
import { erisilebilir, GENIS, BILDIRILEN } from "./motor.mjs";

// Yapilandirmadan okunan sablon govdeleri (kurgu): hepsi yoneticinin yazdigi mesru metinler.
const GOVDE = [
  "Sayin {abone.ad}, {fatura.donem} donemi faturaniz hazir.",
  "Son odeme gunu {fatura.sonGun}.",
  "{abone.no} numarali abonelik icin {fatura.tutar} birim tahakkuk etti.",
  "Bolge {ayar.bolge} icin aylik dokum.",
  "Sunucu {ayar.sunucuAdi} uzerinden uretildi, surum {ayar.surum}.",
];
const adlar = (s) => [...s.matchAll(/\{([\w.]+)\}/g)].map((m) => m[1]);
const gecer = (s, izin) => adlar(s).every((a) => izin.includes(a));

const IZIN = [["bildirilen ad kumesi", BILDIRILEN],
  ["bildirilen + ayar.bolge", [...BILDIRILEN, "ayar.bolge"]],
  ["govde denetimi yok", erisilebilir(GENIS)]];

console.log(`${GOVDE.length} sablon govdesi, ${new Set(GOVDE.flatMap(adlar)).size} ayri ad kullaniliyor\n`);
console.log(`${"izin listesi".padEnd(24)}${"uzunluk".padStart(8)}${"gecen govde".padStart(12)}${"duran mesru govde".padStart(19)}${"yuzey".padStart(7)}`);
for (const [adi, izin] of IZIN) {
  const gecen = GOVDE.filter((s) => gecer(s, izin)).length;
  console.log(adi.padEnd(24) + String(izin.length).padStart(8) + String(gecen).padStart(12) +
    String(GOVDE.length - gecen).padStart(19) + String(izin.length).padStart(7));
}
```

```
5 sablon govdesi, 8 ayri ad kullaniliyor

izin listesi             uzunluk gecen govde  duran mesru govde  yuzey
bildirilen ad kumesi           5           3                  2      5
bildirilen + ayar.bolge        6           4                  1      6
govde denetimi yok            15           5                  0     15
```

Üç satır tek bir eğriyi veriyor: izin listesi her uzadığında bir meşru gövde kurtuluyor ve
yüzey bir artıyor. Beş adlık listede üç gövde geçiyor, iki meşru gövde duruyor. Listeye
bölge adı eklendiğinde dördü geçiyor ve yüzey altıya çıkıyor. Denetim kaldırıldığında beşi
de geçiyor ve yüzey on beş oluyor.

Denetimin kapsamadığı bir durum kalıyor. Gövdesi doğrudan kullanıcı alanından okunan iki
şablonda süslü parantez taşıyan bir metin meşru olabilir — bir rapor başlığında ölçü birimi
ya da bir saha etiketinde alan kodu yazılabilir. Orada izin listesi meşru başlığı reddeder
ve bedel görünür olur; gövdenin bir kullanıcı alanından okunması kararı, izin listesinin
çözemeyeceği bir karardır.

Bu, ilk dersteki izin listesinin şablon gövdesine uygulanmış hâlidir ve aynı özelliği taşır:
listede olmayan meşru bir ad reddedilir. Farkı ölçünün ne olduğudur — orada dışarıda kalan
nokta sayılıyordu, burada **çözülebilen ad sayısı**. İzin listesinin uzunluğu doğrudan
yüzeyin kendisidir; bu yüzden her yeni ad talebi bir yüzey kararıdır ve bir yerde yazılı
olması gerekir.

#### Özet

- Ölçülen büyüklük değerlendirme yüzeyidir: şablon işlenirken ortamda çözülebilen ad sayısı.
  Sonda kümesi elle yazılmaz, ortamın kendisinden üretilir.
- Geniş ortamda ifade motoru 15 adın 15'ini çözdü ve bunların 10'u bildirim dışıydı. Ortamı
  daraltmak erişilebilir adı 6'ya indirdi; motoru değere sabitlemek bildirim dışı çözülen adı
  geniş ortamda bile 0 yaptı. İki savunma bağımsızdır.
- On iki şablonun hepsi kullanıcı verisi alıyor; 8'i yalnız değer olarak, 4'ü ifade olarak.
  Gövde kaynağı ayrımı belirleyici: 7 sabit, 3 yapılandırmadan, 2 kullanıcı alanından.
- Toplam yüzey 65 ve tamamı gövdesi dışarıdan gelen 5 şablondan geliyor. Değer motoru ve dar
  ortam yüzeyi 25'e indiriyor; gövdenin de sabitlenmesi 0'a. Üçüncüsünün bedeli, bildirim
  metninin yönetici tarafından düzenlenememesidir.
- Gövdesi sabitlenemeyen şablonda üçüncü savunma gövdenin izin listesinden geçirilmesidir:
  5 adlık listede 5 gövdenin 3'ü geçti ve 2 meşru gövde durdu; liste 6'ya çıkınca 4'ü geçti
  ve yüzey 6 oldu. İzin listesinin uzunluğu doğrudan yüzeyin kendisidir.
- Üç yeni şablonda yüzey mevcut durumda 65'ten 110'a, kalıp altında 25'ten 40'a çıktı, gövdesi
  sabit kurulumda 0'da kaldı. Yüzey şablon sayısıyla değil, gövdesi dışarıdan gelen şablon
  sayısıyla artar.

#### Sonraki Adım

Şablon gövdesi bir metindi ve dışarıdan gelmesi yüzeyi büyütüyordu. Aynı soru metin olmayan
girdiler için de sorulur. Saha uygulaması iş emri kapanışında fotoğraf ve ölçüm dosyası
yüklüyor; abone portalı sayaç okuma dökümü için dosya alıyor. Bu girdinin bir sözleşmesi
yoktur — bir bayt dizisidir ve hakkında söylenen her şey istemcinin beyanıdır. Sonraki ders
yükleme akışını denetim katmanı olarak sayar: tür, boyut, ad ve saklama yeri denetimlerinden
kaçı var, hangi denetim tek başına yetmiyor ve saklama yerinin sunulması kaç nokta açıyor.

### Dosya Yükleme Güvenliği

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/dosya-yukleme-guvenligi

Yükleme akışının denetim katmanı olarak sayılması: on yükleme kalemi beş denetim katmanından tek tek ve katman katman geçirilir, hangi denetimin tek başına yetmediği ve hangisi eksik kaldığında kaç beklenmedik kalemin geçtiği ölçülür, saklama yerinin doğrudan adreslenebilir olmasının açtığı nokta sayılır ve yükleme noktası sayısı arttığında elle yazılan denetim sayısı karşılaştırılır.

Önceki ders şablon gövdesinin dışarıdan gelmesinin yüzeyi nasıl büyüttüğünü ölçtü. Orada
gelen şey en azından bir metindi ve içindeki adlar sayılabiliyordu. Yükleme akışında gelen
şey bir bayt dizisidir ve hakkında söylenen her şey — türü, adı, boyutu — istemcinin
beyanıdır.

Bu ders yükleme akışını **denetim katmanı** olarak sayar. Ölçülen şey tek bir dosyanın
zararlı olup olmadığı değil, katmanların kaçını geçtiğidir: hangi denetim tek başına
yetmiyor, hangisi eksik kaldığında kaç kalem geçiyor ve saklama yerinin seçimi kaç nokta
açıyor.

#### Yükleme Kalemleri

Saha uygulaması iş emri kapanışında ölçüm fotoğrafı ve döküm belgesi yüklüyor. Kalem
kümesi kurgudur; her kalemin **baş baytları gerçektir**, geri kalanı modellenmiş bir
boyuttur.

**GC11 (varsayım):** her kalemin beklenen kararı elle etiketlenmiştir ve doğrudur. Sayılar
bu etiket kümesine görelidir; kümede olmayan bir yükleme biçimi hakkında bir şey söylemez.

```js
// kalem.mjs — saha uygulamasindan gelen yukleme kalemleri (kurgu model)
// Her kalemin bas baytlari gercektir; kalemin geri kalani modellenmis boyuttur.
export const IMZA = { "resim-png": [0x89, 0x50, 0x4e, 0x47], "resim-jpeg": [0xff, 0xd8, 0xff],
  "belge-pdf": [0x25, 0x50, 0x44, 0x46], "sikistirilmis": [0x50, 0x4b, 0x03, 0x04] };
const bas = (t) => Uint8Array.from(IMZA[t] ?? [0x6f, 0x6b, 0x0a]);
export const turBul = (b) =>
  Object.entries(IMZA).find(([, s]) => s.every((x, i) => b[i] === x))?.[0] ?? "bilinmeyen";

// [ad (istemcinin verdigi), beyan edilen tur, gercek icerik turu, boyut, beklenen karar]
export const KALEM = [
  ["olcum-kuzey.png", "resim-png", "resim-png", 120_000, "kabul"],
  ["olcum-guney.jpg", "resim-jpeg", "resim-jpeg", 240_000, "kabul"],
  ["dokum-2026-03.pdf", "belge-pdf", "belge-pdf", 380_000, "kabul"],
  ["sayac.png", "resim-png", "resim-jpeg", 90_000, "ret"],
  ["arsiv.png", "resim-png", "sikistirilmis", 150_000, "ret"],
  ["panorama.png", "resim-png", "resim-png", 12_000_000, "ret"],
  ["not.txt", "metin-duz", "bilinmeyen", 2_000, "ret"],
  ["saha/olcum.png", "resim-png", "resim-png", 110_000, "ret"],
  ["olcum-kuzey.png", "resim-png", "resim-png", 130_000, "ret"],
  ["kapak.jpeg", "resim-png", "resim-jpeg", 95_000, "ret"],
].map(([ad, beyan, gercek, boyut, bekleniyor], i) =>
  ({ no: i + 1, ad, beyan, boyut, bekleniyor, veri: bas(gercek) }));

export const IZINLI = ["resim-png", "resim-jpeg", "belge-pdf"];
export const UZANTI = { png: "resim-png", jpg: "resim-jpeg", jpeg: "resim-jpeg", pdf: "belge-pdf" };
export const EN_COK = 5_000_000;
```

Yedi kalem beklenmedik olarak etiketlendi ve hiçbiri zararlı içerik taşımıyor. Bir fotoğraf
yanlış uzantıyla adlandırılmış, bir sıkıştırılmış dosyanın adı resim uzantısı taşıyor, bir
panorama sınırı aşıyor, bir ad yol ayırıcı içeriyor, bir ad daha önce yüklenmiş bir dosyayla
çakışıyor. Bunlar saha kullanımının olağan kazalarıdır; ölçüm de bunları sayar.

#### Beş Denetim Katmanı

Her katman tek bir soru sorar ve tek bir şeye bakar. Beyan istemcinin bildirdiği türe,
uzantı dosya adının sonuna, imza gerçek baytlara, boyut sınıra, ad ise saklama adının
belirlenme biçimine bakar.

```js
// denetim.mjs — bes denetim katmani. Her katman tek bir soru sorar.
import { KALEM, IZINLI, UZANTI, EN_COK, turBul } from "./kalem.mjs";

export const KATMAN = {
  beyan:  (k) => IZINLI.includes(k.beyan),
  uzanti: (k) => UZANTI[k.ad.split(".").pop().toLowerCase()] === k.beyan,
  imza:   (k) => turBul(k.veri) === k.beyan,
  boyut:  (k) => k.boyut <= EN_COK,
  ad:     (k, gorulen) => !k.ad.includes("/") && !gorulen.has(k.ad),
};

// Bir denetim kumesini butun kalemlere uygular.
export function kos(kumeAdlari) {
  const gorulen = new Set(), kabuller = [];
  let gecenBeklenmedik = 0, duranMesru = 0;
  for (const k of KALEM) {
    const gecti = kumeAdlari.every((a) => KATMAN[a](k, gorulen));
    if (gecti) { kabuller.push(k); gorulen.add(k.ad); }
    if (gecti && k.bekleniyor === "ret") gecenBeklenmedik++;
    if (!gecti && k.bekleniyor === "kabul") duranMesru++;
  }
  return { kabul: kabuller.length, kabuller, gecenBeklenmedik, duranMesru };
}
```

```js
// kapsama.mjs — tek basina her katman, sonra katman katman ekleme
import { KALEM } from "./kalem.mjs";
import { KATMAN, kos } from "./denetim.mjs";

const AD = Object.keys(KATMAN);
const beklenmedik = KALEM.filter((k) => k.bekleniyor === "ret").length;
console.log(`${KALEM.length} yukleme kalemi: ${KALEM.length - beklenmedik} beklenen, ${beklenmedik} beklenmedik\n`);

console.log(`${"tek basina katman".padEnd(20)}${"kabul".padStart(7)}${"gecen beklenmedik".padStart(19)}${"duran beklenen".padStart(16)}`);
for (const a of AD) {
  const r = kos([a]);
  console.log(a.padEnd(20) + String(r.kabul).padStart(7) + String(r.gecenBeklenmedik).padStart(19) +
    String(r.duranMesru).padStart(16));
}

console.log(`\n${"katman katman".padEnd(34)}${"kabul".padStart(7)}${"gecen beklenmedik".padStart(19)}${"duran beklenen".padStart(16)}`);
for (let i = 1; i <= AD.length; i++) {
  const kume = AD.slice(0, i), r = kos(kume);
  console.log(kume.join(" + ").padEnd(34) + String(r.kabul).padStart(7) +
    String(r.gecenBeklenmedik).padStart(19) + String(r.duranMesru).padStart(16));
}
```

```
10 yukleme kalemi: 3 beklenen, 7 beklenmedik

tek basina katman     kabul  gecen beklenmedik  duran beklenen
beyan                     9                  6               0
uzanti                    8                  5               0
imza                      6                  3               0
boyut                     9                  6               0
ad                        8                  5               0

katman katman                       kabul  gecen beklenmedik  duran beklenen
beyan                                   9                  6               0
beyan + uzanti                          8                  5               0
beyan + uzanti + imza                   6                  3               0
beyan + uzanti + imza + boyut           5                  2               0
beyan + uzanti + imza + boyut + ad      3                  0               0
```

Üst tablo tek başına hiçbir katmanın yetmediğini gösteriyor. En güçlü katman imzadır ve o da
yedi beklenmedik kalemin dördünü durdurup üçünü geçiriyor. Beyan ve boyut altışar kalem
geçiriyor. **Tür denetimi tek başına yetmez** cümlesinin sayısı budur: beyan tek başına
geçen altı kalemden birinde bile içeriğe bakmıyor.

Alt tablo katmanların birbirini nasıl tamamladığını veriyor. Beş katmanla geçen beklenmedik
kalem sıfıra iniyor ve duran beklenen kalem sütunu baştan sona sıfır kalıyor — bu kümede
katmanlar meşru yüklemeleri durdurmuyor. Bedelin sıfır çıkması kümenin dar olmasındandır ve
GC11 bunu söylüyor.

#### Eksik Kalan Katman ve Saklama Yeri

Katmanların hangisinin gerçekten yük taşıdığı, onu çıkarınca görülür. Aynı blok saklama
yerini de ölçüyor: kabul edilen kalemler nereye yazılıyor ve o yer dışarıdan adreslenebiliyor
mu.

**GC12 (varsayım):** doğrudan adreslenebilir her kabul edilmiş kalem bir nokta sayılır.
Noktanın açtığı sonuç değil, **sayısı** ölçülür.

```js
// saklama.mjs — eksik katmanin bedeli ve saklama yerinin actigi nokta
import { KALEM } from "./kalem.mjs";
import { KATMAN, kos } from "./denetim.mjs";

const AD = Object.keys(KATMAN), TAM = kos(AD);
console.log(`${"eksik katman".padEnd(16)}${"kabul".padStart(7)}${"gecen beklenmedik".padStart(19)}`);
for (const a of AD) {
  const r = kos(AD.filter((x) => x !== a));
  console.log(a.padEnd(16) + String(r.kabul).padStart(7) + String(r.gecenBeklenmedik).padStart(19));
}
console.log(`${"eksik yok".padEnd(16)}${String(TAM.kabul).padStart(7)}${String(TAM.gecenBeklenmedik).padStart(19)}`);

// Saklama yeri: ayni kabul kumesi, uc saklama bicimi. Saklama adi kalemden turetilir.
const uretilen = (k) => `y-${String(k.no).padStart(4, "0")}.bin`;
const SAKLAMA = [["sunulan dizin + istemci adi", true, (k) => k.ad],
  ["sunulan dizin + uretilen ad", true, uretilen],
  ["sunulmayan depo + araci uc", false, uretilen]];
console.log(`\n${"saklama bicimi".padEnd(30)}${"adreslenebilir".padStart(15)}${"istemci adiyla".padStart(15)}${"yetki noktasi".padStart(14)}`);
for (const [adi, sunulan, adUret] of SAKLAMA) {
  const adreslenebilir = sunulan ? TAM.kabuller.length : 0;
  const istemciAdi = sunulan ? TAM.kabuller.filter((k) => adUret(k) === k.ad).length : 0;
  console.log(adi.padEnd(30) + String(adreslenebilir).padStart(15) +
    String(istemciAdi).padStart(15) + String(sunulan ? 0 : 1).padStart(14));
}
```

```
eksik katman      kabul  gecen beklenmedik
beyan                 3                  0
uzanti                3                  0
imza                  5                  2
boyut                 4                  1
ad                    5                  2
eksik yok             3                  0

saklama bicimi                 adreslenebilir istemci adiyla yetki noktasi
sunulan dizin + istemci adi                 3              3             0
sunulan dizin + uretilen ad                 3              0             0
sunulmayan depo + araci uc                  0              0             1
```

Üst tablo katmanları ağırlıklarına göre ayırıyor. Beyan ve uzantı çıkarıldığında geçen
beklenmedik kalem yine sıfır: bu iki katmanın durdurduğu her şeyi imza da durduruyor. İmza,
ad ve boyut çıkarıldığında ise sırasıyla iki, iki ve bir kalem geçiyor. **Beş katmanın
üçü taşıyıcı, ikisi yedek.** Yedek olmaları gereksiz oldukları anlamına gelmez — ucuzdurlar
ve daha erken reddederler — ama kapsamayı onlar sağlamıyor.

İmzanın taşıyıcı olması, onun yeterli olduğu anlamına gelmez. İmza yalnız ilk birkaç bayta
bakar ve dosyanın geri kalanı hakkında hiçbir şey söylemez; ölçtüğü tek şey beyanın
içerikle tutarlı olup olmadığıdır. Bu ölçüm kapsamının sınırıdır ve yazılması gerekir:
tabloda sıfırlanan sayı **beyanla içerik arasındaki tutarsızlıktır**, dosyanın güvenli
olduğu değil.

Orta tablo saklama kararını ölçüyor. Üç kalem de kabul edildi; değişen tek şey nereye
yazıldıkları. Sunulan bir dizine yazıldıklarında üçü de doğrudan adreslenebilir üç nokta
açıyor. Ad istemciden geliyorsa bu üç adresin üçü de yükleyenin bildiği adreslerdir.
Sunucunun ürettiği ad bu sayıyı sıfıra indiriyor ama nokta sayısını değiştirmiyor.
Sunulmayan depo ve aracı uç kurulumunda adreslenebilir nokta sıfır, buna karşılık bir yetki
denetimi noktası doğuyor — dosya artık bir uçtan geçerek veriliyor ve o uçta kimin neyi
alabileceği sorulmak zorunda.

Saklama kararının kabul kararından bağımsız olması bu ölçümün asıl bulgusudur. Beş katman da
doğru çalışsa ve geçen beklenmedik kalem sıfır olsa bile saklama yeri hâlâ üç nokta açar.
İki karar iki ayrı yerde verilir ve biri ötekini kapatmaz; yükleme akışının kapsaması ancak
ikisi birlikte sayıldığında bilinir. Denetim katmanlarını sayıp saklama yerini saymayan bir
inceleme, sayının yarısını çıkarmış olur.

#### Yükleme Noktası Envanteri

Buraya kadarki bütün ölçüm tek bir yükleme noktası üzerindeydi. Bölgesel ölçüm ağında beş
yükleme noktası var ve her birinde katmanlar ayrı ayrı yazılmış.

```js
// noktalar.mjs — yukleme noktasi envanteri: hangi noktada hangi katman elle yazilmis
import { KALEM } from "./kalem.mjs";
import { KATMAN, kos } from "./denetim.mjs";

const AD = Object.keys(KATMAN);
// [servis, yukleme noktasi, o noktada elle yazilmis katmanlar]
const NOKTA = [
  ["saha", "is emri eki", ["beyan", "uzanti", "imza", "boyut", "ad"]],
  ["saha", "fotograf", ["beyan", "uzanti", "imza"]],
  ["abone portali", "belge", ["beyan", "boyut"]],
  ["abone portali", "okuma dokumu", ["beyan", "uzanti", "boyut", "ad"]],
  ["fatura", "itiraz eki", ["beyan"]],
];

console.log(`${NOKTA.length} yukleme noktasi, ${AD.length} katman, ${KALEM.length} kalem\n`);
console.log(`${"yukleme noktasi".padEnd(16)}${"yazilan katman".padStart(15)}${"eksik".padStart(7)}${"gecen beklenmedik".padStart(19)}`);
let elle = 0, gecen = 0, tam = 0;
for (const [, n, k] of NOKTA) {
  const r = kos(k);
  elle += k.length; gecen += r.gecenBeklenmedik; tam += k.length === AD.length ? 1 : 0;
  console.log(n.padEnd(16) + String(k.length).padStart(15) + String(AD.length - k.length).padStart(7) +
    String(r.gecenBeklenmedik).padStart(19));
}
console.log(`\n${"kurulum".padEnd(30)}${"yazilan denetim".padStart(16)}${"tam kapsanan nokta".padStart(20)}${"gecen beklenmedik".padStart(19)}`);
console.log("nokta basina elle yazim".padEnd(30) + String(elle).padStart(16) +
  String(tam).padStart(20) + String(gecen).padStart(19));
const m = kos(AD).gecenBeklenmedik * NOKTA.length;
console.log("merkezi yukleme kalibi".padEnd(30) + String(AD.length).padStart(16) +
  String(NOKTA.length).padStart(20) + String(m).padStart(19));
console.log(`\nyeni yukleme noktasi eklendiginde yazilacak denetim: elle ${AD.length}, merkezi kalipta 0`);
```

```
5 yukleme noktasi, 5 katman, 10 kalem

yukleme noktasi  yazilan katman  eksik  gecen beklenmedik
is emri eki                   5      0                  0
fotograf                      3      2                  3
belge                         2      3                  5
okuma dokumu                  4      1                  2
itiraz eki                    1      4                  6

kurulum                        yazilan denetim  tam kapsanan nokta  gecen beklenmedik
nokta basina elle yazim                     15                   1                 16
merkezi yukleme kalibi                       5                   5                  0

yeni yukleme noktasi eklendiginde yazilacak denetim: elle 5, merkezi kalipta 0
```

Beş noktanın yalnız biri beş katmanın hepsini taşıyor. Kalan dördünde toplam on eksik katman
var ve aynı on kalem kümesi bu noktalardan geçtiğinde on altı beklenmedik kabul doğuyor. En
zayıf nokta olan itiraz eki tek bir katman taşıyor ve yedi beklenmedik kalemin altısını
kabul ediyor.

İki kurulum arasındaki fark yazılan denetim sayısında da görünüyor. Nokta başına elle yazımda
on beş denetim yazılmış ve tam kapsanan nokta bir; merkezî kalıpta beş denetim tek yerde
yazılıyor ve beş noktanın beşi de kapsanıyor. **Aynı kapsama üç kat daha az kodla elde
ediliyor** ve yeni bir yükleme noktası eklendiğinde yazılacak denetim sayısı sıfır kalıyor.

#### Özet

- On yükleme kaleminin 7'si beklenmedik olarak etiketlendi ve hiçbiri zararlı içerik
  taşımıyor; yanlış uzantı, yanlış beyan, sınır aşımı, yol ayırıcılı ad ve ad çakışması var.
- Tek başına hiçbir katman yetmedi: en güçlüsü olan imza 7 beklenmedik kalemin 4'ünü durdurup
  3'ünü geçirdi, beyan ve boyut 6'şar kalem geçirdi. Beş katman birlikte geçen beklenmedik
  kalemi 0'a indirdi ve hiçbir aşamada meşru kalem durmadı.
- Katmanlar eşit ağırlıkta değil: beyan ve uzantı çıkarıldığında geçen kalem 0 kalıyor, imza
  ve ad çıkarıldığında 2'şer, boyut çıkarıldığında 1 kalem geçiyor. Üç katman taşıyıcı, ikisi
  yedek.
- Saklama yeri kabul kararından bağımsız bir noktadır: sunulan dizinde 3 kabul edilmiş kalem
  3 adreslenebilir nokta açıyor. Sunucunun ürettiği ad tahmin edilebilir adresi 0'a indiriyor;
  sunulmayan depo adreslenebilir noktayı 0'a indirip 1 yetki denetimi noktası ekliyor.
- Beş yükleme noktasının yalnız 1'i beş katmanı da taşıyor; kalan dörtte 10 eksik katman var
  ve aynı kalem kümesi 16 beklenmedik kabul üretiyor. Nokta başına elle yazımda 15 denetim
  yazılmış, merkezî kalıpta 5 denetim beş noktayı da kapsıyor ve yeni nokta için yazılacak
  denetim 0'dır.

#### Sonraki Adım

Yükleme akışında dışarıdan gelen şey bir bayt dizisiydi ve sunucu onu yalnız saklıyordu.
Bazı akışlarda sunucu daha ileri gider: dışarıdan gelen bir adrese kendisi istek yapar.
Rapor üretici abone portalının verdiği bağlantıdan belge çekiyor, saha uygulaması harita
karosu için dış bir uca gidiyor. O istekleri yapan taraf sunucudur ve sunucunun ağdaki yeri
istemcininkinden başkadır. Sonraki ders giden istek hedeflerini bir kısıt kümesinden geçirir
ve tek soruyu ölçer: kaç hedef iç ağa ulaşabiliyor, kısıt hangi noktada uygulandığında
kapsama düşüyor.

### Sunucu Tarafı İstek Sahteciliği

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/sunucu-tarafi-istek-sahteciligi

Giden istek hedeflerinin kısıt kümesinden geçirilmesi: on iki hedef dört kısıt biçiminden geçirilip kaçının iç ağa ulaşabildiği gerçek adres aritmetiğiyle sayılır, kısıtın yalnız ilk adımda uygulanmasının yeniden yönlendirme zincirinde kaç isteği iç adrese vardırdığı ölçülür ve denetim ile bağlanma arasında ad çözümlemesinin tekrarlanmasının açtığı nokta gösterilir.

Önceki ders yükleme akışında sunucunun aldığı baytları saydı; orada sunucu yalnız saklıyordu.
Bazı akışlarda sunucu daha ileri gider ve dışarıdan gelen bir adrese **kendisi** istek yapar:
rapor üretici abone portalının verdiği bağlantıdan belge çekiyor, saha uygulaması harita
karosunu bir dış uçtan alıyor.

İstek sahteciliğinin daha önce ölçülen biçiminde isteği kullanıcının tarayıcısı gönderiyor ve
kullanıcının kimliğini taşıyordu. Burada isteği sunucunun kendisi gönderir ve taşıdığı
ayrıcalık kimlik değil, **ağdaki yerdir**: sunucu iç ağın içindedir, istemci değildir. Bu
ders o farkı hedef sayısı cinsinden ölçer.

#### Adres Aritmetiği

Bir hedefin iç ağa ulaşıp ulaşmadığı ad üzerinden anlaşılmaz, adres üzerinden anlaşılır.
Adres otuz iki bitlik bir sayıdır; bir bloğa düşüp düşmediği maskeyle bir ve işlemidir.
Aşağıdaki blok bu aritmetiği yazar ve kurgu bir ad çözümleyici kurar — ağa hiç çıkılmaz.

**GC13 (varsayım):** ad çözümleme modeldir; gerçek bir sorgu yapılmaz ve tablodaki adlar
belgeleme için ayrılmış aralıklara çözülür. Ölçülen şey çözümlemenin doğruluğu değil,
**kısıtın hangi değere uygulandığıdır**.

```js
// adres.mjs — adres aritmetigi ve kurgu ad cozumleyici. Ag erisimi yok, hepsi model.
export const ayristir = (a) => {
  const p = a.split(".");
  if (p.length !== 4) return null;
  const s = p.map((x) => (/^\d{1,3}$/.test(x) ? Number(x) : -1));
  return s.every((n) => n >= 0 && n <= 255)
    ? (((s[0] << 24) >>> 0) + (s[1] << 16) + (s[2] << 8) + s[3]) >>> 0 : null;
};
export const blok = (c) => {
  const [taban, uzunluk] = c.split("/");
  const m = uzunluk === "0" ? 0 : (0xffffffff << (32 - Number(uzunluk))) >>> 0;
  return [(ayristir(taban) & m) >>> 0, m, c];
};
export const icinde = (adres, [taban, m]) => ((adres & m) >>> 0) === taban;

// Yerel ve ozel kullanima ayrilmis adres bloklari.
export const OZEL = ["0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8",
  "169.254.0.0/16", "172.16.0.0/12", "192.168.0.0/16"].map(blok);
export const ozelMi = (adres) => adres !== null && OZEL.some((b) => icinde(adres, b));

// Kurgu ad cozumleyici: ad -> adres. Gercek bir cozumleme yapilmaz.
export const COZUM = {
  "belge.dis-1.ornek": "203.0.113.24", "karo.dis-2.ornek": "198.51.100.9",
  "arsiv.dis-1.ornek": "203.0.113.40", "arsiv-2.dis-1.ornek": "203.0.113.41",
  "gecis.dis-3.ornek": "192.168.30.5",
  "rapor.ic": "10.12.4.7", "olcum-depo.ic": "10.12.4.20", "yerel.ic": "127.0.0.1",
  "kenar.ic": "169.254.10.5", "sayac.ic": "172.20.8.3",
};
export const coz = (ad) => ayristir(COZUM[ad] ?? ad);

// Giden istek hedefleri (kurgu): rapor ureticinin ve saha uygulamasinin cektigi adresler.
export const HEDEF = [
  ["belge.dis-1.ornek", "bildirilen"], ["karo.dis-2.ornek", "bildirilen"],
  ["arsiv.dis-1.ornek", "kullanici"], ["gecis.dis-3.ornek", "kullanici"],
  ["rapor.ic", "kullanici"], ["olcum-depo.ic", "kullanici"], ["yerel.ic", "kullanici"],
  ["kenar.ic", "kullanici"], ["sayac.ic", "kullanici"], ["203.0.113.99", "kullanici"],
  ["10.12.4.9", "kullanici"], ["127.0.0.1", "kullanici"],
];
export const BILDIRILEN = ["belge.dis-1.ornek", "karo.dis-2.ornek", "arsiv.dis-1.ornek"];
```

Hedeflerin üçü bildirilen dış konaklardır; onu kullanıcı verisinden geliyor ve biri her
ikisidir. Bu ayrım
kapsamanın çıkış noktasıdır: kısıtın kapatması gereken küme dokuz kalemdir, üç kalem zaten
sözleşmenin içindedir.

#### Dört Kısıt Biçimi

Aynı hedef kümesi dört kısıttan geçiriliyor. Kısıt yokken, ada bakan bir ret listesiyle,
çözülmüş adrese bakan bir ret listesiyle ve bildirilen konakları sayan bir izin listesiyle.

```js
// kisit.mjs — ayni hedef kumesi dort kisit biciminden geciriliyor
import { HEDEF, BILDIRILEN, OZEL, coz, ozelMi, blok, icinde, ayristir } from "./adres.mjs";

const KISIT = [
  ["kisit yok", () => true],
  ["ad temelli ret listesi", (h) => !h.endsWith(".ic")],
  ["adres temelli ret listesi", (h) => !ozelMi(coz(h))],
  ["izin listesi (bildirilen konak)", (h) => BILDIRILEN.includes(h)],
];

console.log(`${HEDEF.length} giden istek hedefi, ${OZEL.length} ozel adres blogu`);
console.log(`bildirilen dis konak: ${BILDIRILEN.length}, kullanici verisinden gelen hedef: ` +
  `${HEDEF.filter(([, k]) => k === "kullanici").length}\n`);
console.log(`${"kisit bicimi".padEnd(34)}${"gecen".padStart(7)}${"ic aga ulasan".padStart(15)}${"duran bildirilen".padStart(18)}`);
for (const [ad, f] of KISIT) {
  const gecen = HEDEF.filter(([h]) => f(h));
  const ic = gecen.filter(([h]) => ozelMi(coz(h))).length;
  const duran = BILDIRILEN.filter((h) => !f(h)).length;
  console.log(ad.padEnd(34) + String(gecen.length).padStart(7) + String(ic).padStart(15) +
    String(duran).padStart(18));
}

// Adres aritmetigi gercekten kosuyor: hangi hedef hangi bloga dusuyor.
console.log("\nozel bloga dusen hedefler:");
for (const [h] of HEDEF) {
  const a = coz(h);
  const b = OZEL.find((x) => a !== null && icinde(a, x));
  if (b) console.log(`  ${h.padEnd(20)} -> ${String(a >>> 0).padStart(10)} blok ${b[2]}`);
}
console.log(`\n10.12.4.7 -> ${ayristir("10.12.4.7")}, 10.0.0.0/8 tabani ${blok("10.0.0.0/8")[0]}, ` +
  `maske ${blok("10.0.0.0/8")[1] >>> 0}`);
```

```
12 giden istek hedefi, 7 ozel adres blogu
bildirilen dis konak: 3, kullanici verisinden gelen hedef: 10

kisit bicimi                        gecen  ic aga ulasan  duran bildirilen
kisit yok                              12              8                 0
ad temelli ret listesi                  7              3                 0
adres temelli ret listesi               4              0                 0
izin listesi (bildirilen konak)         3              0                 0

ozel bloga dusen hedefler:
  gecis.dis-3.ornek    -> 3232243205 blok 192.168.0.0/16
  rapor.ic             ->  168559623 blok 10.0.0.0/8
  olcum-depo.ic        ->  168559636 blok 10.0.0.0/8
  yerel.ic             -> 2130706433 blok 127.0.0.0/8
  kenar.ic             -> 2851998213 blok 169.254.0.0/16
  sayac.ic             -> 2886993923 blok 172.16.0.0/12
  10.12.4.9            ->  168559625 blok 10.0.0.0/8
  127.0.0.1            -> 2130706433 blok 127.0.0.0/8

10.12.4.7 -> 168559623, 10.0.0.0/8 tabani 167772160, maske 4278190080
```

Kısıt yokken on iki hedefin sekizi iç ağa ulaşıyor. Ad temelli ret listesi bu sayıyı üçe
indiriyor ve **üç hedefi hâlâ geçiriyor** — çünkü ada bakıyor, adrese bakmıyor. Geçenlerin
biri dış görünümlü bir ad taşıyor ve bir iç adrese çözülüyor; ikisi zaten sayı olarak
yazılmış iç adreslerdir ve adları `.ic` ile bitmediği için ret listesine takılmıyor.

Adres temelli ret listesi iç ağa ulaşan hedefi sıfırlıyor. Alt liste bunun nasıl olduğunu
gösteriyor: her hedef otuz iki bitlik bir sayıya çevriliyor ve yedi bloktan hangisine
düştüğü hesaplanıyor. Sekiz hedefin sekizi bir bloğa düşüyor ve hiçbiri ada bakılarak
bulunmuyor.

İzin listesi de sıfır veriyor, ama farklı bir yolla: on kullanıcı hedefinin dokuzunu
reddediyor. İki kısıtın kapsaması aynı, kapsama biçimi değil. Ret listesi listelenmemiş bir
blok çıktığında açık kalır; izin listesi meşru yeni bir dış konak eklendiğinde onu da durdurur.
Her ikisinin de bedeli bu tabloda sıfır görünüyor, çünkü bildirilen üç konak ikisinden de
geçiyor.

#### Kısıtın Uygulandığı Nokta

Kısıtın hangi değere uygulandığı kadar **ne zaman** uygulandığı da kapsamayı belirler. İki
durum bunu ayırır: istek bir yeniden yönlendirme zinciri izliyorsa ve ad çözümlemesi denetimle
bağlanma arasında tekrarlanıyorsa.

**GC14 (varsayım):** zincirler ve ikinci çözümleme modeldir. Ölçülen şey bir olayın olma
olasılığı değil, **kısıtın o adımı görüp görmediğidir**.

```js
// zincir.mjs — kisitin uygulandigi nokta: ilk adim, her adim, ve cozumlemenin tekrari
import { HEDEF, COZUM, coz, ozelMi, ayristir } from "./adres.mjs";

// Yeniden yonlendirme zincirleri (kurgu): istegin izledigi adim dizisi.
const ZINCIR = [
  ["belge.dis-1.ornek", ["belge.dis-1.ornek"]],
  ["karo.dis-2.ornek", ["karo.dis-2.ornek", "203.0.113.60"]],
  ["arsiv.dis-1.ornek", ["arsiv.dis-1.ornek", "arsiv-2.dis-1.ornek", "10.12.4.20"]],
  ["gecis.dis-3.ornek", ["gecis.dis-3.ornek"]],
  ["belge.dis-1.ornek", ["belge.dis-1.ornek", "127.0.0.1"]],
];
const adim = ZINCIR.reduce((t, [, z]) => t + z.length, 0);
console.log(`${ZINCIR.length} yeniden yonlendirme zinciri, ${adim} adim\n`);
console.log(`${"kisitin uygulandigi nokta".padEnd(28)}${"tamamlanan".padStart(12)}${"ic adrese varan".padStart(17)}`);
for (const [ad, secim] of [["yalniz ilk adim", (z) => [z[0]]], ["her adim", (z) => z]]) {
  const tamam = ZINCIR.filter(([, z]) => secim(z).every((h) => !ozelMi(coz(h))));
  const ic = tamam.filter(([, z]) => ozelMi(coz(z[z.length - 1]))).length;
  console.log(ad.padEnd(28) + String(tamam.length).padStart(12) + String(ic).padStart(17));
}

// Cozumlemenin tekrari: denetim anindaki adres ile baglanma anindaki adres ayri olabilir.
const IKINCI = { ...COZUM, "belge.dis-1.ornek": "10.12.4.31" };
const cozIkinci = (ad) => ayristir(IKINCI[ad] ?? ad);
const gecen = HEDEF.filter(([h]) => !ozelMi(coz(h)));
console.log(`\nadres temelli kisiti gecen hedef: ${gecen.length}`);
console.log(`${"baglanma bicimi".padEnd(34)}${"ic adrese varan".padStart(17)}`);
console.log("denetim ve baglanti ayri cozumleme".padEnd(34) +
  String(gecen.filter(([h]) => ozelMi(cozIkinci(h))).length).padStart(17));
console.log("tek cozumleme, denetlenen adrese".padEnd(34) +
  String(gecen.filter(([h]) => ozelMi(coz(h))).length).padStart(17));
```

```
5 yeniden yonlendirme zinciri, 9 adim

kisitin uygulandigi nokta     tamamlanan  ic adrese varan
yalniz ilk adim                        4                2
her adim                               2                0

adres temelli kisiti gecen hedef: 4
baglanma bicimi                     ic adrese varan
denetim ve baglanti ayri cozumleme                1
tek cozumleme, denetlenen adrese                  0
```

Üst tablo kısıtın yerini ölçüyor. Aynı kısıt yalnız ilk adıma uygulandığında dört zincir
tamamlanıyor ve ikisi bir iç adreste bitiyor; her adıma uygulandığında iki zincir tamamlanıyor
ve hiçbiri iç adrese varmıyor. Kısıt aynı kısıttır — değişen tek şey dokuz adımın kaçının
görüldüğüdür. **Bir isteğin hedefi tek bir değer değil, bir dizidir.**

Alt tablo ikinci noktayı veriyor. Kısıtı geçen dört hedeften biri, bağlanma anında yeniden
çözüldüğünde bir iç adrese düşüyor. Denetim doğru çalışmıştı; denetlenen değer artık
bağlanılan değer değil. İkinci satır çözümü gösteriyor: çözümleme bir kez yapılır, kısıt o
adrese uygulanır ve bağlantı **ada değil, denetlenen adrese** kurulur. O zaman iç adrese
varan hedef sıfırdır.

İki satırın ortak dersi kapsamanın tanımıyla ilgilidir. Bir kısıt, uygulandığı değer ile
kullanılan değer aynı olduğu sürece kapsar; ikisi ayrıldığı anda kısıt geçerli kalır ama
kapsaması yoktur. Yeni bir giden istek noktası eklendiğinde sorulacak soru budur: bu nokta
kısıtı hangi değere ve hangi adıma uyguluyor.

#### Giden İstek Noktası Envanteri

Kısıtın hangi değere ve hangi adıma uygulandığı ölçüldü. Kalan soru kaç noktada uygulandığı.
Bölgesel ölçüm ağında sekiz giden istek noktası var ve yarısı merkezî bir giden istek
istemcisinden geçiyor.

```js
// nokta.mjs — giden istek noktasi envanteri ve merkezi giden istek istemcisinin kapsamasi
import { coz, ozelMi } from "./adres.mjs";

// [servis, nokta, hedefler, hedef kaynagi, istemci]
const NOKTA = [
  ["rapor", "belgeCek", ["belge.dis-1.ornek", "arsiv.dis-1.ornek"], "kullanici", "merkezi"],
  ["rapor", "arsivCek", ["arsiv.dis-1.ornek", "10.12.4.9"], "kullanici", "dogrudan"],
  ["saha", "karoCek", ["karo.dis-2.ornek"], "bildirilen", "merkezi"],
  ["saha", "onizleme", ["gecis.dis-3.ornek", "yerel.ic"], "kullanici", "dogrudan"],
  ["abone portali", "logoCek", ["belge.dis-1.ornek", "kenar.ic"], "kullanici", "dogrudan"],
  ["fatura", "kurCek", ["karo.dis-2.ornek"], "bildirilen", "merkezi"],
  ["bildirim", "webKanci", ["203.0.113.99", "rapor.ic"], "kullanici", "dogrudan"],
  ["olcum", "toplayiciCek", ["sayac.ic"], "bildirilen", "merkezi"],
];
const hedefSayisi = NOKTA.reduce((t, n) => t + n[2].length, 0);

// Bir kurulumda bir hedefin gecip gecmedigi: kisit ya hic uygulanmiyor ya adrese uygulaniyor.
const gecer = (n, h, kurulum) => {
  const kisitli = kurulum === "hepsi" || (kurulum === "istisna" ? n[3] === "kullanici" : n[4] === "merkezi");
  return kisitli ? !ozelMi(coz(h)) : true;
};

console.log(`${NOKTA.length} giden istek noktasi, ${hedefSayisi} hedef, ` +
  `${NOKTA.filter((n) => n[4] === "merkezi").length} nokta merkezi istemciden geciyor\n`);
console.log(`${"kurulum".padEnd(32)}${"gecen hedef".padStart(12)}${"ic aga ulasan".padStart(15)}${"duran mesru ic cagri".padStart(22)}`);
for (const [adi, kurulum] of [["mevcut: dogrudan cagrilar var", "mevcut"],
    ["tumu merkezi istemciden", "hepsi"], ["merkezi + bildirilen ic cagri", "istisna"]]) {
  let gecen = 0, ic = 0, duran = 0;
  for (const n of NOKTA) for (const h of n[2]) {
    const g = gecer(n, h, kurulum);
    if (g) { gecen++; if (ozelMi(coz(h))) ic++; }
    else if (n[3] === "bildirilen" && ozelMi(coz(h))) duran++;
  }
  console.log(adi.padEnd(32) + String(gecen).padStart(12) + String(ic).padStart(15) + String(duran).padStart(22));
}
console.log(`\nyeni giden istek noktasi eklendiginde: merkezi istemcide yazilacak kisit 0, ` +
  `dogrudan cagrida ${1} kisit`);
```

```
8 giden istek noktasi, 13 hedef, 4 nokta merkezi istemciden geciyor

kurulum                          gecen hedef  ic aga ulasan  duran mesru ic cagri
mevcut: dogrudan cagrilar var             12              5                     1
tumu merkezi istemciden                    7              0                     1
merkezi + bildirilen ic cagri              8              1                     0

yeni giden istek noktasi eklendiginde: merkezi istemcide yazilacak kisit 0, dogrudan cagrida 1 kisit
```

İlk satır mevcut durumu veriyor: on üç hedefin beşi iç ağa ulaşıyor ve beşinin de geldiği yer
merkezî istemciden geçmeyen dört doğrudan çağrıdır. Kısıt kod tabanında vardır ve doğru
yazılmıştır; **yalnız dört noktada çağrılıyor.**

İkinci satır bütün noktaların merkezî istemciye alınmasını gösteriyor. İç ağa ulaşan sıfıra
iniyor, ama son sütun bir kalıyor: ölçüm toplayıcıya yapılan çağrı meşru bir iç çağrıdır ve
kısıt onu da durduruyor. Hedefi kullanıcı verisinden gelmeyen bir nokta için kısıt yanlış
yerdedir.

Üçüncü satır düzeltmeyi ölçüyor: kısıt yalnız hedefi kullanıcı verisinden gelen noktalara
uygulanınca duran meşru çağrı sıfırlanıyor ve iç ağa ulaşan bire çıkıyor — o bir, bildirilmiş
iç servis çağrısıdır. **Kapsamanın ölçüsü iç ağa ulaşan hedef sayısı değil, kullanıcı
verisinden gelip iç ağa ulaşan hedef sayısıdır**; bu ayrım yazılmazsa kısıt ya meşru işi
durdurur ya da kaldırılır.

#### Özet

- Giden istek hedeflerinin iç ağa ulaşıp ulaşmadığı ad üzerinden değil, otuz iki bitlik
  adres ve maske aritmetiğiyle belirlenir. Yedi özel blok, sekiz hedefi yakaladı.
- On iki hedefin 8'i kısıt yokken iç ağa ulaşıyor. Ad temelli ret listesi bunu 3'e indiriyor
  ve geçirdiği üçün biri dış görünümlü bir addır, ikisi sayıyla yazılmış iç adrestir. Adres
  temelli ret listesi ve izin listesi 0 veriyor.
- İki sıfır aynı değildir: ret listesi listelenmemiş bir blok çıktığında açık kalır, izin
  listesi kullanıcı verisinden gelen on hedefin dokuzunu reddeder ve yeni meşru dış konağı da
  durdurur.
- Kısıt yalnız ilk adıma uygulandığında 5 zincirin 4'ü tamamlandı ve 2'si iç adreste bitti;
  her adıma uygulandığında 2 zincir tamamlandı ve iç adrese varan 0 oldu. Bir isteğin hedefi
  tek değer değil, dizidir.
- Sekiz giden istek noktasının 4'ü merkezî istemciden geçiyor; kalan dördü yüzünden 13 hedefin
  5'i iç ağa ulaşıyor. Hepsi merkezî istemciye alındığında bu 0 oluyor ama 1 meşru iç çağrı
  duruyor; kısıt yalnız kullanıcı kaynaklı hedeflere uygulandığında duran meşru çağrı 0.
- Denetimle bağlanma arasında ad yeniden çözüldüğünde kısıtı geçen 4 hedeften 1'i iç adrese
  düştü. Tek çözümleme yapılıp denetlenen adrese bağlanıldığında bu sayı 0. Kısıt, uygulandığı
  değer ile kullanılan değer ayrıldığı anda kapsamasını yitirir.

#### Sonraki Adım

Buraya kadarki altı ders dışarıdan gelen verinin sınırını çizdi ve her sınırda kapsamayı
saydı: giriş noktası, çıkış bağlamı, sorgu yeri, şablon gövdesi, yükleme katmanı ve giden
istek hedefi. Hepsinin ortak varsayımı, gelen şeyin bir **değer** olmasıydı — bir metin, bir
sayı, bir bayt dizisi. Kimi akışlarda gelen şey değer değil, bir nesne tanımıdır: alanları,
türü ve kurulma biçimi verinin içinde yazar. Sonraki ders seri durumdan çıkarmayı güvenilir
tür kümesi cinsinden ölçer ve konuyu kapatır: kaç tür kabul ediliyor, izin listesi kaçını
kapatıyor ve veri biçimi seçmek kaç noktayı ortadan kaldırıyor.

### Güvensiz Seri Durumdan Çıkarma

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/guvensiz-seri-durumdan-cikarma

Seri durumdan çıkarmanın güvenilir tür kümesi cinsinden ölçülmesi: altı iletideki tür etiketli düğümler her derinlikte sayılır, açık tür kaydı ile izin listesinin kabul ettiği tür ve kurduğu düğüm sayısı karşılaştırılır, etiketsiz bir veri biçiminin yapı kurma noktasını sıfıra indirmesi ve karşılığında getirdiği eşleme noktası ölçülür, kod tabanına üç yeni tür eklendiğinde kabul kümesinin nasıl değiştiği çıkarılır.

Önceki altı ders dışarıdan gelen şeyi hep bir **değer** olarak ele aldı: bir metin, bir sayı,
bir bayt dizisi, bir adres. Her seferinde savunma, o değerin gideceği yerin kuralını bilerek
kuruldu. Kimi akışlarda gelen şey değer değildir: alanlarının yanında **türü** de verinin
içinde yazar ve alıcı taraf o türü okuyup karşılık gelen yapıyı kurar.

Bu ders o kurma anını sayar. Ölçü birimi **güvenilir tür kümesidir**: kaç tür kabul ediliyor,
izin listesi kaçını kapatıyor ve türü verinin içinden okumayan bir veri biçimi kaç noktayı
ortadan kaldırıyor.

#### Tür Etiketli Biçim

Model bir kayıt tutar: uygulamanın tanıdığı türler ve her türü kuran işlev. Gelen iletide
`$tur` alanı hangi kaydın çağrılacağını söyler. Kayıttaki türlerin bir kısmı yalnız veri
taşır; bir kısmı kurulurken bir kaynak açar ya da bir iş başlatır.

**GC15 (varsayım):** yan etki işareti bildirimdir; bu derste hiçbir tür gerçekten bir şey
çalıştırmaz. Ölçülen şey bir etkinin gerçekleşmesi değil, **kaç türün erişilebilir olduğudur**.

```js
// bicim.mjs — tur etiketi tasiyan bir seri durumdan cikarma bicimi (kurgu model)
// yanEtki: kurulurken bir kaynak acan ya da bir is baslatan tur. Burada yalniz
// isaretlenir; hicbiri gercekten bir sey calistirmaz.
export const KAYIT = {
  Abone:        { yanEtki: false, kur: (a) => ({ tur: "Abone", no: a.no }) },
  Okuma:        { yanEtki: false, kur: (a) => ({ tur: "Okuma", deger: a.deger }) },
  Fatura:       { yanEtki: false, kur: (a) => ({ tur: "Fatura", tutar: a.tutar }) },
  IsEmri:       { yanEtki: false, kur: (a) => ({ tur: "IsEmri", no: a.no }) },
  DosyaKaynagi: { yanEtki: true,  kur: (a) => ({ tur: "DosyaKaynagi", yol: a.yol }) },
  Zamanlayici:  { yanEtki: true,  kur: (a) => ({ tur: "Zamanlayici", her: a.her }) },
  DisCagri:     { yanEtki: true,  kur: (a) => ({ tur: "DisCagri", hedef: a.hedef }) },
  Gunlukcu:     { yanEtki: true,  kur: (a) => ({ tur: "Gunlukcu", kanal: a.kanal }) },
  Onbellek:     { yanEtki: true,  kur: (a) => ({ tur: "Onbellek", boyut: a.boyut }) },
};
export const IZIN = ["Abone", "Okuma", "Fatura", "IsEmri"];

// Gelen iletiler (kurgu): ucu sistemin gercekten alisverisini yaptigi bicimler.
export const MESAJ = [
  ["okuma yukleme", { $tur: "Okuma", deger: 412 }, "beklenen"],
  ["abone kaydi", { $tur: "Abone", no: "A-100432", ek: { $tur: "IsEmri", no: 12 } }, "beklenen"],
  ["fatura ozeti", { $tur: "Fatura", tutar: 512, kalem: [{ $tur: "Okuma", deger: 5 }] }, "beklenen"],
  ["gecmis alimi", { $tur: "Okuma", deger: 7, kaynak: { $tur: "DosyaKaynagi", yol: "d1" } }, "beklenmedik"],
  ["toplu is", { $tur: "IsEmri", no: 3,
    is: { $tur: "Zamanlayici", her: 60, adim: { $tur: "DisCagri", hedef: "h" } } }, "beklenmedik"],
  ["ayar tasima", { $tur: "Onbellek", boyut: 100 }, "beklenmedik"],
];

// Bir iletideki butun tur etiketlerini, hangi derinlikte olursa olsun toplar.
export const etiketler = (o) =>
  o === null || typeof o !== "object" ? []
    : (Array.isArray(o) ? [] : o.$tur ? [o.$tur] : []).concat(Object.values(o).flatMap(etiketler));
```

Etiket toplayıcı ölçünün ilk yarısıdır. Bir iletideki tür kararı üst düzeyde bir tane
değildir; ileti bir ağaçtır ve her düğüm kendi türünü söyleyebilir. Kapsama sayılırken
sayılacak birim ileti değil, **etiket taşıyan düğümdür**.

#### Kabul Edilen Tür Kümesi

Üç çözme biçimi aynı ileti kümesine uygulanıyor. Açık kayıt tanıdığı her türü kurar. İzin
listesi yalnız bildirilen dört türü kurar. Etiketsiz veri biçimi `$tur` alanını hiç okumaz;
gelen şeyi düz veri olarak bırakır.

```js
// kapsama.mjs — uc coz bicimi: acik kayit, izin listesi, etiketsiz veri bicimi
import { KAYIT, IZIN, MESAJ, etiketler } from "./bicim.mjs";

const BICIM = [
  ["acik kayit", Object.keys(KAYIT)],
  ["izin listesi", IZIN],
  ["etiketsiz veri bicimi", []],
];
const tumEtiket = MESAJ.flatMap(([, g]) => etiketler(g));
console.log(`${MESAJ.length} ileti, ${tumEtiket.length} tur etiketi tasiyan dugum, ` +
  `${new Set(tumEtiket).size} ayri tur\n`);
console.log(`${"coz bicimi".padEnd(24)}${"kabul tur".padStart(10)}${"kurulan dugum".padStart(14)}` +
  `${"yan etkili kurulum".padStart(20)}${"kabul ileti".padStart(12)}`);
for (const [ad, kabul] of BICIM) {
  let kurulan = 0, yanEtkili = 0, kabulIleti = 0;
  for (const [, govde] of MESAJ) {
    const e = etiketler(govde);
    const gecti = e.every((t) => kabul.includes(t));
    if (kabul.length === 0) { kabulIleti++; continue; }        // hicbir yapi kurulmaz
    if (gecti) { kabulIleti++; kurulan += e.length; yanEtkili += e.filter((t) => KAYIT[t].yanEtki).length; }
  }
  console.log(ad.padEnd(24) + String(kabul.length).padStart(10) + String(kurulan).padStart(14) +
    String(yanEtkili).padStart(20) + String(kabulIleti).padStart(12));
}
const erisilir = (k) => k.filter((t) => KAYIT[t].yanEtki).length;
console.log(`\nkayitta ${Object.keys(KAYIT).length} tur var, ${erisilir(Object.keys(KAYIT))} tanesi yan etkili`);
console.log(`izin listesi ${IZIN.length} tur kabul ediyor, ${erisilir(IZIN)} tanesi yan etkili`);
console.log(`etiketsiz bicimde yapi kurma noktasi: 0, elle esleme noktasi: ` +
  `${MESAJ.filter(([, , b]) => b === "beklenen").length}`);
```

```
6 ileti, 11 tur etiketi tasiyan dugum, 8 ayri tur

coz bicimi               kabul tur kurulan dugum  yan etkili kurulum kabul ileti
acik kayit                       9            11                   4           6
izin listesi                     4             5                   0           3
etiketsiz veri bicimi            0             0                   0           6

kayitta 9 tur var, 5 tanesi yan etkili
izin listesi 4 tur kabul ediyor, 0 tanesi yan etkili
etiketsiz bicimde yapi kurma noktasi: 0, elle esleme noktasi: 3
```

Açık kayıt altı iletinin altısını da kabul ediyor ve on bir düğümün on birini kuruyor;
dördü yan etkili türdür. Kabul edilen tür sayısı burada bir karar değil, bir **sonuçtur**:
kayıtta ne varsa kabul edilir.

İzin listesi kabul edilen türü dörde, kurulan düğümü beşe indiriyor ve yan etkili kurulumu
sıfırlıyor. Üç ileti reddediliyor ve üçü de beklenmedik olarak etiketlenmiş iletilerdir.
Reddin nedeni iletinin üst düzey türü değil, **derinlikteki bir düğümdür**: toplu iş
iletisinin kökü izin listesindedir, iki alt düğümü değildir.

Üçüncü satır ölçünün asıl noktasıdır. Etiketsiz veri biçiminde kurulan düğüm sıfırdır ve
bu bir savunmanın başarısı değildir — **kurulacak bir şey yoktur**. Tür kararı verinin
içinden gelmiyorsa saldırıya açık bir tür seçimi de yoktur. Karşılığında son satırdaki sayı
duruyor: uygulama gelen düz veriyi kendi türlerine üç ayrı yerde elle eşlemek zorundadır.

Üç biçimin kod tabanında yerleri de ayrıdır. Açık kayıt, iç servisler arasında tasarlanmış
bir kolaylıktır ve dış sınırda kaldığında ölçülen sayıyı üretir. İzin listesi, tür kararının
verinin içinden gelmesini kabul edip kümesini daraltır. Etiketsiz veri biçimi kararı verinin
dışına çıkarır ve ölçülecek bir tür kümesi bırakmaz. Seçim bir tercih değil, hangi sınırda
durulduğuyla ilgilidir: dış sınırda birinci biçimin ölçüsü kod tabanının büyüklüğüdür.

#### Denetimin Uygulandığı Derinlik

İzin listesi kapsamanın tamamını değil, uygulandığı yer kadarını verir. Bir ileti ağaçtır ve
denetim yalnız köke uygulanabilir.

```js
// derinlik.mjs — izin listesinin uygulandigi derinlik
import { KAYIT, IZIN, MESAJ, etiketler } from "./bicim.mjs";

const kok = (g) => (g && g.$tur ? [g.$tur] : []);
console.log(`${"denetimin uygulandigi yer".padEnd(28)}${"kabul ileti".padStart(12)}` +
  `${"kurulan dugum".padStart(14)}${"yan etkili kurulum".padStart(20)}`);
for (const [ad, sec] of [["yalniz kok dugum", kok], ["her dugum", etiketler]]) {
  let ileti = 0, kurulan = 0, yan = 0;
  for (const [, g] of MESAJ) {
    if (!sec(g).every((t) => IZIN.includes(t))) continue;
    ileti++;
    const e = etiketler(g);
    kurulan += e.length;
    yan += e.filter((t) => KAYIT[t].yanEtki).length;
  }
  console.log(ad.padEnd(28) + String(ileti).padStart(12) + String(kurulan).padStart(14) +
    String(yan).padStart(20));
}
const derin = MESAJ.filter(([, g]) => etiketler(g).length > 1);
console.log(`\n${MESAJ.length} iletinin ${derin.length} tanesi birden cok dugumde tur etiketi tasiyor`);
console.log(`en derin ileti: ${etiketler(MESAJ[4][1]).join(" -> ")}`);
```

```
denetimin uygulandigi yer    kabul ileti kurulan dugum  yan etkili kurulum
yalniz kok dugum                       5            10                   3
her dugum                              3             5                   0

6 iletinin 4 tanesi birden cok dugumde tur etiketi tasiyor
en derin ileti: IsEmri -> Zamanlayici -> DisCagri
```

Aynı izin listesi, yalnız kök düğüme uygulandığında beş iletiyi kabul ediyor ve on düğüm
kuruyor; bunların üçü yan etkili türdür. Her düğüme uygulandığında kabul edilen ileti üçe,
kurulan düğüm beşe, yan etkili kurulum sıfıra iniyor. **Liste aynı listedir; değişen tek
şey kaç düğümün görüldüğüdür.**

Son satır nedenini gösteriyor: altı iletinin dördü birden çok düğümde tür etiketi taşıyor ve
en derin ileti üç düzey iniyor. Kök düğüm denetimi bu iletilerin ilk düzeyini onaylayıp
kalanını görmeden geçiriyor. Önceki dersteki yeniden yönlendirme zinciriyle aynı biçimdir:
denetlenen değer ile kullanılan değer arasına bir adım girdiğinde kapsama kaybolur.

#### Yeni Tür Eklendiğinde

Kapsamanın sınavı, ölçüm yapıldıktan sonra kod tabanının değişmesiyle başlar. Üç yeni tür
ekleniyor ve hiçbiri seri durumdan çıkarmayı düşünerek yazılmıyor.

**GC16 (varsayım):** kayda tür eklemek bildirim gerektirmez; bir tür tanımlandığı anda
kayda girer. Ölçülen şey bu eklemenin kabul kümesine etkisidir.

```js
// yeni-tur.mjs — kod tabanina yeni tur eklendiginde kabul kumesi nasil degisiyor
import { KAYIT, IZIN } from "./bicim.mjs";

// Uc yeni tur kod tabanina giriyor; ikisi kurulurken bir is baslatiyor.
const YENI = { RaporIsi: { yanEtki: true }, Bildirim: { yanEtki: false }, AgIstemcisi: { yanEtki: true } };
const KAYIT2 = { ...KAYIT, ...YENI };

const yanEtkili = (kayit, kume) => kume.filter((t) => kayit[t].yanEtki).length;
console.log(`${"asama".padEnd(24)}${"kayitta tur".padStart(12)}${"acik kabul".padStart(12)}` +
  `${"izinli kabul".padStart(14)}${"kapatilan tur".padStart(15)}${"erisilen yan etkili".padStart(21)}`);
for (const [ad, k] of [["mevcut kod tabani", KAYIT], ["uc yeni tur eklendi", KAYIT2]]) {
  const tum = Object.keys(k);
  console.log(ad.padEnd(24) + String(tum.length).padStart(12) + String(tum.length).padStart(12) +
    String(IZIN.length).padStart(14) + String(tum.length - IZIN.length).padStart(15) +
    `${yanEtkili(k, tum)} / ${yanEtkili(k, IZIN)}`.padStart(21));
}
console.log(`\nyeni tur icin yazilan kod: acik kayitta 0 satir (tur tanimlandigi anda kabul edilir),`);
console.log(`izin listesinde 1 satir (mesru tur icin), etiketsiz bicimde 1 esleme noktasi`);
```

```
asama                    kayitta tur  acik kabul  izinli kabul  kapatilan tur  erisilen yan etkili
mevcut kod tabani                  9           9             4              5                5 / 0
uc yeni tur eklendi               12          12             4              8                7 / 0

yeni tur icin yazilan kod: acik kayitta 0 satir (tur tanimlandigi anda kabul edilir),
izin listesinde 1 satir (mesru tur icin), etiketsiz bicimde 1 esleme noktasi
```

Açık kabul sütunu kod tabanıyla birlikte büyüyor: dokuzdan on ikiye. Erişilen yan etkili tür
beşten yediye çıkıyor ve bu artış için kimsenin bir şey yazması gerekmedi — üç tür tanımlandı,
kabul kümesi kendiliğinden genişledi. **Açık kayıtta kapsama, kod tabanının büyümesiyle
ters yönde hareket eder.**

İzin listesi sütunu her iki satırda da dört. Kapatılan tür beşten sekize çıkıyor: liste
değişmediği hâlde kapsaması arttı, çünkü kapsama listenin uzunluğunda değil, **listenin
dışında bıraktığı kümededir**. Yan etkili erişim iki satırda da sıfır.

Etiketsiz biçim bu tabloda hiç görünmüyor, çünkü orada kabul edilen tür kümesi diye bir
büyüklük yoktur. Bedeli alt satırdadır: her yeni türün eşlemesi bir yerde elle yazılır.
Üç biçim üç ayrı yerde iş yapar — biri hiçbir şey yapmaz ve her şeyi kabul eder, biri bir
liste tutar, biri türü veriden tamamen çıkarır.

Üç eşleme noktası bugünkü sayıdır ve o da büyür. Etiketsiz biçimin bedeli sabit bir kerelik
iş değil, ileti biçimi başına tekrarlanan bir iştir: dördüncü bir meşru ileti biçimi
eklendiğinde dördüncü eşleme yazılır. Fark şudur ki bu iş **unutulduğunda ileti çözülmez** —
eksiklik bir açık değil, kırık bir akıştır ve ilk çağrıda görülür. İzin listesinde ise
unutulan bir satır kapsamayı sessizce daraltmaz, genişletmez de; kapsama listeye eklenmedikçe
sabittir.

#### Özet

- Ölçü birimi güvenilir tür kümesidir. Bir iletideki tür kararı tek değildir: altı ileti 11
  etiket taşıyan düğüm içeriyor ve red kararı çoğu zaman derinlikteki bir düğümden geliyor.
- Açık kayıt 9 türün 9'unu kabul etti, 11 düğümü kurdu ve 4'ü yan etkili türdü. İzin listesi
  kabul edilen türü 4'e, kurulan düğümü 5'e indirdi, yan etkili kurulumu 0'ladı ve 3 iletiyi
  reddetti.
- Etiketsiz veri biçiminde yapı kurma noktası 0'dır ve bu bir savunmanın başarısı değil,
  kararın veriden çıkarılmasıdır. Bedeli 3 elle eşleme noktasıdır.
- Kod tabanına 3 yeni tür eklendiğinde açık kabul 9'dan 12'ye, erişilen yan etkili tür 5'ten
  7'ye çıktı ve bunun için hiçbir satır yazılmadı. İzin listesinin kabulü 4'te kaldı,
  kapattığı tür 5'ten 8'e çıktı.
- Aynı izin listesi yalnız kök düğüme uygulandığında 5 ileti kabul edildi, 10 düğüm kuruldu ve
  3 yan etkili kurulum oldu; her düğüme uygulandığında bu sayılar 3, 5 ve 0. Altı iletinin
  4'ü birden çok düğümde etiket taşıyor.
- Kapsama listenin uzunluğunda değil, listenin dışında bıraktığı kümededir: liste sabit
  kaldığı hâlde kod tabanı büyüdükçe kapsaması artar.

#### Sonraki Adım

Yedi ders boyunca tek bir sınır çizildi: dışarıdan gelen veri nereye giriyor, nereden
çıkıyor, hangi metne katılıyor, hangi şablona ulaşıyor, hangi baytla saklanıyor, hangi
adrese gidiyor ve hangi türü kuruyor. Her birinde kapsama sayıldı ve dışarıda kalan nokta
yazıldı. Bütün bu ölçümlerin ortak varsayımı, sorunun **verinin kendisinde** olduğuydu.

Veri doğru olduğunda da bir soru kalır. Sözleşmeye tam uyan bir abone numarası, biçimi
kusursuz bir fatura dönemi ve izin listesinden geçen bir tür — hepsi geçerliyken, isteği
yapanın kim olduğu ve o kişinin bu kayda hakkı olup olmadığı hiç sorulmadı. Sonraki konu
**Kimlik, Oturum ve Yetki Hataları** bu soruyu aynı ölçüyle ele alır: kimlik doğrulama
gerçekleştiriminin sızdırdığı bilgi, oturumun yenilenmediği nokta ve kaç ucun nesne
düzeyinde denetim yaptığı.

## Kimlik, Oturum ve Yetki Hataları

Kimliğin belirlendiği ve kimin ne yapabileceğine karar verilen noktalarda kapsamanın ölçülmesi: yanıt süresi farkının ve ayrık hata mesajının sızdırdığı kullanıcı varlığı, oturum kimliğinin girişte yenilenmemesi ve çıkışta geçersiz kılınmaması, yalnız rol denetimi yapan uç sayısı, akış sırasının atlanabildiği yollar ve eşzamanlı isteklerin aynı kontrolden kaç kez geçtiği.

### Kimlik Doğrulama Gerçekleştirim Hataları

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/kimlik-dogrulama-gerceklestirim-hatalari

Gerçekleştirim hatasının sızdırılan bilgi cinsinden ölçülmesi: sekiz kimlik doğrulama noktasında ayrık hata mesajı, kayıt yokken atlanan iş ve ilk uyuşmazlıkta çıkan karşılaştırma üç kanal olarak sayılır ve beşi sızdırır, yanıt süresi farkı ham süre yazılmadan bant denetimiyle gösterilir, tek kanalı kapatan önlem beşten yalnız birini kapatır, üç kanalı birden kapatan merkezî doğrulayıcı dokuzuncu noktayı kendiliğinden örter ve dördüncü kanal sayıma katıldığında aynı kalıbın dışarıda bıraktığı nokta ikiye çıkar.

Önceki konu dışarıdan gelen verinin sınırını çizdi ve aynı üç sütunu her nokta kümesinde
yazdı: kaç nokta var, kalıp kaçını örtüyor, kaç nokta dışarıda kalıyor. Bütün o ölçümler tek
bir varsayımın üstünde durdu — isteğin arkasındaki kişinin kim olduğu zaten bilinmektedir.
Veri kurallara uygun olabilir ve yine de yanlış kişiden gelmiş olabilir.

Bu konu o varsayımı kaldırır. Kimlik doğrulamanın hangi akışla yapıldığı burada ele
alınmıyor; akışların türleri ve kimlik bilgisinin nasıl saklandığı Kimlik Doğrulama ve Yetkilendirme
kursunun konusudur. Burada ölçülen şey **gerçekleştirimdir**: doğru kimlik bilgisini hiç
bilmeyen birine sunucunun verdiği yanıt, doğru bilgiyi bilmeden de bir şey söylüyor mu?

#### Sızdırılan Bilgi Sayılabilir

Bir kimlik doğrulama noktası iki şeyi ayrı ayrı bilir: aranan kayıt var mı, sunulan kimlik
bilgisi doğru mu. Yanıt yalnız ikincisini söylemelidir. Birincisinin dışarıdan ayırt
edilebilmesine **kullanıcı numaralandırma (user enumeration)** denir: saldırgan hiçbir
parolayı bilmeden, hangi abone numaralarının kayıtlı olduğunu öğrenir. Bu bilgi tek başına
erişim vermez; sonraki her denemenin hedef kümesini daraltır.

Numaralandırmayı bir servis üzerinde denemek **yazılı yetki ve tanımlı kapsam olmadan hem
yasadışı hem meslek dışıdır**. Aşağıdaki envanter ve ölçüm, kurgu bölgesel ölçüm ağının
kendi kodu üzerinde koşar; gerçek bir servis, gerçek bir hesap ve gerçek bir parola yoktur.

```js
// kimlik/nokta.mjs — abone portalinin kimlik dogrulama noktalari (model): gercek bir servis,
// gercek bir hesap ve gercek bir parola yok. Rastgelelik yok, is miktari sabittir.
import { createHash } from "node:crypto";

// KH1: bir nokta kullanici varligini uc ayri kanaldan sizdirabilir — hata mesaji ayrik mi,
// kayit yokken yapilan is farkli mi, karsilastirma ilk uyusmazlikta cikiyor mu. Dorduncu alan
// `kilit` envanterde sayilmayan bir kanaldir; dersin son bolumunde sayima katilir.
export const NOKTA = [
  { ad: "portal-giris",           mesaj: "tek",   is: "sabit",       kars: "sabit",       kilit: "ayrik" },
  { ad: "portal-sifirlama",       mesaj: "ayrik", is: "erken-donus", kars: "sabit",       kilit: "tek" },
  { ad: "portal-eposta-degistir", mesaj: "ayrik", is: "sabit",       kars: "sabit",       kilit: "tek" },
  { ad: "saha-uygulamasi-giris",  mesaj: "tek",   is: "sabit",       kars: "sabit",       kilit: "tek" },
  { ad: "sayac-alim-anahtari",    mesaj: "tek",   is: "erken-donus", kars: "erken-cikis", kilit: "tek" },
  { ad: "fatura-sorgu",           mesaj: "ayrik", is: "erken-donus", kars: "erken-cikis", kilit: "tek" },
  { ad: "ikinci-etmen-kodu",      mesaj: "tek",   is: "sabit",       kars: "sabit",       kilit: "ayrik" },
  { ad: "rapor-ucu-anahtari",     mesaj: "tek",   is: "sabit",       kars: "erken-cikis", kilit: "tek" },
];

// KH2: is birimi sabit adimli bir karma dongusudur; ham sure degil, adim sayisi yazilir.
export const ARAMA = 2000, DOGRULAMA = 20000;
const dongu = (tohum, adim) => {
  let s = tohum;
  for (let i = 0; i < adim; i++) s = createHash("sha256").update(s).digest("hex");
  return s;
};
const KAYIT = new Set(["a-4471", "a-5120", "a-6033"]);

// Kusurlu gerceklestirim: kayit yoksa dogrulama isi hic yapilmaz, mesaj da ayrilir.
export function kusurlu(abone, sunulan) {
  dongu(abone, ARAMA);
  if (!KAYIT.has(abone)) return { mesaj: "abone bulunamadi", adim: ARAMA };
  dongu(sunulan, DOGRULAMA);
  return { mesaj: "parola hatali", adim: ARAMA + DOGRULAMA };
}

// Kalipli gerceklestirim: kayit yoksa da ayni is yapilir, mesaj tektir.
export function kalipli(abone, sunulan) {
  dongu(abone, ARAMA);
  const bulundu = KAYIT.has(abone);
  dongu(bulundu ? sunulan : "yer-tutucu-karsilastirma-tohumu", DOGRULAMA);
  return { mesaj: "abone numarasi ya da parola hatali", adim: ARAMA + DOGRULAMA };
}
```

```js
// kimlik/kapsama.mjs — kac nokta var, kalip kacini ortuyor, kac nokta disarida kaliyor.
import { NOKTA, kusurlu, kalipli } from "./nokta.mjs";
const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));

// KH3: bir nokta uc kanaldan en az biri aciksa kullanici varligini sizdiriyor sayilir.
const UC = ["mesaj", "is", "kars"];
const kanal = (n, kapali = [], liste = UC) => liste.filter((k) =>
  !kapali.includes(k) && n[k] !== "tek" && n[k] !== "sabit");
const A = [-24, 7, 14, 15, 8, 12];
console.log(`1. ${NOKTA.length} kimlik dogrulama noktasi ve acik kanallari`);
yaz(A, "nokta", "mesaj", "kayit yokken", "karsilastirma", "kanal", "sizdiriyor");
for (const n of NOKTA)
  yaz(A, n.ad, n.mesaj, n.is, n.kars, kanal(n).length, kanal(n).length ? "EVET" : "hayir");
const sizan = (kume, kapali, liste) => kume.filter((n) => kanal(n, kapali, liste).length).length;
console.log(`sizdiran nokta: ${sizan(NOKTA, [])} / ${NOKTA.length}`);

// KH4: sure ham olarak yazilmaz. Beklenen oran adim sayisindan turetilir; olculen deger
// yalniz bant denetiminden gecer ve evet/hayir yazilir.
const ortanca = (d) => d.slice().sort((a, b) => a - b)[d.length >> 1];
const olc = (f, abone) => {
  const d = [];
  for (let i = 0; i < 15; i++) {
    const t = process.hrtime.bigint();
    f(abone, "sunulan-deger");
    d.push(Number(process.hrtime.bigint() - t));
  }
  return ortanca(d);
};
const B = [-12, 11, 11, 15, 14, 9, 15];
console.log("\n2. yanit suresi farki (ham sure yazilmaz; beklenen oran adim sayisindan gelir)");
yaz(B, "gercekleme", "adim yok", "adim var", "beklenen oran", "bant", "bantta", "varlik siziyor");
for (const [ad, f] of [["kusurlu", kusurlu], ["kalipli", kalipli]]) {
  const yok = f("a-0000", "x").adim, evet = f("a-4471", "x").adim;
  const beklenen = evet / yok, alt = beklenen / 2, ust = beklenen * 2;
  const olculen = olc(f, "a-4471") / olc(f, "a-0000");
  yaz(B, ad, yok, evet, beklenen.toFixed(2), `${alt.toFixed(2)}..${ust.toFixed(2)}`,
    olculen >= alt && olculen <= ust ? "evet" : "HAYIR", beklenen > 1.5 ? "EVET" : "hayir");
}

// 3. kapsama: hangi onlem kac noktayi kendiliginden kapatiyor
const YENI = { ad: "saha-cihaz-eslesme", mesaj: "ayrik", is: "erken-donus", kars: "erken-cikis" };
const genis = [...NOKTA, YENI];
const C = [-34, 7, 11, 11];
console.log("\n3. onlem kac noktayi kapatiyor");
yaz(C, "onlem", "nokta", "sizdiran", "disarida %");
for (const [ad, k, kapali] of [
  ["bugunku dagitik durum", NOKTA, []],
  ["yalniz sabit sureli karsilastirma", NOKTA, ["kars"]],
  ["yalniz tek hata mesaji", NOKTA, ["mesaj"]],
  ["merkezi dogrulayici (uc kanal)", NOKTA, ["mesaj", "is", "kars"]],
  ["yeni nokta eklendi, dagitik", genis, []],
  ["yeni nokta eklendi, merkezi kalip", genis, ["mesaj", "is", "kars"]],
]) yaz(C, ad, k.length, sizan(k, kapali), ((sizan(k, kapali) / k.length) * 100).toFixed(1));

// 4. envanterin kendisi eksikse: kilit yaniti dorduncu kanal olarak sayima katilir
const DORT = [...UC, "kilit"];
console.log("\n4. dorduncu kanal sayima katilinca");
yaz(C, "sayim", "nokta", "sizdiran", "disarida %");
for (const [ad, liste, kapali] of [
  ["uc kanal, merkezi dogrulayici", UC, UC],
  ["dort kanal, merkezi dogrulayici", DORT, UC],
  ["dort kanal, kilit yaniti da tek", DORT, DORT],
]) yaz(C, ad, NOKTA.length, sizan(NOKTA, kapali, liste),
  ((sizan(NOKTA, kapali, liste) / NOKTA.length) * 100).toFixed(1));
```

```
1. 8 kimlik dogrulama noktasi ve acik kanallari
nokta                     mesaj  kayit yokken  karsilastirma   kanal  sizdiriyor
portal-giris                tek         sabit          sabit       0       hayir
portal-sifirlama          ayrik   erken-donus          sabit       2        EVET
portal-eposta-degistir    ayrik         sabit          sabit       1        EVET
saha-uygulamasi-giris       tek         sabit          sabit       0       hayir
sayac-alim-anahtari         tek   erken-donus    erken-cikis       2        EVET
fatura-sorgu              ayrik   erken-donus    erken-cikis       3        EVET
ikinci-etmen-kodu           tek         sabit          sabit       0       hayir
rapor-ucu-anahtari          tek         sabit    erken-cikis       1        EVET
sizdiran nokta: 5 / 8

2. yanit suresi farki (ham sure yazilmaz; beklenen oran adim sayisindan gelir)
gercekleme     adim yok   adim var  beklenen oran          bant   bantta varlik siziyor
kusurlu            2000      22000          11.00   5.50..22.00     evet           EVET
kalipli           22000      22000           1.00    0.50..2.00     evet          hayir

3. onlem kac noktayi kapatiyor
onlem                               nokta   sizdiran disarida %
bugunku dagitik durum                   8          5       62.5
yalniz sabit sureli karsilastirma       8          4       50.0
yalniz tek hata mesaji                  8          4       50.0
merkezi dogrulayici (uc kanal)          8          0        0.0
yeni nokta eklendi, dagitik             9          6       66.7
yeni nokta eklendi, merkezi kalip       9          0        0.0

4. dorduncu kanal sayima katilinca
sayim                               nokta   sizdiran disarida %
uc kanal, merkezi dogrulayici           8          0        0.0
dort kanal, merkezi dogrulayici         8          2       25.0
dort kanal, kilit yaniti da tek         8          0        0.0
```

Sayılar **ölçüm** sınıfındadır; girdileri KH1–KH4'tür.

#### Beş Nokta ve Üç Kanal

Birinci tablo envanteri açar. Sekiz noktanın **beşi** kullanıcı varlığını sızdırıyor ve
sızıntının kaç kanaldan geldiği noktadan noktaya değişiyor.

`portal-eposta-degistir` yalnız mesajdan sızdırır: kayıt yokken farklı bir metin döner, iş
miktarı aynıdır. `rapor-ucu-anahtari` tam tersidir; mesajı tektir ve kayıt yokken de aynı
işi yapar, ama anahtar karşılaştırması ilk uyuşmayan bayta gelince çıkar. `fatura-sorgu` üç
kanalı birden açık tutar.

Bu dağılım, envanterin niçin nokta nokta çıkarıldığını gösterir. "Hata mesajları tektir"
cümlesi bir kapsama iddiasıdır ve tabloda karşılığı vardır: mesaj sütununda `tek` yazan beş
nokta. Ama o beş noktanın ikisi başka bir kanaldan sızdırmayı sürdürüyor. Kanal sayısı bir
noktanın ne kadar açık olduğunu değil, **kaç ayrı yerde düzeltme gerektiğini** söyler.

Envanterde noktaların hepsinin bir giriş ekranı olmadığına dikkat etmek gerekir.
`sayac-alim-anahtari` ve `rapor-ucu-anahtari` insan görmeyen uçlardır; kimlik bilgisi bir
anahtardır, kimlik doğrulama yine de yapılır ve aynı üç kanal yine açıktır. Kimlik doğrulama
noktası sayılırken "kullanıcı girişi" aranırsa envanter eksik çıkar. Ölçüt tek cümledir: bir
noktada sunulan bir değer bilinen bir kayıtla karşılaştırılıyorsa, o nokta envantere girer.

#### Süre Yazılmaz, Bant Denetlenir

İkinci tablo bu dersin ölçüm kalıbıdır. Yanıt süresi farkı gerçek bir sızıntı kanalıdır ama
ham milisaniye bir ölçüm değildir: makineye, yüke ve o anki iş yüküne bağlıdır, aynı kodda
iki koşumda iki farklı sayı verir ve iki sayının karşılaştırılabilir olup olmadığı
bilinmez.

Ölçüm şöyle kurulur. Beklenen değer **girdiden türetilir**: kusurlu gerçekleştirim kayıt
yokken 2.000 adım, kayıt varken 22.000 adım yapar; beklenen oran 11,00'dir. Bant bu değerin
yarısı ile iki katı arasındadır. Ölçülen süre oranı yalnız bu banda düşüp düşmediğiyle
raporlanır — sayı çıktıya yazılmaz, `evet` yazılır. Kalıplı gerçekleştirimde iki yol da
22.000 adımdır, beklenen oran 1,00'dir ve ölçülen değer 0,50–2,00 bandındadır.

Son sütun kararı verir. Beklenen oran 1'e yakınsa süre farkından çıkarılabilecek bir bilgi
yoktur; 11 ise vardır. Bu ayrım koşumdan koşuma değişmez, çünkü ham süreye değil adım
sayısına dayanır. Ölçülen değerin işi yalnız modelin gerçekten böyle davrandığını
doğrulamaktır.

**Sabit süreli karşılaştırma (constant-time comparison)** bu kanalın yalnız bir parçasını
kapatır: karşılaştırmanın kendi içindeki erken çıkışı. Kayıt bulunamadığında doğrulama
işinin hiç yapılmaması ondan önce gelir ve sabit süreli karşılaştırma oraya dokunmaz.
İkinci tablodaki 11,00 oranı sabit süreli karşılaştırmayla 11,00 kalır.

#### Tek Kanalı Kapatan Önlem Ne Kapatıyor

Üçüncü tablo kapsamayı sayar. Bugünkü dağıtık durumda sekiz noktanın beşi sızdırıyor;
dışarıda kalan oran **%62,5**.

Yalnız sabit süreli karşılaştırma uygulanırsa sızdıran nokta sayısı 5'ten **4'e** iner. Tek
kapanan nokta `rapor-ucu-anahtari`'dır, çünkü onun tek açık kanalı karşılaştırmaydı. Yalnız
tek hata mesajı uygulandığında da sonuç aynıdır: 5'ten 4'e. İki önlem farklı noktaları
kapatır ama her ikisinin kapsaması birdir. Bir sızıntı sınıfını üç kanaldan yalnız biriyle
ölçen bir denetim, işin beşte dördünü açık bırakır.

Üç kanalı birlikte kapatan **merkezî doğrulayıcı** sızdıran nokta sayısını sıfıra indirir.
Kalıbın içeriği tek cümledir: kayıt bulunsa da bulunmasa da aynı iş yapılır, aynı mesaj
döner, karşılaştırma sabit sürelidir. Bu kalıp bir kural listesi değil, tek bir işlevdir;
noktalar onu çağırır.

Aradaki fark, kuralın nerede yazıldığıdır. Dağıtık savunmada "hata mesajları ayrılmasın"
kuralı sekiz ayrı yerde uygulanmak zorundadır ve doğru uygulandığı ancak sekiz yer tek tek
okunarak bilinir. Merkezî kalıpta aynı kural bir yerde uygulanır; okunacak yer birdir,
sınanacak yer birdir. Kapsama sayısının anlamı da budur: 8'de 8 kapsama, sekiz doğru
gerçekleştirim değil, **bir** doğru gerçekleştirim ve sekiz çağrıdır.

Son iki satır bu kursun üçüncü sorusunu yanıtlar. Envantere dokuzuncu bir nokta —
`saha-cihaz-eslesme` — eklendiğinde dağıtık durumda sızdıran nokta 5'ten 6'ya çıkar ve
dışarıda kalan oran %66,7'ye yükselir. Merkezî kalıpta aynı nokta hiçbir ek iş yapılmadan
kapsanır ve sızdıran nokta sayısı 0 kalır. Fark kalıbın gücünde değil, **yeni noktanın
savunmayı çağırmak zorunda olmasındadır**: dağıtık savunmada her yeni nokta bir açık adayı,
merkezî kalıpta yalnız bir çağrıdır.

#### Envanterin Kendisi Eksik Olabilir

Üçüncü tablonun son satırı sıfır yazıyor ve bu sayı yanıltıcıdır. Sıfır, "hiçbir nokta
sızdırmıyor" demez; **sayılan üç kanalda hiçbir nokta sızdırmıyor** der. Kapsama ölçüsü
sayılan kanal kümesine bağlıdır.

Dördüncü tablo envantere dördüncü bir kanal katar: art arda başarısız denemeden sonra hesabın
kilitlendiğini bildiren yanıt. Bu yanıt, kilitlenebilen bir hesabın **var olduğunu** söyler;
kayıtlı olmayan bir abone numarası hiçbir zaman kilitlenmez. Üç kanallı sayımda merkezî
doğrulayıcı sıfır veriyordu; dört kanallı sayımda aynı doğrulayıcı **2 noktada** sızdırıyor —
dışarıda kalan oran %25. Kilit kararı doğrulayıcının kendisinde değil, ondan sonra gelen
sayaç katmanında verilir; kalıp o katmanı çağırmadığı için oraya dokunmaz.

Son satır kalıbın nasıl genişletildiğini gösterir: kilit yanıtı da tek biçime çekildiğinde
sayı yeniden sıfırlanır. Buradaki ders kalıbın yetersizliği değil, **ölçünün sınırıdır**.
Kapsama yüzdesi bir savunmanın gücünü değil, envanterde sayılan kanal sayısına göre
konumunu verir. Yeni bir kanal keşfedildiğinde bütün geçmiş kapsama sayıları yeniden
hesaplanır; kalıbın değeri, bu hesabın tek bir yerde yapılabilmesidir.

#### Özet

- Kimlik doğrulama gerçekleştirimi, doğru kimlik bilgisi hiç bilinmeden de bilgi sızdırabilir;
  sızan bilgi kullanıcı varlığıdır ve nokta sayısıyla ölçülür.
- Sekiz noktalık envanterde üç kanal sayıldı — ayrık hata mesajı, kayıt yokken atlanan iş,
  ilk uyuşmazlıkta çıkan karşılaştırma — ve beş nokta en az bir kanaldan sızdırdı.
- Yanıt süresi farkı ham süreyle değil bant denetimiyle yazılır: beklenen oran adım
  sayısından türetilir, ölçülen değer yalnız bandın içinde olup olmadığıyla raporlanır.
- Sabit süreli karşılaştırma üç kanaldan yalnız birini kapatır; beş sızdıran noktanın dördü
  açık kalır. Tek hata mesajı da tek başına dördü açık bırakır.
- Üç kanalı birden kapatan merkezî doğrulayıcı sıfır sızdıran nokta verir ve dokuzuncu nokta
  eklendiğinde onu ek iş olmadan kapsar; dağıtık durumda aynı nokta dışarıda kalanı 6'ya
  çıkarır.
- Kapsama sayısı sayılan kanal kümesine bağlıdır: kilit yanıtı dördüncü kanal olarak
  eklendiğinde aynı merkezî doğrulayıcının dışarıda bıraktığı nokta 0'dan 2'ye çıkar.

#### Sonraki Adım

Bu ders kimliğin **belirlendiği** anı ölçtü. Doğrulama başarılı olduğunda ise ortaya yeni
bir nesne çıkar: sonraki isteklerde kimliği yeniden sormamak için üretilen oturum kimliği.
O kimliğin ne zaman üretildiği, doğrulama başarılı olduğunda değişip değişmediği ve
kullanıcı çıkış yaptığında sunucu tarafında gerçekten geçersiz kılınıp kılınmadığı ayrı
kararlardır — ve bu kararların her biri bir akışta ayrı ayrı verilir. Sonraki ders akış
kümesini çıkarır: kaç akış oturum kimliği üretiyor, kaçı üretirken eskisini yenilemiyor ve
sunucu tarafında geçersiz kılma olmadığında çıkıştan sonra kaç oturum yaşamaya devam ediyor.

### Oturum Yönetimi Hataları

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/oturum-yonetimi-hatalari

Oturum kararlarının akış kümesi üzerinde sayılması: yedi akışın dördü oturumun yetki düzeyini yükseltir ve ikisi kimliği yenilemediği için sabitleme noktasıdır, üç geçersiz kılma akışının ikisi sunucu tarafında hiçbir kaydı silmediği için sekiz oturum yaşamaya devam eder, merkezî oturum kalıbı bu sayıyı ikiye indirir, sekizinci akış eklendiğinde dağıtık kurulumda dışarıda kalan akış beşe çıkar ve kalıbı hiç çağırmayan devir akışının yalnız yetki sürümüne bağlı bir kalıpla kapsandığı ölçülür.

Önceki ders kimliğin belirlendiği anı ölçtü ve sekiz noktanın beşinin kullanıcı varlığını
sızdırdığını saydı. Doğrulama başarılı olduğunda ise ortaya yeni bir nesne çıkar: sonraki
isteklerde kimliği yeniden sormamak için üretilen oturum kimliği. Bu kimlik bir kez üretilir
ve sonrasında pek çok akış tarafından değiştirilir, yükseltilir ya da bırakılır.

Oturumun ne olduğu ve nasıl saklandığı Kimlik Doğrulama ve Yetkilendirme kursunda kuruldu. Burada ölçülen şey
kararların **nerede verildiğidir**. Oturumla ilgili her karar bir akışın içinde alınır; akış
sayısı arttıkça karar sayısı artar ve her karar ayrı yazıldığında bazıları yanlış yazılır.
Bu dersin sorusu şudur: kaç akış oturum kimliğine dokunuyor, kaçı doğru dokunuyor, kaçı
dışarıda kalıyor.

#### İki Kural, Yedi Akış

Oturum kimliğine dokunan akışlar iki türe ayrılır. **Yükseltme akışı** oturumun yetki
düzeyini artırır: anonim bir oturum girişli olur, girişli bir oturum ikinci etmeni
tamamlanmış olur, bir görevli oturumu yönetici yetkisi kazanır. Bu türde kural tektir —
düzey değiştiğinde oturum kimliği **yenilenir**. Yenilenmediğinde, düzey değişmeden önce
bilinen bir kimlik, düzey değiştikten sonra da geçerli kalır; buna **oturum sabitleme
(session fixation)** denir.

**Geçersiz kılma akışı** ise yetkiyi geri alır: çıkış, parola değişikliği, aboneliğin başka
bir kişiye devri. Bu türde kural sunucu tarafındadır — silinecek şey defterdeki kayıttır.
İstemci tarafında çerezin silinmesi defterle ilgili hiçbir şey yapmaz; kimliği elinde tutan
bir istemci onu göndermeye devam edebilir.

Aşağıdaki akış kümesi ve oturum defteri bir **modeldir**: gerçek bir oturum deposu, gerçek
bir çerez ve gerçek bir abone yoktur.

```js
// oturum/model.mjs — abone portalinin oturum akislari ve sunucu tarafi oturum defteri (model):
// gercek bir oturum deposu, gercek bir cerez ve gercek bir abone yok.

// KH5: bir akis oturumun yetki duzeyini yukseltiyorsa oturum kimligi yenilenmelidir
// (beklenen: "yenile"); yetkiyi geri aliyorsa etkilenen oturumlar sunucu tarafinda silinmelidir
// (beklenen: cikis icin "tek", digerleri icin "hepsi"). `simdi` alani bugunku davranistir.
export const AKIS = [
  { ad: "portal-giris",            tur: "yukselt",  simdi: "yenile" },
  { ad: "ikinci-etmen-onayi",      tur: "yukselt",  simdi: "yerinde" },
  { ad: "rol-yukseltme",           tur: "yukselt",  simdi: "yerinde" },
  { ad: "saha-uygulamasi-eslesme", tur: "yukselt",  simdi: "yenile" },
  { ad: "cikis",                   tur: "gecersiz", simdi: "tek" },
  { ad: "parola-degistirme",       tur: "gecersiz", simdi: "hicbiri" },
  { ad: "abone-devri",             tur: "gecersiz", simdi: "hicbiri" },
];
export const beklenen = (a) =>
  a.tur === "yukselt" ? "yenile" : a.ad === "cikis" ? "tek" : "hepsi";

// KH6: oturum defteri sunucu tarafindadir; bir kimlik ancak defterde kaydi varken cozulur.
// Istemci tarafinda cerezin silinmesi deftere dokunmaz. `surumlu` acikken kayit abonenin
// yetki surumunu tasir ve surum degistiginde silinmeden gecersizlesir.
export function defterAc({ surumlu = false } = {}) {
  const kayit = new Map(), surum = new Map();
  let sayac = 0;
  const s = (abone) => surum.get(abone) ?? 1;
  return {
    ac(abone, duzey) {
      const k = `o-${++sayac}`;
      kayit.set(k, { abone, duzey, surum: s(abone) });
      return k;
    },
    coz(k) {
      const v = kayit.get(k);
      if (!v) return null;
      return surumlu && v.surum !== s(v.abone) ? null : v;
    },
    yerinde(k, duzey) { const v = kayit.get(k); if (v) v.duzey = duzey; },
    sil: (k) => kayit.delete(k),
    silHepsi(abone) {
      let n = 0;
      for (const [k, v] of kayit) if (v.abone === abone) { kayit.delete(k); n++; }
      return n;
    },
    surumArtir(abone) { surum.set(abone, s(abone) + 1); },
    canli(abone) {
      return [...kayit.keys()].filter((k) => this.coz(k)?.abone === abone).length;
    },
  };
}
```

```js
// oturum/olcum.mjs — kac akis oturum kimligi uretiyor, kaci yenilemiyor, gecersiz kilma
// yoksa kac oturum yasamaya devam ediyor.
import { AKIS, beklenen, defterAc } from "./model.mjs";
const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));

// KH7: sabitleme noktasi, yukseltme akisindan sonra eski kimligin hala yukseltilmis duzeyi
// cozdugu akistir. Asagidaki dongu bunu defter uzerinde gercekten kosturur.
const yukseltme = AKIS.filter((a) => a.tur === "yukselt");
const A = [-24, 10, 22, 20];
console.log(`1. ${yukseltme.length} yukseltme akisi: eski kimlik giristen sonra ne cozuyor`);
yaz(A, "akis", "simdi", "eski kimligin duzeyi", "sabitleme noktasi");
let sabitleme = 0;
for (const a of yukseltme) {
  const d = defterAc();
  const eski = d.ac("a-4471", "anonim");
  if (a.simdi === "yenile") { d.sil(eski); d.ac("a-4471", "girisli"); }
  else d.yerinde(eski, "girisli");
  const duzey = d.coz(eski)?.duzey ?? "cozulmuyor";
  if (duzey === "girisli") sabitleme++;
  yaz(A, a.ad, a.simdi, duzey, duzey === "girisli" ? "EVET" : "hayir");
}
console.log(`oturum kimligi ureten akis: ${yukseltme.length}, yenilemeyen: ${sabitleme}`);

// KH8: her abonenin uc ayri uctan acilmis uc canli oturumu vardir; olculen sey bir gecersiz
// kilma akisindan sonra defterde kalan oturum sayisidir.
const uygula = (politika, d, k) =>
  politika === "tek" ? d.sil(k) : politika === "hepsi" ? d.silHepsi("a-4471") : 0;
const yasayan = (politika) => {
  const d = defterAc();
  const kimlikler = [d.ac("a-4471", "girisli"), d.ac("a-4471", "girisli"), d.ac("a-4471", "girisli")];
  uygula(politika, d, kimlikler[0]);
  return d.canli("a-4471");
};
const gecersiz = AKIS.filter((a) => a.tur === "gecersiz");
const B = [-22, 12, 10, 12, 10];
console.log("\n2. gecersiz kilma akislari (baslangic: her akista 3 canli oturum)");
yaz(B, "akis", "simdi", "yasayan", "beklenen", "yasayan");
let simdiToplam = 0, kalipToplam = 0;
for (const a of gecersiz) {
  const s = yasayan(a.simdi), b = yasayan(beklenen(a));
  simdiToplam += s; kalipToplam += b;
  yaz(B, a.ad, a.simdi, s, beklenen(a), b);
}
yaz(B, "toplam", "", simdiToplam, "", kalipToplam);

// 3. kapsama: kac akis kalibin bekledigi davranisi zaten yapiyor
const kapsanan = (kume) => kume.filter((a) => a.simdi === beklenen(a)).length;
const YENI = { ad: "saha-cihaz-degisimi", tur: "yukselt", simdi: "yerinde" };
const genis = [...AKIS, YENI];
const C = [-34, 6, 10, 10, 12];
console.log("\n3. akis kumesi kapsamasi");
yaz(C, "kume", "akis", "kapsanan", "disarida", "disarida %");
for (const [ad, k, merkezi] of [
  ["bugunku dagitik durum", AKIS, false],
  ["merkezi oturum kalibi", AKIS, true],
  ["yeni akis eklendi, dagitik", genis, false],
  ["yeni akis eklendi, merkezi kalip", genis, true],
]) {
  const kap = merkezi ? k.length : kapsanan(k);
  yaz(C, ad, k.length, kap, k.length - kap, (((k.length - kap) / k.length) * 100).toFixed(1));
}

// 4. kalibin ulasamadigi akis: `abone-devri` abonelik kaydini portalin disindan degistirir ve
// oturum kalibini cagirmayabilir. Surume bagli kalipta cagri gerekmez.
const devir = (surumlu, bildirir) => {
  const d = defterAc({ surumlu });
  d.ac("a-4471", "girisli"); d.ac("a-4471", "girisli"); d.ac("a-4471", "girisli");
  if (bildirir) d.silHepsi("a-4471");
  d.surumArtir("a-4471");                       // abonelik kaydi her durumda degisir
  return d.canli("a-4471");
};
const D = [-24, 16, 16, 16];
console.log("\n4. abone-devri akisi (3 canli oturum): kalip cagrilmazsa ne oluyor");
yaz(D, "kurulum", "olay bildirdi", "yasayan oturum", "kapsanan akis");
for (const [ad, surumlu, bildirir] of [
  ["cagriya bagli kalip", false, true],
  ["cagriya bagli kalip", false, false],
  ["surume bagli kalip", true, false],
]) yaz(D, ad, bildirir ? "evet" : "hayir", devir(surumlu, bildirir),
  `${surumlu || bildirir ? gecersiz.length : gecersiz.length - 1} / ${gecersiz.length}`);
```

```
1. 4 yukseltme akisi: eski kimlik giristen sonra ne cozuyor
akis                         simdi  eski kimligin duzeyi   sabitleme noktasi
portal-giris                yenile            cozulmuyor               hayir
ikinci-etmen-onayi         yerinde               girisli                EVET
rol-yukseltme              yerinde               girisli                EVET
saha-uygulamasi-eslesme     yenile            cozulmuyor               hayir
oturum kimligi ureten akis: 4, yenilemeyen: 2

2. gecersiz kilma akislari (baslangic: her akista 3 canli oturum)
akis                         simdi   yasayan    beklenen   yasayan
cikis                          tek         2         tek         2
parola-degistirme          hicbiri         3       hepsi         0
abone-devri                hicbiri         3       hepsi         0
toplam                                     8                     2

3. akis kumesi kapsamasi
kume                                akis  kapsanan  disarida  disarida %
bugunku dagitik durum                  7         3         4        57.1
merkezi oturum kalibi                  7         7         0         0.0
yeni akis eklendi, dagitik             8         3         5        62.5
yeni akis eklendi, merkezi kalip       8         8         0         0.0

4. abone-devri akisi (3 canli oturum): kalip cagrilmazsa ne oluyor
kurulum                    olay bildirdi  yasayan oturum   kapsanan akis
cagriya bagli kalip                 evet               0           3 / 3
cagriya bagli kalip                hayir               3           2 / 3
surume bagli kalip                 hayir               0           3 / 3
```

Sayılar **ölçüm** sınıfındadır; girdileri KH5–KH8'dir.

#### Dört Akış Üretiyor, İkisi Yenilemiyor

Birinci tablo yükseltme tarafını sayar. Dört akış oturumun yetki düzeyini değiştiriyor;
ikisi kimliği yeniliyor, ikisi kaydı **yerinde** yükseltiyor. Yerinde yükselten iki akışta
eski kimlik `girisli` düzeyini çözmeye devam ediyor; yenileyen iki akışta `cozulmuyor`.

İki sabitleme noktasının hangileri olduğu öğreticidir. `portal-giris` doğru yazılmıştır —
bu akış herkesin baktığı akıştır ve kural en çok orada bilinir. Yanlış yazılan ikisi
`ikinci-etmen-onayi` ve `rol-yukseltme`'dir: ikisi de girişten **sonra** gelen ve girişin
kendisi sanılmayan akışlardır. Kural "girişte kimlik yenilenir" biçiminde ezberlendiğinde,
düzeyi girişten sonra artıran akışlar kuralın dışında kalır.

Sayının anlamı da buradadır. "Oturum sabitlemeye karşı korunuyoruz" cümlesi tek bir akış
hakkında doğru, dört akış hakkında yarı doğrudur. Ölçülebilir biçimi şudur: **oturum
kimliği üreten akış 4, yenilemeyen 2.**

Yerinde yükseltmenin niçin yazıldığını görmek de kolaydır. Kaydı yerinde güncellemek daha
az iş yapar: yeni bir kimlik üretilmez, istemciye yeni bir değer gönderilmez, aradaki
istekler etkilenmez. Yenileme ise kaydın kopyalanmasını, eskisinin silinmesini ve istemcinin
yeni değeri almasını gerektirir. Sabitleme noktalarının ikisinin de bu ucuz yolu seçmiş
olması bir dikkatsizlik değil, kuralın o akışta hiç sorulmamış olmasıdır.

#### Sekiz Oturum Yaşamaya Devam Ediyor

İkinci tablo geçersiz kılma tarafını sayar. Her akış üç canlı oturumu olan bir abone
üzerinde koşuyor; ölçülen şey akıştan sonra defterde kalan kayıt sayısıdır.

`cikis` akışı kendi oturumunu siliyor ve geriye iki oturum kalıyor. Bu doğrudur: bir uçtan
çıkmak öteki uçlardaki oturumları kapatmaz. `parola-degistirme` ve `abone-devri` ise
defterde hiçbir kaydı silmiyor; üçü de yaşamaya devam ediyor. Toplam sekiz oturum, yetkinin
geri alındığı varsayılan üç akıştan sonra hâlâ canlı.

İki akışın niçin bu durumda olduğu, işin nerede yapıldığıyla ilgilidir. Parola değiştirme
akışının görünür işi kimlik bilgisini güncellemektir; oturum defterine dokunmak o işin
parçası gibi görünmez. Abone devri ise portalın değil, aboneliği yöneten tarafın akışıdır ve
oturum defterinin varlığından haberdar olmayabilir. Her iki durumda da eksik olan şey bir
denetim değil, **bir çağrıdır**.

Sayının nasıl ölçüldüğü de önemlidir. Her akış üç canlı oturumu olan bir abone üzerinde
koşuyor; üç oturum aynı kişinin ayrı uçlarıdır. Abone başına oturum sayısı arttıkça, geçersiz
kılmayan bir akışın bıraktığı oturum sayısı doğrudan artar. Bu yüzden ölçünün birimi "akış"
değil, **akıştan sonra yaşayan oturum**tur: iki eksik akış, oturum sayısıyla çarpılarak
büyüyen bir kalemdir.

Beklenen sütun aynı akışları doğru politikayla koşuyor: `cikis` için `tek`, diğer ikisi için
`hepsi`. Toplam sekizden **ikiye** iniyor ve kalan iki oturum çıkış akışının öteki
uçlarındaki oturumlardır — kapatılmaması gereken oturumlar. Ölçüm burada bir şeyi daha
gösteriyor: doğru sayı sıfır değildir. "Hepsini kapat" her akış için doğru olsaydı,
kullanıcı bir uçtan çıktığında öteki uçlardan da düşerdi.

#### Kalıbın Kapsadığı Akış

Üçüncü tablo kapsamayı sayar. Yedi akışın **üçü** kalıbın beklediği davranışı yapıyor;
dördü dışarıda — dışarıda kalan oran %57,1.

Merkezî oturum kalıbı tek bir işlevdir ve iki şeyi bir yerde tutar: hangi olayın hangi
politikaya karşılık geldiği ve o politikanın defter üzerinde nasıl uygulandığı. Akışlar kararı
kendileri vermez, olayı bildirir. Bu kurulumda kapsanan akış sayısı 7'dir ve dışarıda kalan
sıfırdır — ama bu sıfır, kalıbın akıllı olmasından değil, kararın **bir kez** verilmiş
olmasından gelir.

Son iki satır yeni akış sorusunu yanıtlar. Sekizinci akış — `saha-cihaz-degisimi`, bir saha
görevlisinin uçbirimini değiştirmesi — dağıtık kurulumda dışarıda kalanı 4'ten 5'e çıkarır
ve oran %62,5'e yükselir. Merkezî kalıpta aynı akış bir olay bildirir ve dışarıda kalan
sıfır kalır. Dağıtık kurulumda her yeni akış, iki kuralın ikisini de yeniden hatırlamayı
gerektirir; kalıpta yalnız olayın adını doğru yazmayı.

#### Kalıbı Çağırmayan Akış

Dördüncü tablo, üçüncü tablodaki sıfırın hangi varsayıma dayandığını açar. Merkezî kalıp bir
**çağrı** kalıbıdır: akış olayı bildirdiğinde çalışır, bildirmediğinde çalışmaz. Bu varsayım
yedi akışın altısı için sorunsuzdur, çünkü altısı portalın kendi kodudur.

`abone-devri` farklıdır. Abonelik kaydını değiştiren taraf portal değildir; devir, abonelik
kaydını doğrudan güncelleyen ayrı bir akıştır. Tablo iki durumu ayırıyor: akış olayı
bildirdiğinde üç oturum sıfıra iniyor, bildirmediğinde **üçü de yaşamaya devam ediyor** ve
kapsanan akış 3'ten 2'ye düşüyor. Merkezî kalıp yerinde duruyor, doğru yazılmış ve
çalışıyor; kapsamadığı tek şey onu çağırmayan akış.

Son satır kalıbın biçimini değiştiriyor. Oturum kaydı abonenin **yetki sürümünü** taşırsa,
çözümleme her istekte kayıttaki sürümü abonenin güncel sürümüyle karşılaştırır. Devir akışı
abonelik kaydını değiştirdiğinde sürüm zaten artar; oturum kalıbını hiç çağırmamasına karşın
üç oturum da geçersizleşir — yaşayan oturum 0, kapsanan akış 3/3. Fark, savunmanın nereye
bağlandığıdır: çağrıya bağlı kalıp akışın hatırlamasını bekler, veriye bağlı kalıp akışın
zaten yaptığı değişikliği ölçüt olarak kullanır. İkincisinin kapsaması, akış kümesi
büyüdükçe sabit kalır.

#### Özet

- Oturum kararları iki kurala indirgenir: yetki düzeyi yükselten akış kimliği yeniler, yetki
  geri alan akış sunucu tarafındaki kaydı siler. İkisi de akış başına ayrı verilir.
- Yedi akışın dördü oturum kimliği üretiyor; ikisi kimliği yenilemediği için sabitleme
  noktasıdır ve yanlış yazılan ikisi girişten sonra gelen akışlardır.
- Üç geçersiz kılma akışının ikisi defterde hiçbir kaydı silmiyor; üçünden sonra sekiz oturum
  yaşamaya devam ediyor, doğru politikayla bu sayı ikiye iniyor.
- Doğru sayı sıfır değildir: çıkış akışından sonra öteki uçlardaki iki oturumun yaşaması
  beklenen sonuçtur; politika akış başına ayrılır.
- Yedi akışın üçü kalıbın beklediğini yapıyor, dördü dışarıda kalıyor (%57,1); merkezî kalıpta
  sekizinci akış eklendiğinde dışarıda kalan sıfır kalırken dağıtık kurulumda beşe çıkıyor.
- Çağrıya bağlı kalıp, kendisini çağırmayan akışı kapsamaz: devir akışı olayı bildirmediğinde
  üç oturum da yaşıyor. Oturum kaydı abonenin yetki sürümünü taşıdığında aynı akış hiçbir
  çağrı yapmadan üç oturumu geçersiz kılıyor.

#### Sonraki Adım

Bu iki ders isteğin arkasındaki kimliği güvence altına aldı: kim olduğu sızdırılmadan
belirlendi ve o kimliğin taşıyıcısı doğru anlarda yenilendi, doğru anlarda silindi. Kimliğin
doğru bilinmesi ise kararın yalnız yarısıdır. Bilinen bir kimlik bir uca istek yolladığında
sorulması gereken ikinci soru vardır ve bu soru uç başına sorulur: bu kimlik **bu nesneye**
dokunabilir mi? Sonraki ders yetki matrisini çıkarır ve konunun en önemli sayısını verir:
kaç uç yalnız rol denetimi yapıyor, kaçı istenen nesnenin sahibini de denetliyor, yeni bir uç
eklendiğinde merkezî kalıp onu kapsıyor mu.

### Bozuk Erişim Denetimi

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/bozuk-erisim-denetimi

Yetki matrisinde nesne düzeyinde denetimi olmayan uç sayısının çıkarılması: on dört ucun beşi yalnız rol denetimi yapar, rol denetimini geçen yetmiş iki isteğin on dördü başkasının nesnesine ulaşır, öntanımlı ret üzerine kurulu merkezî kapı bu sayıyı sıfıra indirirken on meşru isteği durdurur ve ölçüt bildirmeden eklenen on beşinci uç dağıtık denetimde yanlış geçeni on yediye çıkarırken merkezî kapıda hiçbir yeni yol açmaz.

Önceki iki ders isteğin arkasındaki kimliği güvence altına aldı: kim olduğu sızdırılmadan
belirlendi, kimliğin taşıyıcısı doğru anlarda yenilendi ve doğru anlarda silindi. Kimliğin
doğru bilinmesi kararın yalnız yarısıdır. Bilinen bir kimlik bir uca istek yolladığında
ikinci soru sorulur ve bu soru uç başına sorulur.

Sorunun iki ayrı biçimi vardır ve karıştırıldığında hata doğar. Birincisi "bu rol bu eylemi
yapabilir mi?", ikincisi "bu asıl **bu nesneye** dokunabilir mi?". Yalnız birincisi sorulan
bir uç, kendi rolündeki herkesin bütün nesnelerini açar. **Nesne düzeyinde yetki atlaması**
budur: geçerli bir oturumla, doğru rolle, tek bir yol parametresi değiştirilerek yapılır.
Bunu bir servis üzerinde denemek **yazılı yetki ve tanımlı kapsam olmadan hem yasadışı hem
meslek dışıdır**; aşağıdaki ölçüm kendi yazdığımız matris üzerinde koşar.

#### Matris, Asıl ve Nesne

Ölçümün girdisi bir yetki matrisidir: her uç için hangi rollerin çağırabildiği ve istenen
nesnenin istekle nasıl ilişkilendirildiği. İkinci alan iki kez yazılır — ucun **taşıması
gereken** ölçüt ve kodun bugün **gerçekten uyguladığı** ölçüt. İkisinin ayrı yazılması,
eksikliğin bir sayıya dönüşmesini sağlar.

Ölçüt üç biçimde yazılıyor. `sahip`, nesnenin doğrudan isteği yapana ait olmasıdır;
`atanan`, bir iş emrinin o görevliye verilmiş olmasıdır; `sube`, nesnenin isteği yapanla
aynı şubeye bağlı olmasıdır. Üçü de asıl ile nesne arasındaki bir **ilişkiyi** sınar — rol
gibi asılın tek başına taşıdığı bir nitelik değil. Yönetici rolü bu üç ölçütün hepsinde
şube sınırına indirgenir: yöneticinin ayrıcalığı bütün nesnelere erişmek değil, kendi
şubesinin nesnelerine erişmektir.

Aşağıdaki matris, asıl kümesi ve nesne kümesi bir **modeldir**: gerçek bir servis, gerçek
bir uç ve gerçek bir abone yoktur.

```js
// yetki/matris.mjs — abone portalinin yetki matrisi (model): gercek bir servis, gercek bir uc
// ve gercek bir abone yok. Roller, nesneler ve uclar veri olarak yazilmistir.

// KH9: her ucun iki ayri denetimi vardir — rol denetimi eylem turunu, olcut denetimi istenen
// nesnenin istegi yapanla iliskisini yetkilendirir. `dogru` o ucun tasimasi gereken olcuttur;
// `bildirilen` kodun bugun gercekten uyguladigi olcuttur (null: nesne denetimi yok).
export const UC = [
  { yol: "GET /faturalar/{id}",           tur: "fatura", rol: ["abone", "yonetici"], dogru: "sahip",  bildirilen: "sahip" },
  { yol: "GET /faturalar/{id}/kalemler",  tur: "fatura", rol: ["abone", "yonetici"], dogru: "sahip",  bildirilen: null },
  { yol: "POST /faturalar/{id}/itiraz",   tur: "fatura", rol: ["abone"],             dogru: "sahip",  bildirilen: null },
  { yol: "DELETE /faturalar/{id}",        tur: "fatura", rol: ["yonetici"],          dogru: "sube",   bildirilen: "sube" },
  { yol: "GET /okuma/{id}",               tur: "okuma",  rol: ["abone", "gorevli", "yonetici"], dogru: "sahip", bildirilen: "sahip" },
  { yol: "GET /okuma/{id}/gecmis",        tur: "okuma",  rol: ["abone", "gorevli"],  dogru: "sahip",  bildirilen: null },
  { yol: "POST /okuma/{id}/duzeltme",     tur: "okuma",  rol: ["gorevli", "yonetici"], dogru: "sube", bildirilen: "sube" },
  { yol: "GET /abone/{id}/profil",        tur: "abone",  rol: ["abone", "yonetici"], dogru: "sahip",  bildirilen: "sahip" },
  { yol: "PUT /abone/{id}/iletisim",      tur: "abone",  rol: ["abone"],             dogru: "sahip",  bildirilen: "sahip" },
  { yol: "POST /abone/{id}/tarife",       tur: "abone",  rol: ["yonetici"],          dogru: "sube",   bildirilen: "sube" },
  { yol: "GET /is-emri/{id}",             tur: "isemri", rol: ["gorevli", "yonetici"], dogru: "atanan", bildirilen: "atanan" },
  { yol: "POST /is-emri/{id}/kapat",      tur: "isemri", rol: ["gorevli", "yonetici"], dogru: "atanan", bildirilen: "atanan" },
  { yol: "POST /is-emri/{id}/not",        tur: "isemri", rol: ["gorevli"],           dogru: "atanan", bildirilen: null },
  { yol: "GET /rapor/{id}",               tur: "rapor",  rol: ["yonetici"],          dogru: "sube",   bildirilen: null },
];

// KH10: asil ve nesne kumeleri kurgudur; her nesnenin bir subesi, kimisinin sahibi ya da
// atanani vardir.
export const ASIL = [
  { kod: "a-4471", rol: "abone",    sube: "S-1" },
  { kod: "a-5120", rol: "abone",    sube: "S-2" },
  { kod: "g-31",   rol: "gorevli",  sube: "S-1" },
  { kod: "g-47",   rol: "gorevli",  sube: "S-2" },
  { kod: "y-08",   rol: "yonetici", sube: "S-1" },
];
export const NESNE = [
  { kod: "f-1", tur: "fatura", sahip: "a-4471", sube: "S-1" },
  { kod: "f-2", tur: "fatura", sahip: "a-5120", sube: "S-2" },
  { kod: "o-1", tur: "okuma",  sahip: "a-4471", sube: "S-1" },
  { kod: "o-2", tur: "okuma",  sahip: "a-5120", sube: "S-2" },
  { kod: "b-1", tur: "abone",  sahip: "a-4471", sube: "S-1" },
  { kod: "b-2", tur: "abone",  sahip: "a-5120", sube: "S-2" },
  { kod: "i-1", tur: "isemri", atanan: "g-31",  sube: "S-1" },
  { kod: "i-2", tur: "isemri", atanan: "g-47",  sube: "S-2" },
  { kod: "r-1", tur: "rapor",  sube: "S-1" },
  { kod: "r-2", tur: "rapor",  sube: "S-2" },
];

// KH11: olcut, asil ile nesne arasindaki iliskiyi sinar. Yonetici kendi subesinin nesnelerine
// erisir; sahiplik ve atanma dogrudan kod esitligidir.
export const olcut = (ad, asil, nesne) =>
  ad === "sube" ? nesne.sube === asil.sube
  : asil.rol === "yonetici" ? nesne.sube === asil.sube
  : ad === "sahip" ? nesne.sahip === asil.kod
  : nesne.atanan === asil.kod;
export const rolGecer = (uc, asil) => uc.rol.includes(asil.rol);
```

```js
// yetki/kapsama.mjs — kac uc nesne duzeyinde denetim yapiyor, denetimsiz uctan kac istek
// baskasinin nesnesine ulasiyor, merkezi kapi kacini kapatiyor ve kac mesru istegi durduruyor.
import { UC, ASIL, NESNE, olcut, rolGecer } from "./matris.mjs";
const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));

// KH12: istek kumesi, rol denetimini gecen her (asil, uc, nesne) uclusudur. Uc kurulum ayni
// kume uzerinde kosar: dogru karar, dagitik denetim, merkezi kapi (olcut yoksa ontanimli ret).
const istekler = (kume) => {
  const l = [];
  for (const uc of kume) for (const a of ASIL) for (const n of NESNE)
    if (n.tur === uc.tur && rolGecer(uc, a)) l.push({ uc, a, n });
  return l;
};
const karar = (i, kurulum) => {
  if (kurulum === "dogru") return olcut(i.uc.dogru, i.a, i.n);
  if (i.uc.bildirilen) return olcut(i.uc.bildirilen, i.a, i.n);
  return kurulum === "dagitik";                       // merkezi kapida ontanimli ret
};
const say = (kume, kurulum, kosul) => istekler(kume).filter((i) => kosul(
  karar(i, kurulum), karar(i, "dogru"))).length;

const A = [-30, 6, 9, 12, 14];
console.log(`1. ${UC.length} uc: hangisi nesne duzeyinde denetim yapiyor`);
yaz(A, "uc", "rol", "dogru", "bildirilen", "yanlis gecen");
for (const uc of UC) {
  const y = istekler([uc]).filter((i) => karar(i, "dagitik") && !karar(i, "dogru")).length;
  yaz(A, uc.yol, uc.rol.length, uc.dogru, uc.bildirilen ?? "-", y || "-");
}
const olcutsuz = UC.filter((u) => !u.bildirilen);
console.log(`nesne duzeyinde denetimi olmayan uc: ${olcutsuz.length} / ${UC.length}`);

const B = [-38, 9, 9];
console.log("\n2. rol denetimini gecen istek kumesi");
yaz(B, "olcum", "istek", "pay %");
const T = istekler(UC).length;
const satir = (ad, n) => yaz(B, ad, n, ((n / T) * 100).toFixed(1));
satir("rol denetimini gecen istek", T);
satir("gecmesi gereken (dogru karar)", say(UC, "dogru", (k) => k));
satir("dagitik denetimde gecen", say(UC, "dagitik", (k) => k));
satir("dagitik: yanlis gecen istek", say(UC, "dagitik", (k, d) => k && !d));
satir("merkezi kapi: yanlis gecen", say(UC, "merkezi", (k, d) => k && !d));
satir("merkezi kapi: durdurulan mesru istek", say(UC, "merkezi", (k, d) => !k && d));

// 3. yeni uc eklendiginde: olcut bildirmeden eklenen bir uc
const YENI = { yol: "GET /fatura/{id}/odeme-plani", tur: "fatura", rol: ["abone", "yonetici"],
  dogru: "sahip", bildirilen: null };
const genis = [...UC, YENI];
const C = [-30, 6, 11, 14, 18];
console.log("\n3. olcut bildirmeyen yeni uc eklendiginde");
yaz(C, "kurulum", "uc", "olcutsuz", "yanlis gecen", "durdurulan mesru");
for (const [ad, kume, kurulum] of [
  ["bugunku dagitik denetim", UC, "dagitik"],
  ["merkezi kapi", UC, "merkezi"],
  ["yeni uc, dagitik denetim", genis, "dagitik"],
  ["yeni uc, merkezi kapi", genis, "merkezi"],
]) yaz(C, ad, kume.length, kume.filter((u) => !u.bildirilen).length,
  say(kume, kurulum, (k, d) => k && !d), say(kume, kurulum, (k, d) => !k && d));
```

```
1. 14 uc: hangisi nesne duzeyinde denetim yapiyor
uc                               rol    dogru  bildirilen  yanlis gecen
GET /faturalar/{id}                2    sahip       sahip             -
GET /faturalar/{id}/kalemler       2    sahip           -             3
POST /faturalar/{id}/itiraz        1    sahip           -             2
DELETE /faturalar/{id}             1     sube        sube             -
GET /okuma/{id}                    3    sahip       sahip             -
GET /okuma/{id}/gecmis             2    sahip           -             6
POST /okuma/{id}/duzeltme          2     sube        sube             -
GET /abone/{id}/profil             2    sahip       sahip             -
PUT /abone/{id}/iletisim           1    sahip       sahip             -
POST /abone/{id}/tarife            1     sube        sube             -
GET /is-emri/{id}                  2   atanan      atanan             -
POST /is-emri/{id}/kapat           2   atanan      atanan             -
POST /is-emri/{id}/not             1   atanan           -             2
GET /rapor/{id}                    1     sube           -             1
nesne duzeyinde denetimi olmayan uc: 5 / 14

2. rol denetimini gecen istek kumesi
olcum                                     istek    pay %
rol denetimini gecen istek                   72    100.0
gecmesi gereken (dogru karar)                32     44.4
dagitik denetimde gecen                      46     63.9
dagitik: yanlis gecen istek                  14     19.4
merkezi kapi: yanlis gecen                    0      0.0
merkezi kapi: durdurulan mesru istek         10     13.9

3. olcut bildirmeyen yeni uc eklendiginde
kurulum                           uc   olcutsuz  yanlis gecen  durdurulan mesru
bugunku dagitik denetim           14          5            14                 0
merkezi kapi                      14          5             0                10
yeni uc, dagitik denetim          15          6            17                 0
yeni uc, merkezi kapi             15          6             0                13
```

Sayılar **ölçüm** sınıfındadır; girdileri KH9–KH12'dir.

#### Beş Uç, On Dört İstek

Birinci tablo envanteri verir: on dört ucun **beşi** nesne düzeyinde denetim yapmıyor.
Bunlar `bildirilen` sütununda çizgi taşıyan uçlardır ve her birinin yanında kaç isteğin
yanlış geçtiği yazılıdır.

Beş ucun dördü **alt kaynak** ya da **alt eylem**tir: fatura kalemleri, okuma geçmişi, iş
emri notu ve fatura itirazı. Üst kaynak için yazılmış ölçüt alt yola uygulanmış sayılır ve
soru orada bir daha sorulmaz. Beşincisi `GET /rapor/{id}`, tek rollü bir uçtur; tek rol
yetki denetiminin tamamı sanılmıştır.

Yanlış geçen istek sayıları uçlar arasında eşit değildir. `GET /okuma/{id}/gecmis` altı
istek sızdırıyor, `GET /rapor/{id}` yalnız bir. Fark rol sayısından gelir: okuma geçmişi iki
role açıktır ve iki rolün toplam dört asılı vardır, rapor ucu tek role ve tek asıla. Bir
ucun taşıdığı risk, ölçütünün eksik olmasıyla **rol kümesinin genişliğinin** çarpımıdır.
"Beş uç eksik" cümlesi bu farkı göstermez; istek sayısı gösterir.

Sıralama düzeltme sırasını da verir. Beş ucun tamamı aynı gün düzeltilemiyorsa, altı istek
sızdıran uç iki istek sızdırandan önce gelir; iki ucu düzeltmek sızıntının %64'ünü kapatır.
Bu sıralamanın kod incelemesinde görülemeyeceğine dikkat etmek gerekir: kodda beş uç da aynı
görünür, tek satır eksiktir. Farkı üreten şey kodun kendisi değil, ucun rol kümesi ile nesne
kümesinin kesişimidir.

#### Yetmiş İki İsteğin On Dördü

İkinci tablo istek kümesini açar. Rol denetimini geçen istek sayısı **72**'dir; bunların
**32**'si geçmesi gereken isteklerdir (%44,4). Aradaki fark, rol denetiminin tek başına
neyi bıraktığını verir: rol denetimini geçen her iki istekten biri, doğru karara göre
reddedilmelidir.

Dağıtık denetimde geçen istek sayısı 46'dır. Bu sayı 32'den 14 fazladır ve o 14 istek,
**başkasının nesnesine ulaşan** isteklerdir — istek kümesinin %19,4'ü. Hepsi geçerli bir
oturumla, doğru rolle ve tek bir yol parametresi değiştirilerek yapılabilir.

Bu 14 isteğin ortak özelliği, sunucuda hiçbir hata üretmemeleridir. Yanıt 200'dür, kayıt
gerçekten vardır, sorgu doğru çalışır. Ölçülebilir tek iz istek kümesinin kendisidir: aynı
asıl, birbiri ardına, kendisine ait olmayan kimliklerle aynı uca gitmiştir. Denetim kaydı
bu deseni görebilir ama karar veremez; kararı verecek olan ölçüttür.

İstek kümesinin böyle üretilebilmesi ölçümün asıl kazancıdır. Küme elle yazılmadı; asıl,
uç ve nesne listelerinden **türetildi**. Aynı türetme bir sınama kümesi olarak da kullanılır:
her uç için "sahibi olmayan bir asılla çağır ve ret bekle" isteği kendiliğinden üretilir ve
uç sayısı arttıkça sınama sayısı da artar. Ölçüt bir veri olduğu için hem denetlenebilir hem
sınanabilir; bir `if` satırının içine gömülü olsaydı ikisi de yapılamazdı.

#### Merkezî Kapı ve Bedeli

Merkezî kapı iki şeyi birleştirir: ölçüt uygulaması tek bir yerdedir ve ölçüt bildirmeyen uç
**öntanımlı olarak reddedilir**. İkinci tablonun son iki satırı bu kurulumun iki sütununu
verir. Yanlış geçen istek **0**'dır; durdurulan meşru istek **10**'dur — istek kümesinin
%13,9'u.

Bu on istek bir bedeldir ve yazılmalıdır. Öntanımlı ret, ölçütü eksik beş ucu tümüyle
kapatır; o uçların meşru kullanımı da durur. Fatura kalemlerini kendi faturası için isteyen
abone de 403 alır. Ama iki hata türü arasındaki fark gözlenebilirlikte yatar: durdurulan
meşru istek **ilk gün** bir hata kaydı üretir ve düzeltilir; yanlış geçen istek hiçbir kayıt
üretmez ve yıllarca fark edilmeyebilir. Öntanımlı ret, sessiz hatayı gürültülü hataya
çevirir.

Üçüncü tablo bunun sürekliliğini ölçer. Ölçüt bildirmeden eklenen on beşinci bir uç —
`GET /fatura/{id}/odeme-plani` — dağıtık denetimde yanlış geçeni 14'ten **17**'ye çıkarır.
Merkezî kapıda aynı uç yanlış geçeni 0'da bırakır ve durdurulan meşru isteği 10'dan 13'e
çıkarır. Kapsama sabittir; değişen şey görünür bedeldir.

Fark kalıbın gücünde değil, **varsayılanın yönündedir**. Dağıtık denetimde ölçütü yazmayı
unutmanın karşılığı açık bir uçtur; merkezî kapıda çalışmayan bir uçtur. İkisi de hatadır,
ama biri üretimde sessizce durur, öteki hemen bildirir. Merkezî kapının ölçülebilir katkısı
kapsama yüzdesi değil, **unutmanın sonucunu değiştirmesidir**.

#### Özet

- Yetki sorusunun iki biçimi vardır: rol eylem türünü yetkilendirir, ölçüt istenen nesnenin
  isteği yapanla ilişkisini yetkilendirir. Yalnız birincisi sorulduğunda rol kümesindeki
  herkes bütün nesnelere ulaşır.
- On dört ucun beşi nesne düzeyinde denetim yapmıyor; dördü alt kaynak ya da alt eylem,
  biri tek rollü bir uç.
- Rol denetimini geçen 72 isteğin 32'si geçmelidir; dağıtık denetimde 46 istek geçiyor ve
  aradaki 14 istek (%19,4) başkasının nesnesine ulaşıyor.
- Bir ucun taşıdığı risk ölçüt eksikliğiyle rol kümesinin genişliğinin çarpımıdır: aynı
  eksiklik bir uçta 6, başka bir uçta 1 istek sızdırıyor.
- Öntanımlı ret üzerine kurulu merkezî kapı yanlış geçeni 0'a indirir ve 10 meşru isteği
  durdurur; on beşinci uç eklendiğinde yanlış geçen 0 kalır, dağıtık denetimde 17'ye çıkar.
- Merkezî kapının ölçülebilir katkısı, ölçütü unutmanın sonucunu sessiz bir açıktan
  gözlenebilir bir hataya çevirmesidir.

#### Sonraki Adım

Bu ders yetki sorusunu tek bir istek üzerinde sordu: bu asıl, bu uçta, bu nesneye
dokunabilir mi? Üç sayı da tek istek varsayımına dayanıyor. Oysa bir sistemin kuralları yalnız
tek isteklerde değil, **istek dizilerinde** de yaşar. Bir indirim bir kez uygulanır, bir iade
bir kez alınır, bir kota bir dönemde bir kez tüketilir. Bu kuralların hiçbiri tek bir uçta
yazılı değildir; adımların sırasında yazılıdır. Sonraki ders akışları adım dizisi olarak
modeller ve sorar: adımların atlanabildiği ya da tekrarlanabildiği kaç yol var, durum
makinesi kaçını kapatıyor, kaçı dışarıda kalıyor.

### İş Mantığı Açıkları

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/is-mantigi-aciklari

İş mantığı açığının akış sırası olarak modellenmesi: beş adımlı bir itiraz ve iade akışında dört adımlık altı yüz yirmi beş yolun beş yüz biri adım başına yetkilendirmede kural dışı sonuç verir, üç yüz altmış dokuzu ödeme yapar ve yüz on üçü kotayı aşar; durum makinesi bu sayıları sıfıra indirip ödeme yapan yolu bire düşürür, geçişi bildirilmemiş altıncı adım eklendiğinde adım başına kurulumda kural dışı yol bin yüz yetmiş ikiye çıkarken durum makinesinde sıfır kalır ve dizi uzunluğu büyüdükçe kural dışı yolların payı yüzde altmıştan yüzde doksana yükselir.

Önceki ders yetki sorusunu tek bir istek üzerinde sordu ve üç sayı verdi: kaç uç nesne
düzeyinde denetim yapıyor, kaç istek yanlış geçiyor, merkezî kapı kaçını kapatıyor. O
ölçümün tamamı tek istek varsayımına dayanıyordu. Bir isteğin tek başına doğru olması ise
bir kuralın korunduğu anlamına gelmez.

Bir sistemin kurallarının bir bölümü tek isteklerde değil, **istek dizilerinde** yaşar. Bir
iade bir kez ödenir, bir indirim bir dönemde bir kez uygulanır, bir onay ödemeden önce
gelir. Bu cümlelerin hiçbiri tek bir ucun içine yazılamaz; hepsi adımların **sırası** ve
**tekrarı** hakkındadır. **İş mantığı açığı** bu kuralların denetlenmediği yerde doğar:
her adım tek tek yetkilidir, dizinin kendisi yetkisizdir.

#### Kural Adımların Sırasında

Ölçüm için bir akış adım kümesine indirgenir. Her adımın iki özelliği yazılır: etkisi
(ödeme, indirim) ve durum makinesinde hangi önceki durumlardan sonra gelebileceği. İkinci
alan boş bırakılırsa adım hiçbir durumda çalışamaz — bu, önceki dersteki öntanımlı retin
akış karşılığıdır.

İki kurulum karşılaştırılır. **Adım başına yetkilendirme** her adımı kendi başına
yetkilendirir ve sırayı hiç sormaz; bu, uç başına yetki denetimi yapan bir servisin
davranışıdır. **Durum makinesi** her adımdan önce mevcut durumu sınar ve izinli olmayan
adımı çalıştırmaz.

Aşağıdaki akış bir **modeldir**: gerçek bir ödeme, gerçek bir tutar ve gerçek bir para
birimi yoktur; tutarlar kurgu birimdir.

```js
// akis/model.mjs — abone portalinin fatura itiraz ve iade akisi (model): gercek bir odeme,
// gercek bir tutar ve gercek bir para birimi yok. Tutarlar kurgu birimdir.

// KH13: her adim iki sey tasir — etkisi (odeme, indirim) ve durum makinesinde hangi onceki
// durumlardan sonra gelebilecegi. `izin` bos ise adim hicbir durumda calisamaz.
export const ETKI = {
  "basvur":         { izin: ["yok"],                     sonra: "basvuruldu" },
  "incele":         { izin: ["basvuruldu"],              sonra: "incelendi" },
  "onayla":         { izin: ["incelendi"],               sonra: "onaylandi" },
  "iade-ode":       { izin: ["onaylandi"],               sonra: "kapandi", odeme: 100 },
  "indirim-uygula": { izin: ["basvuruldu", "incelendi"], sonra: null,      indirim: 1 },
};
export const ADIM = Object.keys(ETKI);
export const KOTA = 1;                       // indirim kotasi: donem basina bir kez

// KH14: iki kurulum ayni adim dizisini kosar. "adim-basina" kurulumunda her adim kendi
// yetkisini tasir ve sira sorulmaz; "durum-makinesi" kurulumunda izinli olmayan adim atlanir.
export function kosu(dizi, kurulum, etki = ETKI) {
  const d = { durum: "yok", odeme: 0, indirim: 0 };
  for (const ad of dizi) {
    const e = etki[ad];
    if (kurulum === "durum-makinesi") {
      if (!e.izin.includes(d.durum)) continue;
      if (e.indirim && d.indirim >= KOTA) continue;
    }
    d.odeme += e.odeme ?? 0;
    d.indirim += e.indirim ?? 0;
    if (e.sonra) d.durum = e.sonra;
  }
  return d;
}

// KH15: yol kumesi, verilen adim kumesinden uretilen sabit uzunluktaki butun dizilerdir.
export function diziler(adimlar, uzunluk) {
  let l = [[]];
  for (let i = 0; i < uzunluk; i++) l = l.flatMap((d) => adimlar.map((a) => [...d, a]));
  return l;
}
```

```js
// akis/olcum.mjs — adimlarin atlanabildigi ya da tekrarlanabildigi kac yol var, durum
// makinesi kacini kapatiyor, kaci disarida kaliyor.
import { ETKI, ADIM, KOTA, kosu, diziler } from "./model.mjs";
const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));
const KOD = { b: "basvur", i: "incele", o: "onayla", d: "iade-ode", n: "indirim-uygula" };

const A = [-10, 10, 12, 12, 14];
console.log("1. dort adimlik ornek diziler (b=basvur i=incele o=onayla d=iade-ode n=indirim)");
yaz(A, "dizi", "AB odeme", "AB indirim", "DM odeme", "DM indirim");
for (const kisa of ["biod", "bdio", "bidd", "bddd", "bnnn"]) {
  const dizi = [...kisa].map((c) => KOD[c]);
  const a = kosu(dizi, "adim-basina"), m = kosu(dizi, "durum-makinesi");
  yaz(A, [...kisa].join(" "), a.odeme, a.indirim, m.odeme, m.indirim);
}

// KH16: bir yol, adim-basina kurulumda durum makinesinden daha comert bir sonuc veriyorsa
// kural disidir. Asagidaki sayim butun yollari gercekten kosturur.
const UZUNLUK = 4;
const olc = (adimlar, etki, uzunluk = UZUNLUK) => {
  const yol = diziler(adimlar, uzunluk);
  const s = { yol: yol.length, odeyen: [0, 0], fazla: [0, 0], kota: [0, 0],
    disi: [0, 0], ayni: 0 };
  for (const d of yol) {
    const k = [kosu(d, "adim-basina", etki), kosu(d, "durum-makinesi", etki)];
    for (let i = 0; i < 2; i++) {
      if (k[i].odeme > 0) s.odeyen[i]++;
      if (k[i].odeme > 100) s.fazla[i]++;
      if (k[i].indirim > KOTA) s.kota[i]++;
      if (k[i].odeme > k[1].odeme || k[i].indirim > k[1].indirim) s.disi[i]++;
    }
    if (k[0].odeme === k[1].odeme && k[0].indirim === k[1].indirim) s.ayni++;
  }
  return s;
};
const t = olc(ADIM, ETKI);
const B = [-34, 16, 16];
console.log(`\n2. uzunluk ${UZUNLUK} dizilerin tamami (${ADIM.length} adim, ${t.yol} yol)`);
yaz(B, "olcum", "adim-basina", "durum-makinesi");
for (const [ad, alan] of [["odeme yapan yol", "odeyen"], ["100 biriminden cok odeyen", "fazla"],
  ["indirim kotasini asan", "kota"], ["kural disi sonuc veren", "disi"]])
  yaz(B, ad, t[alan][0], t[alan][1]);
console.log(`iki kurulumun ayni sonucu verdigi yol: ${t.ayni} / ${t.yol}` +
  ` (%${((t.ayni / t.yol) * 100).toFixed(1)})`);

// 3. bildirilmemis yeni adim: gecisi tanimlanmamis oldugu icin durum makinesi onu calistirmaz
const GENIS = { ...ETKI, "ek-odeme": { izin: [], sonra: null, odeme: 100 } };
const g = olc(Object.keys(GENIS), GENIS);
const C = [-28, 7, 8, 14, 13];
console.log("\n3. gecisi bildirilmemis yeni adim (ek-odeme) eklendiginde");
yaz(C, "kurulum", "adim", "yol", "odeme yapan", "kural disi");
for (const [ad, s, i, n] of [["adim-basina", t, 0, 5], ["durum-makinesi", t, 1, 5],
  ["adim-basina, yeni adim", g, 0, 6], ["durum-makinesi, yeni adim", g, 1, 6]])
  yaz(C, ad, n, s.yol, s.odeyen[i], s.disi[i]);

// 4. dizi uzunlugu buyudukce yol kumesi ve kural disi yol sayisi
const D = [-9, 9, 16, 18, 16];
console.log(`\n4. dizi uzunlugu buyudukce (${ADIM.length} adim)`);
yaz(D, "uzunluk", "yol", "AB kural disi", "AB kural disi %", "DM kural disi");
for (let u = 2; u <= 6; u++) {
  const s = olc(ADIM, ETKI, u);
  yaz(D, u, s.yol, s.disi[0], ((s.disi[0] / s.yol) * 100).toFixed(1), s.disi[1]);
}
```

```
1. dort adimlik ornek diziler (b=basvur i=incele o=onayla d=iade-ode n=indirim)
dizi        AB odeme  AB indirim    DM odeme    DM indirim
b i o d          100           0         100             0
b d i o          100           0           0             0
b i d d          200           0           0             0
b d d d          300           0           0             0
b n n n            0           3           0             1

2. uzunluk 4 dizilerin tamami (5 adim, 625 yol)
olcum                                  adim-basina  durum-makinesi
odeme yapan yol                                369               1
100 biriminden cok odeyen                      113               0
indirim kotasini asan                          113               0
kural disi sonuc veren                         501               0
iki kurulumun ayni sonucu verdigi yol: 124 / 625 (%19.8)

3. gecisi bildirilmemis yeni adim (ek-odeme) eklendiginde
kurulum                        adim     yol   odeme yapan   kural disi
adim-basina                       5     625           369          501
durum-makinesi                    5     625             1            0
adim-basina, yeni adim            6    1296          1040         1172
durum-makinesi, yeni adim         6    1296             1            0

4. dizi uzunlugu buyudukce (5 adim)
uzunluk        yol   AB kural disi   AB kural disi %   DM kural disi
2               25              15              60.0               0
3              125              90              72.0               0
4              625             501              80.2               0
5             3125            2686              86.0               0
6            15625           14075              90.1               0
```

Sayılar **ölçüm** sınıfındadır; girdileri KH13–KH16'dır.

#### Beş Dizi, Üç Kusur Türü

Birinci tablo beş örnek diziyi iki kurulumda koşturuyor. İlk satır beklenen akıştır:
başvuru, inceleme, onay, ödeme. İki kurulum da 100 birim ödüyor.

İkinci satır **adım atlamadır**: ödeme, onaydan önce geliyor. Adım başına yetkilendirmede
100 birim ödeniyor, çünkü ödeme adımının kendisi yetkilidir ve önündeki adımların
tamamlanıp tamamlanmadığı sorulmuyor. Durum makinesinde aynı dizi 0 ödüyor.

Üçüncü ve dördüncü satırlar **adım tekrarıdır**: aynı ödeme adımı iki ve üç kez geçiyor,
sonuç 200 ve 300 birim. Bu iki dizide ödeme adımı her seferinde tek başına yetkilidir;
eksik olan, adımın **bir kez** çalışabileceği bilgisidir.

Beşinci satır **kota aşımıdır**. İndirim adımı üç kez uygulanıyor ve adım başına
yetkilendirmede indirim 3'e çıkıyor. Durum makinesinde 1'de kalıyor, çünkü kota da bir durum
bilgisidir: indirim uygulandıktan sonra ikinci uygulama izinli değildir.

Üç kusur türünün ortak yanı, hiçbirinin yetki denetimini kırmamasıdır. Her adım doğru rolle,
doğru nesne üzerinde, doğru ölçütle çağrıldı. Kırılan şey akışın kendisidir.

Bu, önceki dersteki ölçümün niçin yetmediğini de gösteriyor. Yetki matrisi on dört ucun
beşinde eksik ölçüt buldu; burada bütün ölçütler yerinde olsa bile 501 yol kural dışı sonuç
veriyor. İki ölçüm birbirinin yerine geçmez: biri **kimin hangi nesneye** dokunabildiğini,
öteki **hangi sırayla** dokunabildiğini denetler. Bir uç için "yetki denetimi tamam" demek,
o ucun bir dizinin ortasında çağrılmasına karşı hiçbir şey söylemez.

#### Altı Yüz Yirmi Beş Yolun Beş Yüz Biri

İkinci tablo bütün yol kümesini sayar. Beş adımdan üretilen dört adımlık dizi sayısı
**625**'tir ve her biri iki kurulumda gerçekten koşturulur.

Yol kümesinin sabit uzunlukta üretilmesi bilinçli bir seçimdir. Gerçek bir istemci istediği
adımı istediği sayıda çağırabilir; dört adımlık pencere, bu serbestliğin sayılabilir bir
kesitidir. Kesit büyütüldüğünde sonuç değişmez, yalnız sayılar büyür — dördüncü tablo bunu
gösteriyor. Ölçümün geçerliliği pencerenin genişliğine değil, aynı pencerenin iki kurulumda
da kullanılmasına dayanır.

Adım başına yetkilendirmede **369 yol** ödeme yapıyor; durum makinesinde **1**. Aradaki fark
368 yoldur ve hepsi onaysız ödemedir. **113 yol** 100 birimden çok ödüyor — bunlar ödeme
adımının iki ya da üç kez geçtiği dizilerdir. Aynı sayı indirim kotası için de çıkıyor: 113
yol kotayı aşıyor.

Toplam sayı son satırdadır: **501 yol** durum makinesinden daha cömert bir sonuç veriyor —
yol kümesinin %80,2'si. İki kurulumun aynı sonucu verdiği yol yalnız 124, yani %19,8. Bu
oran, sırayı denetlemeyen bir akışta "doğru davranan" yolların azınlıkta olduğunu söylüyor:
kural dışı sonuç istisna değil, çoğunluktur.

Sayının bu kadar büyük olması adım kümesinin küçüklüğüne karşın şaşırtıcı değildir. Yol
sayısı adım sayısının uzunluk kuvvetidir; beş adım ve dört uzunluk 625 verir, altı adım ve
dört uzunluk 1.296. Akışa her yeni adım eklendiğinde denetlenmesi gereken dizi sayısı
katlanarak artar. Bu yüzden kural dizi başına yazılamaz; **durum başına** yazılmalıdır.

Dördüncü tablo bu büyümeyi ölçüyor. Dizi uzunluğu 2'den 6'ya çıktığında yol sayısı 25'ten
15.625'e, adım başına kurulumda kural dışı yol 15'ten 14.075'e çıkıyor. Asıl bilgi son
sütunda değil, oran sütununda: kural dışı yolların payı %60,0'tan **%90,1'e** yükseliyor.
Dizi uzandıkça doğru davranan yolların payı düşer, çünkü doğru sıra tektir ve yanlış
sıraların sayısı uzunlukla birlikte katlanır. Durum makinesi sütunu beş satırın beşinde de
sıfırdır — geçiş tablosu dizi uzunluğundan bağımsızdır.

Bu, sırayı sınama yoluyla korumanın niçin yetmediğini de söylüyor. Dört adımlık dizilerin
tamamını sınayan bir sınama kümesi 625 durum içerir ve beş adımda 3.125'e çıkar; beş satırlık
bir geçiş tablosu ise değişmez. Kural dışı yol sayısını sıfırda tutan şey sınama sayısı
değil, kararın yerleştiği yerdir.

#### Durum Makinesi Ne Kapatıyor, Neye Mal Oluyor

Durum makinesi 625 yolun 501'ini kapatır ve kural dışı yol sayısını sıfıra indirir. Kapatma
mekanizması bir denetim listesi değildir: her adımın izinli önceki durumları yazılıdır ve
bu listede olmayan her geçiş reddedilir. Beş satırlık bir tablo, 501 kural dışı yolu birden
kapatır.

Üçüncü tablo yeni adım sorusunu yanıtlar. Geçişi bildirilmemiş altıncı bir adım —
`ek-odeme` — eklendiğinde adım başına kurulumda yol sayısı 1.296'ya, ödeme yapan yol
1.040'a, kural dışı yol **1.172**'ye çıkıyor. Durum makinesinde aynı adım hiçbir yol açmıyor:
ödeme yapan yol 1'de, kural dışı yol 0'da kalıyor. `izin` listesi boş olduğu için adım hiçbir
durumdan sonra çalışmıyor.

Bedeli de buradadır ve yazılmalıdır. Bildirilmemiş adım **hiç çalışmaz**; yeni özellik
geçiş tablosuna eklenene kadar ölü kalır. Aynı bedel meşru esneklikte de görülür: indirim
adımı yalnız `basvuruldu` ve `incelendi` durumlarında izinlidir, dolayısıyla onaydan sonra
indirim uygulamak isteyen meşru bir iş kabul edilmez. Bu isteği karşılamak için tabloya bir
satır eklemek gerekir ve o satır bilinçli bir karardır — kod incelemesinde görülebilir,
sınanabilir ve geri alınabilir.

Bedelin ölçüsü de tablodadır. Durum makinesinde ödeme yapan yol sayısı 1'dir; adım başına
kurulumda 369. Bu 368 yolun tamamı kapatılmıştır ve kullanıcının akışı tamamlamak için
izleyebileceği tek bir sıra kalmıştır. Bir akışta meşru esneklik varsa — belgenin inceleme
öncesinde de eklenebilmesi, indirimin iki farklı noktada uygulanabilmesi — bu esneklik
geçiş tablosunda satır olarak görünmek zorundadır. Tabloda olmayan esneklik ürün kararı
değil, kapatılmış bir yoldur.

Bir ayrıntı gerçekleştirimde önemlidir. Yukarıdaki modelde izinli olmayan adım **sessizce
atlanıyor**; gerçek bir serviste atlamak yerine ret döndürmek gerekir. Sessiz atlama,
çağıranın adımın çalıştığını sanmasına yol açar ve bu, kapatılan açığın yerine bir tutarlılık
hatası koyar. Durum makinesinin değeri kararı vermesindedir; kararın bildirilmesi ayrı bir
sorumluluktur.

#### Özet

- İş mantığı açığı tek bir isteğin yetkisinde değil, adım dizisinin sırasında ve tekrarında
  doğar; her adım tek tek yetkiliyken dizinin kendisi yetkisiz olabilir.
- Üç kusur türü ölçüldü: adım atlama (onaysız ödeme), adım tekrarı (200 ve 300 birim ödeme)
  ve kota aşımı (indirimin üç kez uygulanması).
- Beş adımdan üretilen 625 dört adımlık yolun 369'u ödeme yapıyor, 113'ü 100 birimden çok
  ödüyor, 113'ü kotayı aşıyor ve 501'i (%80,2) kural dışı sonuç veriyor.
- Durum makinesi kural dışı yolu sıfıra, ödeme yapan yolu bire indirir; kapatma mekanizması
  beş satırlık bir geçiş tablosudur, dizi başına yazılmış bir denetim değil.
- Geçişi bildirilmemiş altıncı adım eklendiğinde adım başına kurulumda kural dışı yol 501'den
  1.172'ye çıkarken durum makinesinde 0 kalır; dizi uzunluğu 2'den 6'ya çıktığında kural dışı
  yolların payı %60,0'tan %90,1'e yükselirken durum makinesi sütunu sıfırda kalır.
- İzinli olmayan adım sessizce atlanmaz, reddedilir; sessiz atlama açığın yerine bir
  tutarlılık hatası koyar.

#### Sonraki Adım

Bu dersin bütün ölçümü tek bir varsayıma dayanıyor: adımlar **birbiri ardına** çalışıyor.
Durum makinesi, ödeme adımına geldiğinde durumun `onaylandi` olduğunu okuyor, kararı veriyor
ve durumu `kapandi` yapıyor. Okuma ile yazma arasında geçen sürede başka hiçbir şeyin
olmadığı varsayılıyor. İki istek aynı anda geldiğinde bu varsayım düşer: ikisi de durumu
`onaylandi` okuyabilir, ikisi de kararı geçebilir ve ikisi de ödeyebilir. Sonraki ders
eşzamanlı istek modelinde aynı kontrolün kaç kez geçtiğini sayar ve kilit ile tek yazarlı
kuyruğun kaçını kapattığını ölçer.

### Yarış Koşulları

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/yaris-kosullari

Aynı kontrolün eşzamanlı isteklerde kaç kez geçtiğinin ölçülmesi: tek kullanımlık bir kotada on altı eşzamanlı istek denetimsiz kurulumda on altı kez geçer, kilit ve tek yazarlı kuyruk bunu kotaya indirir, yeniden denemesiz atomik koşullu güncelleme kota üçken yalnız bir isteği geçirerek iki meşru isteği reddeder; yedi kontrol noktasının dördü korumasızdır ve sekiz eşzamanlı istekte yirmi yedi fazla geçiş üretir, tek yazarlı kuyruk bunu sıfıra indirir ve sekizinci nokta eklendiğinde dağıtık kurulumda fazla geçiş otuz dörde çıkar.

Önceki dersin bütün ölçümü tek bir varsayıma dayanıyordu: adımlar birbiri ardına çalışıyor.
Durum makinesi ödeme adımına geldiğinde durumu okuyor, kararı veriyor ve durumu
güncelliyordu. Okuma ile yazma arasında geçen sürede başka hiçbir şeyin olmadığı
varsayılıyordu.

İki istek aynı anda geldiğinde bu varsayım düşer. İkisi de durumu `onaylandi` okuyabilir,
ikisi de kararı geçebilir ve ikisi de yazabilir. Kural kodda yazılıdır, denetim çalışır ve
sonuç yine yanlıştır. **Yarış koşulu** budur: kusur denetimin varlığında değil, denetim ile
etkinin arasındaki boşluktadır.

#### Üç Adımın Arasına Girmek

Paylaşılan bir değeri değiştiren her istek üç adımdır: oku, denetle, yaz. Bu üçlünün
arasına başka bir isteğin girmesi, ikinci isteğin birincinin henüz yazmadığı değeri okuması
demektir. İki istek de eski değere bakarak karar verir ve iki karar da olumlu çıkar.

Aşağıdaki ölçüm bir **modeldir**: gerçek bir sunucu, gerçek bir istemci ve gerçek bir ağ
yoktur. Zamanlama gerçek değildir; istekler adım adım serpiştirilerek kendi yazdığımız
düzenleyicide koşar, bu yüzden çıktı her koşumda aynıdır. Bir servise eşzamanlı istek
göndererek kural atlatmayı denemek **yazılı yetki ve tanımlı kapsam olmadan hem yasadışı hem
meslek dışıdır**; ölçülen şey burada kendi modelimizin davranışıdır.

```js
// yaris/model.mjs — eszamanli istek modeli (model): gercek bir sunucu, gercek bir istemci ve
// gercek bir ag yok. Zamanlama gercek degil, adim adim serpistirmeyle uretilir; bu yuzden
// cikti her kosumda aynidir.

// KH17: her istek uc adimdir — paylasilan durumu oku, karar ver, yaz. Yaris kosulu bu ucun
// arasina baska bir istegin girmesiyle dogar.
export function* istek(ad, durum, kurulum) {
  const yerel = durum.kalan;                     // 1. oku
  yield "oku";
  const gecti = yerel > 0;                       // 2. denetle
  yield "denetle";
  if (!gecti) return { ad, sonuc: "reddedildi" };
  if (kurulum === "kuyruk") return { ad, sonuc: "kuyrukta" };
  if (kurulum === "atomik" && durum.kalan !== yerel) return { ad, sonuc: "carpisma" };
  durum.kalan = yerel - 1;                       // 3. yaz
  return { ad, sonuc: "gecti" };
}

// KH18: dort kurulum ayni istek kumesini kosar. "denetimsiz" her istegi adim adim serpistirir;
// "kilit" oku-denetle-yaz ucluslunu serilestirir; "atomik" yazmadan once degeri yeniden sinar;
// "kuyruk" yazmayi tek yazarli bir kuyruga birakir ve kuyruk yeniden denetler.
export function kosu(eszamanli, kurulum, kota = 1) {
  const durum = { kalan: kota }, sonuc = [];
  const uretec = Array.from({ length: eszamanli },
    (_, i) => istek(`i-${i + 1}`, durum, kurulum));
  if (kurulum === "kilit") {
    for (const u of uretec) { let r; while (!(r = u.next()).done); sonuc.push(r.value); }
  } else {
    const acik = uretec.map((u) => ({ u, bitti: false }));
    while (acik.some((a) => !a.bitti))
      for (const a of acik) {
        if (a.bitti) continue;
        const r = a.u.next();
        if (r.done) { a.bitti = true; sonuc.push(r.value); }
      }
  }
  if (kurulum === "kuyruk")
    for (const s of sonuc)
      if (s.sonuc === "kuyrukta")
        s.sonuc = durum.kalan > 0 ? (durum.kalan--, "gecti") : "reddedildi";
  const gecen = sonuc.filter((s) => s.sonuc === "gecti").length;
  return { gecen, fazla: Math.max(0, gecen - kota), kalan: durum.kalan };
}

// KH19: kontrol noktasi, paylasilan bir degeri okuyup denetleyip yazan her yerdir.
export const NOKTA = [
  { ad: "indirim-kodu-kullan", kota: 1, kurulum: "denetimsiz" },
  { ad: "iade-ode",            kota: 1, kurulum: "denetimsiz" },
  { ad: "kota-dus",            kota: 3, kurulum: "kilit" },
  { ad: "abone-devri-onay",    kota: 1, kurulum: "kilit" },
  { ad: "is-emri-atama",       kota: 1, kurulum: "denetimsiz" },
  { ad: "fatura-kilitle",      kota: 1, kurulum: "atomik" },
  { ad: "sayac-okuma-al",      kota: 2, kurulum: "denetimsiz" },
];
```

```js
// yaris/olcum.mjs — ayni kontrol kac kez geciyor, kilit ve tek yazarli kuyruk kacini kapatiyor.
import { kosu, NOKTA } from "./model.mjs";
const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));
const KURULUM = ["denetimsiz", "kilit", "atomik", "kuyruk"];

const A = [-11, 13, 8, 9, 9];
console.log("1. tek kullanimlik indirim kodu (kota 1): kac istek kontrolden gecti");
yaz(A, "eszamanli", ...KURULUM);
for (const n of [1, 2, 4, 8, 16]) yaz(A, n, ...KURULUM.map((k) => kosu(n, k).gecen));

const B = [-11, 13, 8, 9, 9];
console.log("\n2. ayni olcum kota 3 ile (gecmesi gereken en cok 3 istek)");
yaz(B, "eszamanli", ...KURULUM);
for (const n of [1, 2, 4, 8, 16]) yaz(B, n, ...KURULUM.map((k) => kosu(n, k, 3).gecen));

// KH20: fazla gecen istek, kotanin ustunde gecen istek sayisidir; olculen sey budur.
const ESZAMANLI = 8;
const C = [-22, 7, 13, 9, 9, 11];
console.log(`\n3. ${NOKTA.length} kontrol noktasi, her birinde ${ESZAMANLI} eszamanli istek`);
yaz(C, "kontrol noktasi", "kota", "kurulum", "gecen", "fazla", "kuyrukta");
let fazlaToplam = 0, kuyrukToplam = 0;
for (const n of NOKTA) {
  const s = kosu(ESZAMANLI, n.kurulum, n.kota), k = kosu(ESZAMANLI, "kuyruk", n.kota);
  fazlaToplam += s.fazla; kuyrukToplam += k.fazla;
  yaz(C, n.ad, n.kota, n.kurulum, s.gecen, s.fazla, k.fazla);
}
yaz(C, "toplam", "", "", "", fazlaToplam, kuyrukToplam);

// 4. kapsama: yeni kontrol noktasi eklendiginde
const YENI = { ad: "odeme-plani-olustur", kota: 1, kurulum: "denetimsiz" };
const genis = [...NOKTA, YENI];
const fazla = (kume, merkezi) => kume.reduce((t, n) =>
  t + kosu(ESZAMANLI, merkezi ? "kuyruk" : n.kurulum, n.kota).fazla, 0);
const D = [-36, 8, 12, 14, 13];
console.log("\n4. kapsama: korumasiz kontrol noktasi ve fazla gecen istek");
yaz(D, "kume", "nokta", "korumasiz", "fazla gecen", "disarida %");
for (const [ad, k, merkezi] of [
  ["bugunku dagitik durum", NOKTA, false],
  ["tek yazarli kuyruk butun noktalarda", NOKTA, true],
  ["yeni nokta eklendi, dagitik", genis, false],
  ["yeni nokta eklendi, merkezi kalip", genis, true],
]) {
  const acik = merkezi ? 0 : k.filter((n) => n.kurulum === "denetimsiz").length;
  yaz(D, ad, k.length, acik, fazla(k, merkezi), ((acik / k.length) * 100).toFixed(1));
}
```

```
1. tek kullanimlik indirim kodu (kota 1): kac istek kontrolden gecti
eszamanli     denetimsiz   kilit   atomik   kuyruk
1                      1       1        1        1
2                      2       1        1        1
4                      4       1        1        1
8                      8       1        1        1
16                    16       1        1        1

2. ayni olcum kota 3 ile (gecmesi gereken en cok 3 istek)
eszamanli     denetimsiz   kilit   atomik   kuyruk
1                      1       1        1        1
2                      2       2        1        2
4                      4       3        1        3
8                      8       3        1        3
16                    16       3        1        3

3. 7 kontrol noktasi, her birinde 8 eszamanli istek
kontrol noktasi          kota      kurulum    gecen    fazla   kuyrukta
indirim-kodu-kullan         1   denetimsiz        8        7          0
iade-ode                    1   denetimsiz        8        7          0
kota-dus                    3        kilit        3        0          0
abone-devri-onay            1        kilit        1        0          0
is-emri-atama               1   denetimsiz        8        7          0
fatura-kilitle              1       atomik        1        0          0
sayac-okuma-al              2   denetimsiz        8        6          0
toplam                                                    27          0

4. kapsama: korumasiz kontrol noktasi ve fazla gecen istek
kume                                   nokta   korumasiz   fazla gecen   disarida %
bugunku dagitik durum                      7           4            27         57.1
tek yazarli kuyruk butun noktalarda        7           0             0          0.0
yeni nokta eklendi, dagitik                8           5            34         62.5
yeni nokta eklendi, merkezi kalip          8           0             0          0.0
```

Sayılar **ölçüm** sınıfındadır; girdileri KH17–KH20'dir.

#### Kontrol Eşzamanlılık Kadar Geçiyor

Birinci tablo tek kullanımlık bir indirim kodunu ölçüyor: kota 1, yani en çok bir istek
geçmelidir. Denetimsiz kurulumda geçen istek sayısı eşzamanlı istek sayısına eşit — 2 istek
2 kez, 16 istek 16 kez geçiyor. Kontrol her istekte gerçekten çalıştı ve her istekte olumlu
sonuç verdi, çünkü hepsi yazmadan önce okudu.

Bu, denetimin **etkisiz** olduğu anlamına gelmez; ölçeklenmediği anlamına gelir. Tek istekle
koşulan bir sınama bu kusuru hiçbir zaman göremez: birinci satırda dört kurulum da 1 veriyor.
Kusur ancak eşzamanlılık bir değişken olduğunda görünür.

Kilit ve tek yazarlı kuyruk her eşzamanlılık düzeyinde 1 veriyor. İkisi aynı sonuca iki
farklı yoldan varıyor: kilit oku–denetle–yaz üçlüsünü serileştirir, kuyruk okumayı serbest
bırakıp yalnız yazmayı tek yazara toplar ve yazma anında yeniden denetler. Kuyruğun ayırt
edici özelliği, yavaş okumaların kilit tutmamasıdır.

Kilidin de bir bedeli vardır ve o bedel bu ölçümde görünmez. Kilit, kritik bölümü
serileştirdiği için eşzamanlılık arttıkça bekleyen istek sayısını artırır; kritik bölümün
içinde bir okuma yavaşsa bütün istekler o okumayı sırayla bekler. Tablodaki `gecen` sütunu
bu bekleme süresini ölçmez, yalnız doğruluğu ölçer. İki sütunun ayrı tutulması gerekir:
doğruluk sütununda kilit ile kuyruk aynı sonucu verir, gecikme sütununda vermez.

#### Savunmanın Bedeli: Çarpışma

İkinci tablo aynı ölçümü kota 3 ile yineliyor ve bir savunmanın bedelini açığa çıkarıyor.
Denetimsiz kurulum yine eşzamanlılık kadar geçiriyor; kilit ve kuyruk 3'te duruyor. Atomik
koşullu güncelleme ise **1** veriyor.

Sebep modeldeki gerçekleştirimdir: yazma anında değer değişmişse istek çarpışma sayılıp
reddediliyor ve **yeniden denenmiyor**. Kota 3 iken üç isteğin geçmesi gerekirken yalnız
biri geçiyor; iki meşru istek kaybediliyor. Koşullu güncelleme fazla geçişi engelliyor ama
yeniden deneme olmadan kullanıldığında meşru işin bir bölümünü de durduruyor.

Bu, bu müfredatın kuralının bir örneğidir: bedeli yazılmayan savunma ölçülmemiş sayılır.
Koşullu güncellemenin doğru biçimi çarpışan isteği belirli bir sınıra kadar yeniden
denemektir; sınır da bir karardır ve yükün altında kaç isteğin düşeceğini belirler.

Birinci tablonun kota 1 sonucuyla ikinci tablonun kota 3 sonucu arasındaki fark da bir
uyarıdır. Kota 1
iken atomik kurulum doğru sonucu verir ve kusur görünmez; kusur ancak kota 1'in üstüne
çıktığında ortaya çıkar. Bir savunmanın tek bir yapılandırmada doğru davranması, bütün
yapılandırmalarda doğru davrandığı anlamına gelmiyor. Ölçüm kümesinin en az iki kota
değeri içermesi gerekir.

#### Yedi Noktada Yirmi Yedi Fazla Geçiş

Üçüncü tablo ölçümü tek bir koddan kod tabanına taşır. Yedi kontrol noktası var; her biri
paylaşılan bir değeri okuyup denetleyip yazıyor. Dördü korumasız, ikisi kilitli, biri atomik.

Her noktada sekiz eşzamanlı istek koşuyor ve **fazla geçen** istek sayısı yazılıyor: kotanın
üstünde geçen istek. Korumasız dört nokta sırasıyla 7, 7, 7 ve 6 fazla geçiş üretiyor;
toplam **27**. Korumalı üç nokta 0 üretiyor.

Sayılar arasındaki fark kotadan gelir. `sayac-okuma-al` noktasının kotası 2 olduğu için sekiz
istekten altısı fazladır; kotası 1 olan noktalarda yedisi. Fazla geçiş sayısı bu yüzden nokta
sayısıyla değil, **kota ile eşzamanlılığın farkıyla** ölçülür. "Dört noktamız korumasız"
cümlesi 27 sayısını vermez.

Son sütun aynı noktaların tek yazarlı kuyrukla koşulduğunda ne verdiğini gösteriyor: yedi
noktanın yedisinde 0. Kapatma tek bir yerdedir; her nokta için ayrı bir kilit tasarlanmadı.

Korumalı üç noktanın hangileri olduğu da anlamlıdır. `kota-dus` ve `abone-devri-onay`
kilitlidir, çünkü ikisi de para ya da abonelik durumu değiştiren, üzerinde uzun düşünülmüş
noktalardır. Korumasız dördü ise bir sayaç düşüren, bir kayıt işaretleyen ya da bir atama
yapan noktalardır; hiçbiri "kritik" görünmez. Yarış koşullarının bulunduğu yer, kritik
sayılan nokta değil, **paylaşılan bir değeri okuyup yazan** noktadır. Envanterin ölçütü de
bu olmalıdır: kritiklik değil, paylaşılan yazma.

#### Kapsama ve Sekizinci Nokta

Dördüncü tablo kapsamayı yazar. Bugünkü dağıtık durumda yedi noktanın dördü korumasız —
dışarıda kalan oran **%57,1** — ve toplam fazla geçiş 27. Bütün yazmalar tek yazarlı
kuyruktan geçtiğinde korumasız nokta 0, fazla geçiş 0.

Sekizinci bir nokta — `odeme-plani-olustur` — dağıtık kurulumda korumasız eklenirse fazla
geçiş 27'den **34**'e, dışarıda kalan oran %62,5'e çıkar. Merkezî kalıpta aynı nokta hiçbir
fazla geçiş üretmez; yazma yolu zaten kuyruktan geçer.

Fark burada da kalıbın gücünde değil, yazmanın **tek bir yoldan** yapılmasındadır. Dağıtık
kurulumda her yeni kontrol noktası için "bu değeri kim daha yazıyor" sorusunun yeniden
sorulması gerekir; kuyrukta soru bir kez sorulmuştur. Eşzamanlılık, kod incelemesinde en zor
görülen kusur sınıfıdır: kod tek başına okunduğunda doğrudur ve yalnız iki kopyası aynı anda
koştuğunda yanlıştır.

Bu yüzden ölçümün kendisi bir sınama biçimidir. Yukarıdaki düzenleyici gerçek zamanlama
kullanmadığı için sonucu her koşumda aynıdır; aynı serpiştirme her kontrol noktasına
uygulanabilir ve fazla geçiş sayısı bir sayı olarak kaydedilebilir. Gerçek zamanlamaya
bırakılan bir sınama ise bazen geçer bazen kalır ve geçtiğinde hiçbir şey kanıtlamaz. Bir
yarış koşulunun varlığı, kendi yazdığımız serpiştirmede gösterilebiliyorsa gösterilmiş
sayılır; gösterilemiyorsa yokluğu değil, o serpiştirmede görünmediği bilinir.

#### Özet

- Yarış koşulu denetimin yokluğunda değil, denetim ile etkinin arasındaki boşlukta doğar;
  oku–denetle–yaz üçlüsünün arasına giren ikinci istek eski değeri okur.
- Tek kullanımlık bir kotada denetimsiz kurulum eşzamanlılık kadar geçiş üretir: 16 eşzamanlı
  istek 16 kez geçer. Tek istekle koşulan sınama bu kusuru hiçbir zaman göremez.
- Kilit ve tek yazarlı kuyruk her düzeyde kotayı korur; kuyruğun ayırt edici yanı okumaların
  kilit tutmamasıdır.
- Yeniden denemesiz atomik koşullu güncelleme kota 3 iken yalnız 1 isteği geçirir; fazla
  geçişi kapatırken iki meşru isteği de reddeder. Bedeli yazılmayan savunma ölçülmemiştir.
- Yedi kontrol noktasının dördü korumasız; sekiz eşzamanlı istekte toplam 27 fazla geçiş
  üretiyorlar. Fazla geçiş sayısı nokta sayısıyla değil, kota ile eşzamanlılığın farkıyla
  ölçülür.
- Tek yazarlı kuyruk fazla geçişi 0'a indirir; sekizinci korumasız nokta eklendiğinde dağıtık
  kurulumda fazla geçiş 34'e çıkarken merkezî kalıpta 0 kalır.

#### Sonraki Adım

Bu konu beş derste tek bir soruyu ölçtü: kimin ne yapabildiği kodun kaç noktasında
denetleniyor, kaç nokta dışarıda kalıyor. Kimlik doğrulama noktalarında sızdırılan bilgi
sayıldı, oturum akışlarında yenilenmeyen kimlik ve yaşamaya devam eden oturum sayıldı, yetki
matrisinde nesne düzeyinde denetimi olmayan uç ve yanlış geçen istek sayıldı, akış sırasında
kural dışı yol sayıldı ve bu derste eşzamanlı isteklerin ürettiği fazla geçiş sayıldı. Beş
ölçümün beşinde de aynı biçim çıktı: dağıtık savunma her kümede dışarıda nokta bıraktı,
merkezî kalıp aynı kümede sıfır verdi ve her kümeye yeni bir kalem eklendiğinde dağıtık
tarafın dışarıda bıraktığı arttı, kalıbınki değişmedi.

Bütün bu denetimlerin dayandığı bir şey ise hiç ele alınmadı. Kimlik doğrulama bir değeri
karşılaştırdı, oturum kimliği bir yerden üretildi, yetki ölçütü bir imzaya ya da bir kayda
güvendi, kuyruk bir kimlikle çalıştı. Bu değerlerin nasıl üretildiği, ne kadar tahmin
edilebilir oldukları ve kod tabanının neresinde durdukları sorulmadı — **anahtarlar ve
sırlar** hâlâ kodun içinde bir yerde duruyor. Sonraki konu, **Kriptografi ve Sırlar**, bu
soruyu aynı kapsama ölçüsüyle ele alır: kaç sır var, kaçı kod tabanının dışında, kaçı depo
geçmişinde kalmış.

## Kod İçinde Sırlar

Kodun dayandığı anahtar, üreteç ve bağımlılık kalemlerinin kapsama ölçüsü: kriptografi çağrı noktalarından kaçının merkezî katmandan geçtiği, aynı başlangıç değerini yeniden kullanmanın ve bütünlük etiketi olmamanın somut sonucu, zayıf üretecin tohum bilindiğinde ne kadarını önceden verdiği, kurgu depo geçmişinde kaç işlemde sır kaldığı ve tarihçe temizlemenin neyi kurtarmadığı, hata mesajı ile günlük satırlarındaki sızıntı kalemlerinin merkezî maskeleme katmanıyla kapanan ve dışarıda kalan bölümü, bağımlılık çizgesinde geçişli derinlik ile tek bileşenin etkilediği paket sayısı.

### Kriptografi Kötüye Kullanımı

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/kriptografi-kotuye-kullanimi

Kriptografi çağrı noktalarının kapsaması ve yanlış kullanımın somut sonucu: on dört çağrı noktasının yedisinin merkezî katmanın dışında kalması, aynı anahtarla aynı başlangıç değerini yeniden kullanmanın kırk konumu okunur hâle getirmesi, bütünlük etiketi olmayan şifrelemede biçim denetiminin yirmi dört değişiklikten dördünü kaçırması ve ilkel çağrı tek dosyaya kapatıldığında yeni noktanın kendiliğinden kapsanması.

Önceki ders kimin ne yapabildiğini kodda denetledi: yetki matrisindeki uçlar sayıldı, eşzamanlı
isteğin aynı kontrolden iki kez geçtiği durum ölçüldü. Bütün o denetimler bir şeye dayanıyordu.
Oturumu bir imza taşıyordu, saha uygulamasının yükleme belirtecini bir anahtar üretmişti, yerel
depo bir şifreleme çağrısıyla kilitlenmişti. Bu konu o kalemleri ele alır ve ilk soru onların
nerede saklandığı değil, **nasıl çağrıldığıdır**.

Kriptografi Temelleri kursu algoritmaların özelliklerini ölçmüştü. Burada o özellikler girdidir;
ölçülen şey **çağrı noktasıdır**. Bir kod tabanında kaç nokta kriptografi çağırıyor, kaçı merkezî
bir katmandan geçiyor, dışarıda kalan noktaların yanlış ayarı somut olarak neye mal oluyor.

#### Kriptografi Çağrı Noktaları

**KI1 — kurgu bölgesel ölçüm ağının kod tabanında on dört kriptografi çağrı noktası vardır.** Bir
bölümü merkezî bir kripto katmanının dört işlevini çağırır; kalanı ilkel çağrıyı doğrudan yapar ve
kip, başlangıç değeri, tuz gibi ayarları kendi seçer. Aşağıdaki envanter bir modeldir; kaynak
adları kurgudur.

```js
// kripto/kapsama.mjs — kurgu olcum agi kod tabanindaki kriptografi cagri noktalari
// envanteri (model). kaynak: cagriyi yapan bilesen; katman: merkezi kripto katmanindan
// gecip gecmedigi; kusur: dogrudan cagrida secilen yanlis ayar.
const noktalar = [
  ['portal/oturum', 'sifreleme', false, 'etiketsiz kip'],
  ['portal/kayit', 'parola karmasi', true, null],
  ['portal/fatura-baglantisi', 'imza', true, null],
  ['alim/paket-acma', 'sifreleme', true, null],
  ['alim/yedek-yol', 'sifreleme', false, 'sabit baslangic degeri'],
  ['alim/es-zamanlama', 'karma', false, 'kendi karma zinciri'],
  ['saha/yukleme-belirteci', 'imza', false, null],
  ['saha/yerel-onbellek', 'sifreleme', false, null],
  ['rapor/aktarim', 'sifreleme', true, null],
  ['rapor/dosya-adi', 'karma', true, null],
  ['rapor/arsiv', 'sifreleme', false, 'etiketsiz kip'],
  ['is-emri/imza', 'imza', true, null],
  ['is-emri/eski-uc', 'parola karmasi', false, 'tuzsuz karma'],
  ['bildirim/geri-cagirma', 'imza', true, null],
];

const say = (kume, kural) => kume.filter(kural).length;
const kusurlar = (kume) => {
  const g = {};
  for (const [, , , k] of kume.filter(([, , katman]) => !katman)) if (k) g[k] = (g[k] ?? 0) + 1;
  return Object.entries(g).sort((a, b) => b[1] - a[1]);
};
const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));

const D = [-26, 10, 10, 12, 12];
yaz(D, 'islem turu', 'nokta', 'katmanda', 'disarida', 'kusurlu');
for (const tur of ['sifreleme', 'imza', 'karma', 'parola karmasi']) {
  const t = noktalar.filter(([, i]) => i === tur);
  yaz(D, tur, t.length, say(t, ([, , k]) => k), say(t, ([, , k]) => !k), say(t, ([, , , k]) => k));
}
yaz(D, 'toplam', noktalar.length, say(noktalar, ([, , k]) => k),
  say(noktalar, ([, , k]) => !k), say(noktalar, ([, , , k]) => k));

console.log('\ndisarida kalan noktalarin kusur dagilimi:');
for (const [ad, n] of kusurlar(noktalar)) console.log(`  ${ad.padEnd(24)}${n}`);

// Yeni nokta eklendiginde ne oluyor: uc yeni cagri noktasi, iki ayri duzen.
const yeni = [
  ['saha/cevrimdisi-kuyruk', 'sifreleme'],
  ['portal/disa-aktarma', 'imza'],
  ['alim/olcum-arsivi', 'sifreleme'],
];
console.log('\nuc yeni cagri noktasi ekleniyor:');
const E = [-40, 8, 12, 18];
yaz(E, 'duzen', 'nokta', 'katmanda', 'yeni acik adayi');
// 1) Ilkel cagri serbest: yeni nokta katmani kullanmak zorunda degil.
yaz(E, 'katman var, dogrudan cagri serbest', noktalar.length + yeni.length,
  say(noktalar, ([, , k]) => k), yeni.length);
// 2) Ilkel cagri tek dosyada toplanmis: katmanin disina cikan nokta derlenmez.
yaz(E, 'ilkel cagri tek dosyaya kapatilmis', noktalar.length + yeni.length,
  say(noktalar, ([, , k]) => k) + yeni.length, 0);
console.log(`\nikinci duzende once kapatilmasi gereken nokta: ${say(noktalar, ([, , k]) => !k)}`);
```

```
islem turu                     nokta  katmanda    disarida     kusurlu
sifreleme                          6         2           4           3
imza                               4         3           1           0
karma                              2         1           1           1
parola karmasi                     2         1           1           1
toplam                            14         7           7           5

disarida kalan noktalarin kusur dagilimi:
  etiketsiz kip           2
  sabit baslangic degeri  1
  kendi karma zinciri     1
  tuzsuz karma            1

uc yeni cagri noktasi ekleniyor:
duzen                                      nokta    katmanda   yeni acik adayi
katman var, dogrudan cagri serbest            17           7                 3
ilkel cagri tek dosyaya kapatilmis            17          10                 0

ikinci duzende once kapatilmasi gereken nokta: 7
```

Toplam satırı bu dersin üç sütununu veriyor: on dört nokta var, yedisini merkezî katman örtüyor,
yedisi dışarıda kalıyor. Dördüncü sütun ise dışarıda kalmanın hemen açık anlamına gelmediğini
söylüyor — yedi noktanın beşi kusurlu, ikisi doğru ayarla çağırıyor. O iki nokta bugün doğrudur
çünkü onları yazan kişi ayarı bilerek seçmiştir; yarın da doğru kalacağını söyleyen hiçbir yapı
yoktur. Kapsamanın ölçüsü **şu anda kusurlu olan nokta sayısı değil, kusurlu olabilecek nokta
sayısıdır** ve o sayı yedidir.

Kusur dağılımı dört ayrı sınıf gösteriyor ve ikisi aynı kökten geliyor: bütünlük etiketi olmayan
bir kip seçmek ve başlangıç değerini sabit bırakmak. Üçüncüsü kendi karma zincirini yazmaktır,
dördüncüsü parolayı tuzsuz karmalamaktır. Aşağıdaki iki ölçüm önce üçüncü sınıfın, sonra ilk
ikisinin somut karşılığını sayar.

#### Kendi Yazılan Karışımın Ölçüsü

Alım servisi yinelenen sayaç okumalarını ayırt etmek için elle yazılmış bir karışım işlevi
kullanıyor: her karakterin kodu toplanıyor ve otuz iki bitlik bir değer üretiliyor. İşlev
çalışıyor, sınamaları geçiyor ve hiçbir hata vermiyor.

```js
// kripto/zincir.mjs — envanterdeki "kendi karma zinciri" kusurunun olculmesi: alim servisi
// yinelenen okumayi ayirt etmek icin elle yazilmis bir karisim islevi kullaniyor (model).
import { createHash } from 'node:crypto';

const KAYIT = 20000;
const anahtarlar = Array.from({ length: KAYIT }, (_, i) =>
  `SAY-${4000 + (i % 2500)}|2026-03-${String(1 + (i % 28)).padStart(2, '0')}T06:00Z|${41000 + i}`);

// Elle yazilmis karisim: karakter kodlarinin toplami, 32 bitlik cikti.
const kendiKarisim = (s) => {
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (h + s.charCodeAt(i)) | 0;
  return h >>> 0;
};
const hazirKarma = (s) => createHash('sha256').update(s).digest().readUInt32BE(0);

const olc = (f) => {
  const gorulen = new Set();
  let dusen = 0;
  for (const a of anahtarlar) {
    const k = f(a);
    if (gorulen.has(k)) dusen += 1; else gorulen.add(k);
  }
  return [gorulen.size, dusen];
};
const beklenen = KAYIT - 2 ** 32 * (1 - (1 - 1 / 2 ** 32) ** KAYIT);
console.log(`${KAYIT} kurgu okuma anahtari, hepsi ayri, cikti genisligi 32 bit:`);
console.log(`  32 bitte kuramsal beklenen sessiz dusme : ${beklenen.toFixed(2)}`);
for (const [ad, f] of [['hazir karma', hazirKarma], ['elle yazilmis karisim', kendiKarisim]]) {
  const [ayri, dusen] = olc(f);
  console.log(`  ${ad.padEnd(22)} ayri deger ${String(ayri).padStart(5)}, sessiz dusen ${dusen}`);
}
```

```
20000 kurgu okuma anahtari, hepsi ayri, cikti genisligi 32 bit:
  32 bitte kuramsal beklenen sessiz dusme : 0.05
  hazir karma            ayri deger 20000, sessiz dusen 0
  elle yazilmis karisim  ayri deger    67, sessiz dusen 19933
```

Otuz iki bitlik çıktı genişliğinde yirmi bin ayrı anahtarın çakışması kuramsal olarak 0,05
beklenir; hazır karmanın ilk otuz iki biti bu beklentiyi tutturuyor ve yirmi bin ayrı değer
üretiyor. Elle yazılmış karışım aynı kümede **altmış yedi** ayrı değer üretiyor ve on dokuz bin
dokuz yüz otuz üç okuma "zaten görülmüş" sayılarak sessizce düşüyor. Sebep açıktır: toplama işlemi
karakterlerin sırasını da, kaydın uzunluğunu da yok sayar, dolayısıyla çıktı uzayının otuz iki
bitlik genişliği hiçbir zaman kullanılmaz. Kendi algoritmasını yazmanın somut sonucu budur —
yazılan şey çalışır, kimse hata görmez ve düşünülmemiş olan özellik yoktur.

#### Aynı Anahtar, Aynı Başlangıç Değeri

**KI2 — akış biçiminde çalışan bir şifrelemede aynı anahtar ve aynı başlangıç değeri iki kez
kullanılırsa, iki şifreli metnin bayt farkı iki düz metnin bayt farkına eşit olur.** Bu, bir
algoritma kusuru değildir; yapının kullanım koşulunun çiğnenmesidir. Ölçüm kendi yazdığımız
model üzerinde yapılır: aşağıdaki akış üreteci gerçek bir şifre değildir ve üretim kodunda
kendi şifresini yazan bir yaklaşımı temsil eder.

```js
// kripto/model.mjs — kendi yazdigimiz basit akis modeli ile hazir kimlik dogrulamali
// sifrelemenin ayni olcekte karsilastirilmasi. Model gercek bir sifre degildir; yalniz
// baslangic degeri yeniden kullanildiginda ve butunluk etiketi olmadiginda sizan bilgiyi
// saymak icin yazilmistir. Kurgu bolgesel olcum agindan iki sayac okumasi kullanilir.
import { createHash, createCipheriv, createDecipheriv } from 'node:crypto';

const ANAHTAR = createHash('sha256').update('kurgu-saha-anahtari').digest();
const OKUMA_A = Buffer.from('SAY-4417|2026-03-01T06:00Z|41982|sube-01');
const OKUMA_B = Buffer.from('SAY-4418|2026-03-01T06:00Z|41982|sube-02');
const BICIM = /^SAY-\d{4}\|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\|\d+\|sube-\d{2}$/;

// Kendi akis uretecimiz: anahtar ve baslangic degerinden bayt dizisi turetir.
function akis(bd, uzunluk) {
  const parcalar = [];
  for (let blok = 0; parcalar.length * 32 < uzunluk; blok++) {
    parcalar.push(createHash('sha256').update(ANAHTAR).update(bd).update(String(blok)).digest());
  }
  return Buffer.concat(parcalar).subarray(0, uzunluk);
}
const modelSifrele = (duz, bd) => Buffer.from(duz.map((b, i) => b ^ akis(bd, duz.length)[i]));
const modelCoz = (sifreli, bd) => Buffer.from(sifreli.map((b, i) => b ^ akis(bd, sifreli.length)[i]));

// Hazir yapi: kimlik dogrulamali sifreleme (AES-256-GCM); etiket paketle tasinir.
const aeSifrele = (duz, bd) => {
  const s = createCipheriv('aes-256-gcm', ANAHTAR, bd);
  return { govde: Buffer.concat([s.update(duz), s.final()]), etiket: s.getAuthTag() };
};
const aeCoz = ({ govde, etiket }, bd) => {
  const c = createDecipheriv('aes-256-gcm', ANAHTAR, bd);
  c.setAuthTag(etiket);
  return Buffer.concat([c.update(govde), c.final()]);
};

const BD1 = Buffer.alloc(12, 7), BD2 = Buffer.alloc(12, 9);
const esitSayisi = (x, y) => [...x].filter((b, i) => b === y[i]).length;
const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));

// 1) Ayni anahtarla ayni baslangic degeri: iki sifreli metin karsilastiriliyor.
console.log(`KI2 — ayni anahtar, iki kayit, uzunluk ${OKUMA_A.length} bayt,`
  + ` duz metinde esit konum ${esitSayisi(OKUMA_A, OKUMA_B)}:`);
const D = [-32, 14, 14, 14];
yaz(D, 'duzen', 'esit sifreli', 'fark okunur', 'sizan konum');
for (const [ad, c1, c2] of [
  ['model, ayni baslangic degeri', modelSifrele(OKUMA_A, BD1), modelSifrele(OKUMA_B, BD1)],
  ['model, ayri baslangic degeri', modelSifrele(OKUMA_A, BD1), modelSifrele(OKUMA_B, BD2)],
  ['etiketli, ayri baslangic degeri', aeSifrele(OKUMA_A, BD1).govde, aeSifrele(OKUMA_B, BD2).govde],
]) {
  const okunur = [...c1].every((b, i) => (b ^ c2[i]) === (OKUMA_A[i] ^ OKUMA_B[i]));
  yaz(D, ad, esitSayisi(c1, c2), okunur ? 'evet' : 'hayir', okunur ? OKUMA_A.length : 0);
}

// 2) Butunluk etiketi olmayan sifreleme: degistirilen paketi kim yakaliyor.
const DENEME = 24;
console.log(`\nKI3 — ${DENEME} paketin her birinde tek bit cevriliyor:`);
const E = [-32, 14, 14, 14];
yaz(E, 'duzen', 'bicim gecen', 'yakalanan', 'degisen bayt');
let gecen = 0, degisen = 0, aGecen = 0;
for (let k = 0; k < DENEME; k++) {
  const bd = Buffer.alloc(12, k + 16);
  const mc = Buffer.from(modelSifrele(OKUMA_A, bd));
  mc[k] ^= 1 << (k % 8);
  const cozulen = modelCoz(mc, bd);
  degisen += [...cozulen].filter((b, i) => b !== OKUMA_A[i]).length;
  if (BICIM.test(cozulen.toString('latin1'))) gecen += 1;

  const paket = aeSifrele(OKUMA_A, bd);
  paket.govde[k] ^= 1 << (k % 8);
  try { aeCoz(paket, bd); aGecen += 1; } catch { /* etiket reddetti */ }
}
yaz(E, 'model + bicim denetimi', gecen, DENEME - gecen, degisen);
yaz(E, 'kimlik dogrulamali sifreleme', aGecen, DENEME - aGecen, 0);
```

```
KI2 — ayni anahtar, iki kayit, uzunluk 40 bayt, duz metinde esit konum 38:
duzen                             esit sifreli   fark okunur   sizan konum
model, ayni baslangic degeri                38          evet            40
model, ayri baslangic degeri                 0         hayir             0
etiketli, ayri baslangic degeri              0         hayir             0

KI3 — 24 paketin her birinde tek bit cevriliyor:
duzen                              bicim gecen     yakalanan  degisen bayt
model + bicim denetimi                       4            20            24
kimlik dogrulamali sifreleme                 0            24             0
```

Birinci tablonun ilk satırı sızıntıyı sayıyla veriyor. İki sayaç okuması kırk baytlık kayıtlardır
ve düz metinde otuz sekiz konumda birbirine eşittir — ikisi de aynı tarihi, aynı okuma değerini ve
aynı biçimi taşır, yalnızca sayaç numarası ile şube kodunda ayrılır. Aynı başlangıç değeriyle
şifrelendiklerinde şifreli metinler de **tam olarak otuz sekiz konumda** eşit çıkıyor. Şifreli
metne bakan biri, iki kaydın nerede aynı nerede farklı olduğunu anahtarı hiç bilmeden okuyor.

"Fark okunur" sütunu daha keskin bir şey söylüyor: kırk konumun tamamında, iki şifreli metnin bayt
farkı iki düz metnin bayt farkına eşit. Şifreleme kaydın **değerini** değil yalnızca ham hâlini
gizler; iki kayıt arasındaki bütün ilişki şifreli tarafa aynen geçer. Bir okuma kümesinde bunun
anlamı, hangi kayıtların özdeş olduğunun ve bir şubenin okumalarının ne zaman durduğunun
sayılabilmesidir. İkinci ve üçüncü satır aynı ölçüde sıfır veriyor: başlangıç değeri her şifreleme
için ayrı olduğunda eşit konum sıfır, okunur fark yok. Kusur şifrelemede değil, tek bir
parametrenin sabit bırakılmasındadır.

#### Değişikliği Fark Etmeyen Şifreleme

**KI3 — bütünlük etiketi olmayan bir şifreleme, çözmeyi reddedecek bir kaleme sahip değildir; her
şifreli metin bir düz metne çözülür.** İkinci tablo bu cümlenin kapsama karşılığını veriyor. Yirmi
dört pakette birer bit çevrildi; her çevirme çözülen düz metinde **tam olarak bir bayt** değiştirdi
— toplam yirmi dört bayt, paket başına bir. Akış biçiminde şifreli metnin bir konumundaki değişim
düz metnin aynı konumuna düşer, ne yayılır ne de fark edilir.

Bu noktada tek savunma alıcı tarafın biçim denetimidir ve tablo onun kapsamasını sayıyor: yirmi
dört değişikliğin yirmisini yakalıyor, **dördünü kaçırıyor**. Kaçırılan dördü, düz metnin biçim
kalıbına uyan konumlarına düşenlerdir — sayaç numarasının ya da okuma değerinin rakamları. Sonuç,
kaydın hâlâ geçerli göründüğü ama taşıdığı değerin başka olduğu bir pakettir; faturalama tarafında
bunun karşılığı sessizce yanlış bir tüketimdir. Bir savunma noktaların çoğunu örtebilir ve yine de
örtmediği noktalar tam olarak önemli olanlar olabilir.

Son satır aynı ölçünün öteki ucudur: kimlik doğrulamalı şifrelemede yirmi dört değişikliğin yirmi
dördü reddedildi ve biçim denetimine iş düşmedi. Buradaki fark bir güç farkı değil, **kapsama
farkıdır** — bütünlük denetimi paketin tamamını, biçim denetimi yalnızca kalıba giren alanları
örter.

#### Yeni Nokta Eklendiğinde

Üçüncü tablo kursun üçüncü sorusunu soruyor. Kod tabanına üç yeni çağrı noktası ekleniyor: saha
uygulamasının çevrimdışı kuyruğu, portalın dışa aktarma akışı, alım servisinin ölçüm arşivi. İlk
düzende merkezî katman vardır ama ilkel çağrı serbesttir. Nokta sayısı on dörtten on yediye
çıkıyor, katmandaki nokta sayısı **yedide kalıyor** ve üç yeni nokta doğrudan açık adayı oluyor.
Katman kimseyi bağlamadığı için yeni nokta onu kullanmak zorunda değildir; kapsama sabit kalmaz,
her eklemede düşer.

İkinci düzende ilkel çağrılar tek bir dosyada toplanmıştır ve o dosyanın dışındaki hiçbir yer
şifreleme, imza ya da karma ilkelini çağıramaz. Üç yeni nokta katmandan geçmek zorundadır; katman
sayısı ona çıkar, yeni açık adayı sıfırdır. Kapsamayı sabit tutan şey katmanın var olması değil,
**katmanın dışına çıkmanın mümkün olmamasıdır**. Bedeli son satırda yazılı: bu düzene geçmeden önce
dışarıda kalan yedi noktanın yedisini de taşımak gerekir, çünkü tek dosya kuralı ancak istisnası
olmadığında iş görür. Yedi noktalık bir borç, üç yeni noktanın her birinde tekrarlanacak bir
kararın yerini alır.

#### Özet

- Kurgu ölçüm ağının kod tabanında on dört kriptografi çağrı noktası var; yedisi merkezî katmandan
  geçiyor, yedisi dışarıda kalıyor ve dışarıdakilerin beşi kusurlu ayarla çağırıyor. Kapsamanın
  ölçüsü kusurlu nokta sayısı değil, kusurlu olabilecek nokta sayısıdır.
- Elle yazılmış toplama karışımı yirmi bin ayrı anahtarda altmış yedi ayrı değer üretti ve 19.933
  okuma sessizce düştü; hazır karmanın ilk otuz iki biti kuramsal beklentiyle uyumlu, sıfır düşme
  verdi.
- Aynı anahtarla aynı başlangıç değeri kullanıldığında iki kırk baytlık kaydın şifreli metinleri
  otuz sekiz konumda eşit çıkıyor ve kırk konumun tamamında fark doğrudan okunuyor; ayrı başlangıç
  değerinde bu sayılar sıfır.
- Bütünlük etiketi olmayan şifrelemede tek bit çevirmek düz metinde tam bir bayt değiştiriyor;
  biçim denetimi yirmi dört değişikliğin yirmisini yakalıyor, dördünü kaçırıyor. Kimlik doğrulamalı
  şifreleme aynı yirmi dördün yirmi dördünü reddediyor.
- İlkel çağrı serbestken üç yeni nokta üç açık adayı ekliyor ve kapsama düşüyor; ilkel çağrı tek
  dosyaya kapatıldığında kapsama sabit kalıyor, bedeli önce yedi noktanın taşınmasıdır.

#### Sonraki Adım

Bu dersteki bütün ölçümler bir şeyi verilmiş saydı: anahtarı ve başlangıç değerini üreten kaynağın
tahmin edilemez olduğunu. Oysa o kaynak da kod tabanında bir çağrı noktasıdır ve on dört noktalık
envanterde ayrı bir satır olarak görünmez. Saha uygulamasının yükleme belirtecini, portalın parola
sıfırlama bağlantısını ve fatura dosya adını üreten çağrılar aynı soruyu paylaşır: üretilen değeri
üçüncü bir taraf önceden hesaplayabilir mi? Sonraki ders bu soruyu iki üreteci aynı ölçüde
koşturarak yanıtlar — tohum bilindiğinde kaç değer önceden çıkıyor ve belirteç üreten noktaların
kaçı zayıf üreteci çağırıyor.

### Rastgelelik

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/rastgelelik

Zayıf üreteç ile kriptografik olarak güvenli üretecin aynı ölçekte karşılaştırılması: tohum bilindiğinde on bin değerin tamamının önceden hesaplanması, on bin dört haneli koddan yalnız 903'ünün ayrı çıkması, baytı bölme kalanıyla basamağa eşlemenin ürettiği 1,04'lük sapma ve dokuz belirteç üretim noktasının altısının zayıf üreteç çağırması.

Önceki dersin bütün ölçümleri bir şeyi verilmiş saydı: anahtarı ve başlangıç değerini üreten
kaynağın tahmin edilemez olduğunu. O kaynak da kod tabanında bir çağrı noktasıdır ve on dört
noktalık kriptografi envanterinde ayrı bir satır olarak görünmez. Saha uygulamasının yükleme
belirteci, portalın parola sıfırlama bağlantısı ve dört haneli doğrulama kodu aynı soruyu
paylaşır: üretilen değeri üçüncü bir taraf önceden hesaplayabilir mi?

Soru bir kalite sorusu değildir. Bir üreteç ya belirlenimcidir ya değildir ve arada bir bölge
yoktur. Aşağıdaki ölçümler iki üreteci aynı çekim kümesinde koşturur: biri kendi yazdığımız
doğrusal eşlenik üreteç (linear congruential generator), öteki `node:crypto` içindeki
kriptografik olarak güvenli üreteç.

#### İki Üreteç, Aynı Ölçü

**KI4 — zayıf üreteç belirlenimcidir; durumu tohumdan tek bir aritmetik adımla ilerler,
dolayısıyla tohumu bilen bir taraf üretilecek bütün değerleri önceden hesaplar.** Kriptografik
olarak güvenli üreteçte tahmin edilecek böyle bir tohum kalemi yoktur; çıktı işletim sisteminin
entropi havuzundan beslenir.

**KI5 — bir çekim kümesindeki ayrı değer sayısı üretecin gerçek çıktı genişliğini verir**; sayı
kuramsal beklentinin altına düşüyorsa üreteç nominal genişliğini kullanmıyordur. **KI6 — rastgele
baytı bölme kalanıyla bir aralığa eşlemek, aralık genişliği baytın alabileceği değer sayısını tam
bölmüyorsa düzgün dağılım vermez.**

```js
// rast/uretec.mjs — zayif ureteci kriptografik olarak guvenli uretecle ayni olcekte
// karsilastirir. Zayif uretec kendi yazdigimiz dogrusal eslenik uretectir; tohum gorunur.
// Kriptografik uretecin ciktisi kosumdan kosuma degistigi icin ondan okunan degerler
// dogrudan yazilmaz, girdiden turetilen bir bant denetiminden gecirilir.
import { randomBytes, randomInt } from 'node:crypto';

const TOHUM = 20260301;
const CEKIM = 10000;
const uretecYap = (tohum) => {
  let durum = tohum;
  return () => (durum = (durum * 1103515245 + 12345) % 2 ** 31);
};

// 1) Tohum bilindiginde kac deger onceden hesaplaniyor.
const a = uretecYap(TOHUM), b = uretecYap(TOHUM);
let ayni = 0;
for (let i = 0; i < CEKIM; i++) if (a() === b()) ayni += 1;
const g1 = randomBytes(4).readUInt32BE(0), g2 = randomBytes(4).readUInt32BE(0);
console.log(`KI4 — tohum ${TOHUM} bilindiginde onceden hesaplanan deger: ${ayni}/${CEKIM}`);
console.log(`  kriptografik uretecte ayni tohum kalemi yok; iki cagri esit mi: `
  + `${g1 === g2 ? 'evet' : 'hayir'}`);
console.log(`  saniye cozunurluklu saatten tohumlanan uretecte bir gunluk aday tohum: ${24 * 3600}`);

// 2) Dort haneli dogrulama kodu: zayif uretecin alt basamaklari ile guvenli uretec.
const kod = uretecYap(TOHUM);
const zayif = new Set();
for (let i = 0; i < CEKIM; i++) zayif.add(kod() % 10000);
const guvenli = new Set();
for (let i = 0; i < CEKIM; i++) guvenli.add(randomInt(0, 10000));
const bekleniyor = Math.round(10000 * (1 - (1 - 1 / 10000) ** CEKIM));
console.log(`\nKI5 — ${CEKIM} dort haneli kod, kuramsal beklenen ayri deger: ${bekleniyor}`);
console.log(`  zayif uretec, ayri kod                 : ${zayif.size}`);
console.log(`  guvenli uretec, ayri kod bant icinde mi: `
  + `${Math.abs(guvenli.size - bekleniyor) < 120 ? 'evet' : 'hayir'} (${bekleniyor} +/- 120)`);

// 3) Tek bayttan basamak turetmenin sapmasi: once tam sayim, sonra kosum.
const yol = Array.from({ length: 10 }, (_, r) =>
  Array.from({ length: 256 }, (_, x) => x).filter((x) => x % 10 === r).length);
const ORAN = yol[0] / yol[9];
const CEKIM2 = 1000000;
const sapmali = Array(10).fill(0), reddeden = Array(10).fill(0);
let red = 0;
for (const x of randomBytes(CEKIM2)) {
  sapmali[x % 10] += 1;                       // dogrudan bolum: 0-5 fazla cikar
  if (x < 250) reddeden[x % 10] += 1; else red += 1;   // reddederek ornekleme
}
const oranla = (s) => (s.slice(0, 6).reduce((p, q) => p + q, 0) / 6)
  / (s.slice(6).reduce((p, q) => p + q, 0) / 4);
const bant = (o, hedef) => (Math.abs(o - hedef) < 0.008 ? 'evet' : 'hayir');
const redBekleniyor = (CEKIM2 * 6) / 256;
console.log(`\nKI6 — 256 bayt degerinin 10'a bolumunden kalan: 0-5 icin ${yol[0]} yol, `
  + `6-9 icin ${yol[9]} yol, oran ${ORAN.toFixed(4)}`);
const et = (ad) => `  ${ad.padEnd(46)}: `;
console.log(et(`${CEKIM2} baytta dogrudan bolum, oran banti`)
  + `${bant(oranla(sapmali), ORAN)} (${ORAN.toFixed(4)} +/- 0.008)`);
console.log(et('ayni baytlarda reddederek ornekleme, oran banti')
  + `${bant(oranla(reddeden), 1)} (1.0000 +/- 0.008)`);
console.log(et('reddedilen bayt banti')
  + `${Math.abs(red - redBekleniyor) < 600 ? 'evet' : 'hayir'} (${redBekleniyor} +/- 600)`);
```

```
KI4 — tohum 20260301 bilindiginde onceden hesaplanan deger: 10000/10000
  kriptografik uretecte ayni tohum kalemi yok; iki cagri esit mi: hayir
  saniye cozunurluklu saatten tohumlanan uretecte bir gunluk aday tohum: 86400

KI5 — 10000 dort haneli kod, kuramsal beklenen ayri deger: 6321
  zayif uretec, ayri kod                 : 903
  guvenli uretec, ayri kod bant icinde mi: evet (6321 +/- 120)

KI6 — 256 bayt degerinin 10'a bolumunden kalan: 0-5 icin 26 yol, 6-9 icin 25 yol, oran 1.0400
  1000000 baytta dogrudan bolum, oran banti     : evet (1.0400 +/- 0.008)
  ayni baytlarda reddederek ornekleme, oran banti: evet (1.0000 +/- 0.008)
  reddedilen bayt banti                         : evet (23437.5 +/- 600)
```

#### Tohumun Verdiği Şey

Birinci ölçüm bir oran değil, bir eşitliktir: aynı tohumdan başlayan iki üreteç on bin çekimin
**on binini** aynı sırayla üretiyor. Buradaki sayı önemsizdir; önemli olan onun tam sayı olmasıdır.
Zayıf üreteç bir olasılık dağılımı değil, bir işlevdir — tohum verildiğinde çıktı dizisi tektir.
Kriptografik üreteçte aynı ölçüm yapılamaz, çünkü karşılaştırılacak bir tohum kalemi yoktur.

Üçüncü satır tohumun neden ayrı bir kalem olduğunu söylüyor. Üreteç bir saatten tohumlanıyorsa —
uygulama açılışında saniye çözünürlüklü bir değerden — bir günlük pencerede aday tohum sayısı
**86.400**'dür. Bu, çıktının ne kadar uzun olduğundan bağımsız bir tavandır: yüz yirmi sekiz
bitlik görünen bir belirteç, on altı buçuk bitlik bir tahmin uzayı taşıyor olabilir. Uzunluk
görünür bir niceliktir, tohum uzayı görünmez; kod incelemesinde belirtecin uzunluğuna bakmak bu
yüzden yanıltıcıdır.

#### Kodun Kaç Değeri Var

Tahmin edilebilirlik bir evet-hayır niteliği değil, bir sayıdır ve o sayı üç ayrı yerden gelir:
tohum uzayı, üretecin gerçekten dolaştığı çıktı uzayı ve çıktının istenen aralığa eşlenme biçimi.
Üçü birbirinden bağımsızdır ve en küçüğü tavanı belirler. Birinci ölçüm ilkini saydı; ikinci ve
üçüncü ölçüm kalan ikisini sayıyor.

İkinci ölçüm tohum hiç bilinmediğinde bile bir şey söylüyor. On bin dört haneli kod çekildiğinde
kuramsal olarak 6321 ayrı değer beklenir; on bin kutuya on bin top atıldığında bazı kutulara
birden fazla top düşer. Güvenli üreteç bu beklentiyi tutturuyor. Zayıf üreteç aynı çekimde
yalnızca **903** ayrı kod üretiyor — beklenenin tam yedide biri.

Sebep, kodun üretilme biçimindedir: dört haneli kod üretecin çıktısının **alt basamaklarından**
alınıyor ve bu üreteçte alt basamaklar üstlerden çok daha kısa bir çevrimde tekrarlanır. Nominal
olarak on bin değerlik bir uzay vardır; gerçekte dokuz yüz civarında değer dolaşır. Bu sayının
güvenlik karşılığı doğrudandır: doğrulama kodunu tahmin etmeye çalışan bir taraf için uzay on bin
değil, dokuz yüzdür ve bunun için tohumu bilmesi gerekmez, yalnızca kodları toplaması yeter.

#### Basamak Türetmenin Sapması

Üçüncü ölçüm güvenli üretecin de yanlış çağrılabileceğini gösteriyor. Rastgele bir bayt 0–255
arasında değer alır; on tabanlı bir basamak isteyen kod bölme kalanını alırsa, 256 değerin
kalanları eşit dağılmaz. Sıfırdan beşe kadar olan kalanlara **26** bayt değeri düşer, altıdan
dokuza kadar olanlara **25**. Oran 1,04'tür ve bu sayı ölçümden değil aritmetikten okunur; koşum
yalnızca doğrular. Bir milyon baytta ölçülen oran bandın içinde çıkıyor.

Aynı baytlar reddederek örneklemeden geçirildiğinde — iki yüz elliden büyük ya da eşit bayt atılıp
yenisi çekildiğinde — oran bire iniyor. Bedeli üçüncü satırda: bir milyon baytın **23.437,5**
tanesi reddediliyor, yani her kırk üç bayttan biri. Yüzde iki buçukluk bir fazladan çekim, dört
haneli kodun ilk basamağında yüzde dörtlük bir eğilimi kaldırıyor. Ölçünün burada söylediği şey
şudur: güvenli üreteci çağırmak yetmez, çıktısını aralığa **eşleyen** satır da savunmanın
parçasıdır ve o satır çoğunlukla merkezî kalıbın dışında yazılır.

#### Belirteç Üreten Noktalar

Envanter ölçüsüne dönmek gerekiyor. Kurgu ölçüm ağında belirteç, kimlik ya da dosya adı üreten
dokuz nokta var. Aşağıdaki tablo her noktanın nominal genişliğini, üreteç seçimini ve tohum
uzayıyla sınırlanan **etkin** genişliğini yan yana koyuyor.

```js
// rast/belirtec.mjs — kurgu olcum aginda belirtec ureten noktalarin envanteri (model).
// [nokta, tasidigi sey, uretec, nominal bit]. Zayif uretecin tahmin uzayi tohum uzayiyla
// sinirlidir: saniye cozunurluklu saatten tohumlanan uretecte bir gunluk aday tohum 86400.
const noktalar = [
  ['portal/oturum-kimligi', 'kimlik', 'guvenli', 128],
  ['portal/parola-sifirlama', 'yetki', 'zayif', 128],
  ['portal/dogrulama-kodu', 'yetki', 'zayif', 13.3],
  ['portal/fatura-baglantisi', 'yetki', 'guvenli', 128],
  ['saha/yukleme-adi', 'yetki', 'zayif', 64],
  ['saha/is-emri-kimligi', 'kimlik', 'zayif', 32],
  ['alim/paket-kimligi', 'yok', 'zayif', 32],
  ['rapor/dosya-adi', 'yetki', 'zayif', 64],
  ['izleme/istek-kimligi', 'yok', 'guvenli', 64],
];

const TOHUM_UZAYI = Math.log2(24 * 3600);
const etkin = ([, , uretec, bit]) => (uretec === 'zayif' ? Math.min(bit, TOHUM_UZAYI) : bit);
const say = (kural) => noktalar.filter(kural).length;
const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));

const D = [-26, 10, 10, 12, 12];
yaz(D, 'nokta', 'tasidigi', 'uretec', 'nominal bit', 'etkin bit');
for (const n of noktalar) yaz(D, n[0], n[1], n[2], n[3], etkin(n).toFixed(1));

const zayif = noktalar.filter(([, , u]) => u === 'zayif');
const sonuclu = zayif.filter(([, t]) => t !== 'yok');
console.log(`\nnokta ${noktalar.length}, zayif uretec ${zayif.length}, `
  + `bunlarin kimlik ya da yetki tasiyani ${sonuclu.length}, tasimayani ${zayif.length - sonuclu.length}`);
console.log(`toplam nominal bit ${noktalar.reduce((a, n) => a + n[3], 0).toFixed(1)}, `
  + `toplam etkin bit ${noktalar.reduce((a, n) => a + etkin(n), 0).toFixed(1)}`);
console.log(`en buyuk kayip: ${zayif.map((n) => [n[0], n[3] - etkin(n)])
  .sort((x, y) => y[1] - x[1])[0].map((v) => (typeof v === 'number' ? v.toFixed(1) : v)).join(' ')} bit`);

// Iki yeni belirtec noktasi ekleniyor: kalip zorunlu degilken ve zorunluyken.
const yeni = ['portal/paylasim-baglantisi', 'saha/cevrimdisi-kuyruk-kimligi'];
const E = [-34, 8, 10, 12, 16];
console.log('\niki yeni belirtec noktasi ekleniyor:');
yaz(E, 'duzen', 'nokta', 'kalipta', 'disarida', 'yeni acik adayi');
yaz(E, 'kalip var, dogrudan cagri serbest', noktalar.length + yeni.length,
  say(([, , u]) => u === 'guvenli'), say(([, , u]) => u === 'zayif') + yeni.length, yeni.length);
yaz(E, 'kimlik ureten tek islev disa acik', noktalar.length + yeni.length,
  noktalar.length + yeni.length, 0, 0);
console.log(`ikinci duzende once tasinmasi gereken nokta: ${zayif.length}`);
```

```
nokta                       tasidigi    uretec nominal bit   etkin bit
portal/oturum-kimligi         kimlik   guvenli         128       128.0
portal/parola-sifirlama        yetki     zayif         128        16.4
portal/dogrulama-kodu          yetki     zayif        13.3        13.3
portal/fatura-baglantisi       yetki   guvenli         128       128.0
saha/yukleme-adi               yetki     zayif          64        16.4
saha/is-emri-kimligi          kimlik     zayif          32        16.4
alim/paket-kimligi               yok     zayif          32        16.4
rapor/dosya-adi                yetki     zayif          64        16.4
izleme/istek-kimligi             yok   guvenli          64        64.0

nokta 9, zayif uretec 6, bunlarin kimlik ya da yetki tasiyani 5, tasimayani 1
toplam nominal bit 653.3, toplam etkin bit 415.3
en buyuk kayip: portal/parola-sifirlama 111.6 bit

iki yeni belirtec noktasi ekleniyor:
duzen                                nokta   kalipta    disarida yeni acik adayi
kalip var, dogrudan cagri serbest       11         3           8               2
kimlik ureten tek islev disa acik       11        11           0               0
ikinci duzende once tasinmasi gereken nokta: 6
```

Dokuz noktanın **altısı** zayıf üreteç çağırıyor ve bunların **beşi** kimlik ya da yetki taşıyor.
Altıncısı — alım servisinin paket kimliği — tahmin edilebilir olmasının bir sonucu olmayan tek
noktadır; o kimlikle hiçbir kapı açılmaz. Bu ayrım kapsamayı sayarken önemlidir: dışarıda kalan
nokta sayısı altı, güvenlik sonucu doğuran nokta sayısı beştir ve kalıbı taşıma işi bu beşten
başlar.

Etkin bit sütunu, nominal genişliğe bakmanın neden yanıltıcı olduğunu tek satırda gösteriyor.
Parola sıfırlama bağlantısı yüz yirmi sekiz bitlik bir dizge üretiyor; tohum uzayı hesaba
katıldığında geriye **16,4 bit** kalıyor ve tek bir noktada **111,6 bitlik** bir kayıp doğuyor.
Toplamda 653,3 nominal bitin 415,3'ü ayakta kalıyor. Doğrulama kodu ise ilginç bir istisnadır:
nominal genişliği zaten 13,3 bit olduğu için üreteç değişikliği ona bit kazandırmaz. O noktanın
savunması üreteçte değil, deneme sayısını sınırlayan kuraldadır — ve o kural bu dersin kapsamının
dışındadır.

Bu envanter önceki dersin on dört noktalık kriptografi envanteriyle bir yerde kesişiyor. Orada iki
nokta "sabit başlangıç değeri" kusuruyla işaretlenmişti; sabit başlangıç değeri, üreteci **hiç
çağırmamak** demektir ve o kalemin tahmin uzayını sıfıra indirir. Aynı üreteç iki envanterde birden
görünür: bir yerde belirteç, öteki yerde şifreleme parametresi üretir. İki kalıbı ayrı ayrı taşımak
aynı çağrıyı iki kez kapatmak demektir; iki envanteri tek bir "tahmin edilemez değer üreten"
işleve bağlamak ise tek kapatma yapar ve bundan sonraki her yeni noktayı ikisinde birden kapsar.

#### Yeni Nokta Eklendiğinde

Son tablo iki yeni belirteç noktası ekliyor: portalın paylaşım bağlantısı ve saha uygulamasının
çevrimdışı kuyruk kimliği. İlk düzende güvenli üreteci çağıran bir kalıp vardır ama zorunlu
değildir. Nokta sayısı dokuzdan on bire çıkıyor, kalıptaki nokta **üçte kalıyor**, dışarıda kalan
sekize çıkıyor ve iki yeni nokta doğrudan açık adayı oluyor. Yeni bir noktayı yazan kişi, kalıbın
var olduğunu bilse bile üreteci kendi seçebilir; kapsama her eklemede düşer.

İkinci düzende kimlik üreten tek bir işlev dışa açılmıştır ve üreteç çağrısı başka hiçbir yerde
görünmez. On bir noktanın on biri kalıptan geçer, yeni açık adayı sıfırdır. Bedel yine son
satırdadır: bu düzene geçmeden önce zayıf üreteç kullanan altı noktanın altısını da taşımak
gerekir. Kapsamayı sabit tutan şey kalıbın kalitesi değil, kalıbın **tek yol** olmasıdır.

#### Özet

- Aynı tohumdan başlayan iki zayıf üreteç on bin çekimin on binini aynı sırayla üretti; zayıf
  üreteç bir dağılım değil, bir işlevdir.
- Saniye çözünürlüklü bir saatten tohumlanan üreteçte bir günlük aday tohum sayısı 86.400'dür; bu,
  çıktının uzunluğundan bağımsız bir tahmin tavanı koyar.
- On bin dört haneli kodda kuramsal beklenti 6321 ayrı değerken zayıf üreteç 903 ayrı kod üretti;
  alt basamaklardan okumak nominal uzayı on kat daralttı.
- Baytı bölme kalanıyla basamağa eşlemek 0–5 için 26, 6–9 için 25 yol bırakıyor ve 1,04'lük bir
  eğilim doğuruyor; reddederek örnekleme bunu bire indiriyor, bedeli kırk üç bayttan biridir.
- Dokuz belirteç noktasının altısı zayıf üreteç çağırıyor, beşi kimlik ya da yetki taşıyor;
  parola sıfırlama bağlantısında tek başına 111,6 bitlik bir kayıp var.
- Kalıp zorunlu değilken iki yeni nokta kapsamayı üçte bırakıp iki açık adayı ekliyor; üreteç
  çağrısı tek işleve kapatıldığında on bir noktanın on biri kapsanıyor.

#### Sonraki Adım

Bu iki ders anahtarın ve belirtecin nasıl üretildiğini ölçtü ve ikisi de aynı varsayıma dayandı:
üretilen değerin yalnız onu üreten süreçte kaldığına. Oysa o değerlerin bir bölümü hiç
üretilmemiştir — bir yapılandırma satırında sabit yazılmış, oradan da sürüm denetimine girmiştir.
Sonraki ders bu kalemi kurgu bir depo geçmişi üzerinde sayar: kaç işlemde sır kalmış, bir sır
eklendiği andan döndürüldüğü ana kadar kaç gün açık durmuş, geçmişi temizlemek bunun ne kadarını
kurtarıyor ve deseni tarayan bir kural kaç yanlış alarm üretiyor.

### Kodda Gömülü Sırlar

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/kodda-gomulu-sirlar

Kurgu bir depo geçmişinde sırrın açık kaldığı işlem sayısı ve desen taramasının kapsaması: beş sırrın çalışma ağacında 124, geçmişte 223 işlem görünmesi, tarihçe temizlemenin on dokuz kopyanın on sekizine erişememesi, anlık görüntü taramasının beş sırdan yalnız birini görmesi ve aynı kuralın geçmişte 76,8 kat bulgu ile 0,69 yanlış alarm oranı üretmesi.

Önceki iki ders anahtarın ve belirtecin nasıl üretildiğini ölçtü ve ikisi de aynı varsayıma
dayandı: üretilen değerin yalnız onu üreten süreçte kaldığına. Bu varsayım bir bölüm değer için
hiç doğru olmaz. O değerler üretilmemiştir; bir yapılandırma satırına elle yazılmış, oradan da
sürüm denetimine girmiştir. Girdikleri anda bir dosyada değil, bir **geçmişte** dururlar ve
geçmişin kendine özgü bir ölçüsü vardır.

Gizli Değer Yönetimi dersi üretimdeki değerin döndürme penceresini ve sızdığında ne kadar süre
geçerli kaldığını ölçmüştü. Burada ölçülen şey saklama pratiği değil, **kapsamadır**: kaç işlemde
sır kalmış, silmek kaç işlemi kapatıyor, bir tarama kuralı kaç noktayı görüyor ve görmek için kaç
yanlış alarm ödeniyor. Aşağıdaki depo geçmişi bir modeldir; işlem sayıları, dosya adları ve
değerler kurgudur ve hiçbiri gerçek bir sır biçimi taşımaz.

#### Depo Geçmişinde Bir Sır

**KI7 — bir değer işlendiği anda depo geçmişine girer; çalışma ağacından silinmesi geçmişten
silinmesi değildir.** Silme yeni bir işlemdir ve eski işlemler yerinde durur. Bir sırrın gerçekten
kapandığı an, değerin geçersiz kılındığı — döndürüldüğü — andır.

```js
// depo/gecmis.mjs — kurgu bir depo gecmisi (model): 60 islem, bes kurgu sir. Sir degerleri
// asagida kendi uretecimizle uretilir; gercek bir sir bicimi, gercek anahtar deseni ve
// gercek servis adi kullanilmaz. giren: degerin islendigi islem; silinen: calisma agacindan
// cikarildigi islem; dondurulen: degerin gecersiz kilindigi islem (null = hic).
export const ISLEM = 60;
let durum = 4703;
export const rast = () => (durum = (durum * 1103515245 + 12345) % 2 ** 31) / 2 ** 31;
const ABC = 'abcdefghijkmnpqrstuvwxyzACDEFGHJKLMNPQRTUVWXYZ2346789';
export const uret = (n) => Array.from({ length: n }, () => ABC[Math.floor(rast() * ABC.length)]).join('');

export const sirlar = [
  { ad: 'saha imza anahtari', giren: 7, silinen: 19, dondurulen: null },
  { ad: 'rapor aktarim parolasi', giren: 12, silinen: null, dondurulen: null },
  { ad: 'portal oturum tuzu', giren: 3, silinen: 41, dondurulen: 44 },
  { ad: 'alim ust anahtari', giren: 22, silinen: 26, dondurulen: null },
  { ad: 'is emri geri cagirma anahtari', giren: 33, silinen: 55, dondurulen: 51 },
];

const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));
const yok = (x) => (x === null ? '-' : x);

// KI7: bir sir islendigi anda gecmise girer; calisma agacindan silinmesi gecmisten
// silinmesi degildir. Kapanma yalnizca dondurme ile olur.
const D = [-30, 8, 9, 12, 9, 10, 8];
yaz(D, 'sir', 'giren', 'silinen', 'dondurulen', 'agacta', 'gecmiste', 'acik');
let agacta = 0, gecmiste = 0, acik = 0;
for (const s of sirlar) {
  const a = (s.silinen ?? ISLEM) - s.giren;
  const g = ISLEM - s.giren;
  const c = (s.dondurulen ?? ISLEM) - s.giren;
  agacta += a; gecmiste += g; acik += c;
  yaz(D, s.ad, s.giren, yok(s.silinen), yok(s.dondurulen), a, g, c);
}
yaz(D, 'toplam', '', '', '', agacta, gecmiste, acik);
console.log(`agactan silinmis ama dondurulmemis sir: `
  + `${sirlar.filter((s) => s.silinen !== null && s.dondurulen === null).length}/${sirlar.length}`);

// KI8: tarihce temizleme yalnizca erisilen kopyayi degistirir; dondurme her kopyayi
// degersiz kilar. Kopya sayilari kurgudur.
console.log('\ndeger islendikten sonra ulastigi kopyalar:');
const tutucular = [
  ['merkezi depo', 1, true],
  ['gelistirici klonu', 9, false],
  ['ayna ve yedek', 2, false],
  ['yapi calisma alani ve gunlugu', 4, false],
  ['paketlenmis yapi ciktisi', 3, false],
];
const E = [-32, 8, 18, 10];
yaz(E, 'tutucu', 'kopya', 'temizleme erisir', 'dondurme');
for (const [ad, n, erisir] of tutucular) yaz(E, ad, n, erisir ? 'evet' : 'hayir', 'evet');
const toplam = tutucular.reduce((a, [, n]) => a + n, 0);
const erisilen = tutucular.filter(([, , e]) => e).reduce((a, [, n]) => a + n, 0);
yaz(E, 'toplam', toplam, `${erisilen}`, `${toplam}`);
console.log(`tarihce temizlemenin disinda kalan kopya: ${toplam - erisilen}/${toplam}`);
```

```
sir                              giren  silinen  dondurulen   agacta  gecmiste    acik
saha imza anahtari                   7       19           -       12        53      53
rapor aktarim parolasi              12        -           -       48        48      48
portal oturum tuzu                   3       41          44       38        57      41
alim ust anahtari                   22       26           -        4        38      38
is emri geri cagirma anahtari       33       55          51       22        27      18
toplam                                                           124       223     198
agactan silinmis ama dondurulmemis sir: 2/5

deger islendikten sonra ulastigi kopyalar:
tutucu                             kopya  temizleme erisir  dondurme
merkezi depo                           1              evet      evet
gelistirici klonu                      9             hayir      evet
ayna ve yedek                          2             hayir      evet
yapi calisma alani ve gunlugu          4             hayir      evet
paketlenmis yapi ciktisi               3             hayir      evet
toplam                                19                 1        19
tarihce temizlemenin disinda kalan kopya: 18/19
```

#### Silmenin Kapatmadığı Şey

Üç sayı sütunu üç ayrı soruya karşılık geliyor ve üçü de aynı beş sır için farklı çıkıyor. Beş sır
çalışma ağacında toplam **124 işlem** boyunca görünüyor; aynı beş sır depo geçmişinde **223 işlem**
boyunca erişilebilir durumda; gerçekten açık kaldıkları süre ise **198 işlem**. Ortadaki sütun ile
soldaki arasındaki fark, silme işleminin ürettiği yanılsamanın büyüklüğüdür: doksan dokuz işlemlik
bir aralık "temizlendi" diye kapatılmış görünüyor ama içerik geçmişte duruyor.

Satırlar tek tek okununca ölçü daha keskinleşiyor. Alım servisinin üst anahtarı çalışma ağacında
yalnızca **dört işlem** kalmış — girdiği işlemden dört işlem sonra fark edilip silinmiş. Bu, hızlı
bir düzeltme gibi görünür. Geçmiş sütunu aynı değer için **38** yazıyor ve açık sütunu da 38, çünkü
değer hiç döndürülmemiş. Dört işlemlik bir görünürlük, otuz sekiz işlemlik bir açıklığa karşılık
geliyor. Aynı kalıp saha imza anahtarında da var: on iki işlem ağaçta, elli üç işlem açık.

Son satır düzenin doğru sırasını gösteriyor. İş emri geri çağırma anahtarı 51'inci işlemde
döndürülmüş, 55'inci işlemde silinmiş; açık kaldığı süre **18 işlem** ve bu, beş sırrın en kısası.
Ağaçta kalma süresi (22 işlem) açık kalma süresinden **uzun** olan tek satır budur. Önce döndürüp
sonra temizlemek, ölçüyü tersine çeviren tek sıradır.

İkinci tablo bunun nedenini sayıyor. İşlenen bir değer yayımlandıktan sonra on dokuz kopyaya
ulaşıyor: merkezî depo, dokuz geliştirici klonu, iki ayna ve yedek, dört yapı çalışma alanı ile
günlüğü, üç paketlenmiş yapı çıktısı. Tarihçeyi yeniden yazmak bunlardan **birine** erişiyor;
kalan **on sekizi** dokunulmadan duruyor. Klonların her biri ayrı bir işlemle güncellenmek
zorundadır, yapı günlükleri çoğunlukla değiştirilemez, paketlenmiş çıktı zaten dağıtılmıştır.
Döndürme sütunu ise on dokuzun on dokuzunda `evet` yazıyor — çünkü döndürme kopyayı silmez,
kopyayı **değersiz** kılar. Değer dağıldıysa tek çıkar döndürmektir; tarihçe temizlemek onun yerini
almaz, olsa olsa yeni bir kopya üretmeyi durdurur.

#### Aynı Kural, İki Ayrı Tarama

**KI9 — bir tarama kuralı yalnızca taradığı kümedeki noktaları kapsar; anlık görüntü ile geçmiş iki
ayrı kümedir.** Aşağıdaki kural verilmiştir ve tartışılmaz: yirmi karakterden uzun ve karakter
entropisi 3,6'nın üstünde olan değer işaretlenir. Ölçülen şey kuralın kalitesi değil, aynı kuralın
iki ayrı küme üzerinde ne kapsadığıdır.

```js
// depo/tarama.mjs — ayni kurgu depo gecmisinde desen taramasi (model, kendi basina kosar).
// Kural verilmistir: uzunluk >= 20 ve karakter entropisi >= 3.6 olan deger isaretlenir.
// Degerler kendi uretecimizle uretilir; hicbiri gercek bir sir bicimi tasimaz.
const ISLEM = 60;
let durum = 4703;
const rast = () => (durum = (durum * 1103515245 + 12345) % 2 ** 31) / 2 ** 31;
const ABC = 'abcdefghijkmnpqrstuvwxyzACDEFGHJKLMNPQRTUVWXYZ2346789';
const uret = (n) => Array.from({ length: n }, () => ABC[Math.floor(rast() * ABC.length)]).join('');

// [dosya, deger, gizli mi, giren islem, silinen islem]
const kalemler = [
  ['saha/ayar.mjs', uret(32), true, 7, 19],
  ['rapor/aktar.mjs', uret(28), true, 12, null],
  ['portal/oturum.mjs', uret(30), true, 3, 41],
  ['alim/kaynak.mjs', uret(14), true, 22, 26],
  ['is-emri/uc.mjs', uret(26), true, 33, 55],
  ['izleme/ornek.mjs', uret(28), false, 0, null],
  ['portal/simge.mjs', uret(40), false, 0, null],
  ['yapi/surum.mjs', uret(24), false, 5, null],
  ['katalog/kimlik.mjs', uret(22), false, 0, null],
  ['saha/sinama.mjs', uret(21), false, 9, 38],
  ['ceza/ayar.mjs', './veri/gecici/ceza', false, 0, null],
  ['bildirim/ornek.mjs', 'DEGISTIR-BENI', false, 0, null],
  ['uyelik/tur.mjs', 'abone-standart-tarife', false, 0, null],
];

const entropi = (d) => {
  const say = {};
  for (const c of d) say[c] = (say[c] ?? 0) + 1;
  return -Object.values(say).reduce((a, n) => a + (n / d.length) * Math.log2(n / d.length), 0);
};
const KURAL = (d) => d.length >= 20 && entropi(d) >= 3.6;
const varMi = (k, islem) => islem >= k[3] && (k[4] === null || islem < k[4]);

const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));
const D = [-30, 10, 8, 11, 11, 13, 8];
yaz(D, 'tarama', 'taranan', 'bulgu', 'yakalanan', 'kacirilan', 'yanlis alarm', 'oran');

const olc = (ad, cift, tekil) => {
  const bulgu = tekil ? new Set(cift.filter(([k]) => KURAL(k[1])).map(([k]) => k[1])).size
    : cift.filter(([k]) => KURAL(k[1])).length;
  const dogru = tekil ? new Set(cift.filter(([k]) => KURAL(k[1]) && k[2]).map(([k]) => k[1])).size
    : cift.filter(([k]) => KURAL(k[1]) && k[2]).length;
  const gizliToplam = tekil ? new Set(cift.filter(([k]) => k[2]).map(([k]) => k[1])).size
    : cift.filter(([k]) => k[2]).length;
  yaz(D, ad, cift.length, bulgu, dogru, gizliToplam - dogru, bulgu - dogru,
    bulgu ? ((bulgu - dogru) / bulgu).toFixed(2) : '-');
};

const son = kalemler.filter((k) => varMi(k, ISLEM - 1)).map((k) => [k]);
const gecmis = [];
for (let i = 0; i < ISLEM; i++) for (const k of kalemler) if (varMi(k, i)) gecmis.push([k, i]);
olc('anlik goruntu (son islem)', son, false);
olc('tum gecmis', gecmis, false);
olc('tum gecmis + tekillestirme', gecmis, true);

const gizliGecmis = gecmis.filter(([k]) => k[2]).length;
console.log(`\nson islemde duran gizli kalem: ${son.filter(([k]) => k[2]).length}/5;`
  + ` gecmiste gorunen gizli kalem-islem cifti: ${gizliGecmis}`);
const kacan = kalemler.filter((k) => k[2] && !KURAL(k[1]));
console.log(`kuralin kacirdigi gizli kalem: ${kacan.map((k) => `${k[0]} (uzunluk ${k[1].length})`).join(', ')}`);
console.log(`gecmis taramasinin anlik goruntuye gore bulgu carpani: `
  + `${(gecmis.filter(([k]) => KURAL(k[1])).length / son.filter(([k]) => KURAL(k[1])).length).toFixed(1)}x`);
```

```
tarama                           taranan   bulgu  yakalanan  kacirilan yanlis alarm    oran
anlik goruntu (son islem)              8       5          1          0            4    0.80
tum gecmis                           568     384        120          4          264    0.69
tum gecmis + tekillestirme           568       9          4          1            5    0.56

son islemde duran gizli kalem: 1/5; gecmiste gorunen gizli kalem-islem cifti: 124
kuralin kacirdigi gizli kalem: alim/kaynak.mjs (uzunluk 14)
gecmis taramasinin anlik goruntuye gore bulgu carpani: 76.8x
```

#### Yanlış Alarmın Bedeli

Birinci satır kapsamayı en açık biçimde veriyor. Son işlemin çalışma ağacında yalnızca sekiz kalem
var ve bunların **yalnız biri** gizli. Anlık görüntü taraması o biri buluyor, kaçırdığı yok ve
mükemmel görünüyor. Oysa beş sırrın dördü zaten silinmiştir ve tarama onları hiç görmez. Kapsama
oranı bu satırda 1/5'tir ve tarama bunu bildirmez; bildirdiği sayı yakaladığı bir bulgudur.

Tablonun altındaki satır bu 1/5'i başka bir açıdan yazıyor: geçmişte gizli bir kalemin göründüğü
işlem çifti sayısı **124**'tür. Aynı sayı birinci tablonun "ağaçta" sütununun toplamıdır ve iki
ölçümün aynı modeli okuduğunu gösterir. Anlık görüntü taraması bu 124 çiftin yalnızca son
işlemdekini görür.

İkinci satır aynı kuralı tüm geçmişe uyguluyor. Taranan kalem-işlem çifti 568'e, bulgu 384'e
çıkıyor ve beş sırrın beşi de görünür hâle geliyor. Bulgu sayısı anlık görüntüye göre **76,8 kat**
artmış durumda. Yanlış alarm sayısı 264'tür ve oranı 0,69 — yani her üç bulgudan ikisi gizli
olmayan bir değerdir. Bu oran kuralın kötülüğünden değil, aynı zararsız değerin altmış işlemde
altmış kez sayılmasından geliyor.

Üçüncü satır bu çarpanı kaldırıyor. Bulgular içeriğe göre tekilleştirildiğinde 384 bulgu **dokuza**
iniyor: dördü gizli, beşi yanlış alarm, oran 0,56. İncelenecek kalem sayısı iki büyüklük sırası
küçülüyor ve kapsama hiç değişmiyor. Geçmiş taramasında ölçülmesi gereken şey bulgu sayısı değil,
**ayrı değer** sayısıdır; aksi hâlde tarama kendi gürültüsünü ölçer.

Son satır kuralın sınırını yazıyor: alım servisinin üst anahtarı on dört karakter uzunluğunda ve
eşiğin altında kaldığı için hiçbir taramada görünmüyor. Beş sırdan dördü kapsanıyor, biri dışarıda
kalıyor. Bu, taramanın kendi ölçüsüyle gösterilemeyen bir eksiktir; kaçırdığını ancak başka bir
yerden bilen biri sayabilir.

#### Yeni Sır Eklendiğinde

Üçüncü sütun bu dersin en keskin ayrımını taşıyor. Desen taraması bir **kalıp değil, bir süzgeçtir**:
yarın eklenecek bir sır ancak eşiği geçerse kapsanır. Beş sırrın biri bugün eşiğin altındadır,
dolayısıyla yeni bir değerin kapsanma olasılığı ölçülmüş değil, umulan bir şeydir. Eşiği düşürmek
kapsamayı artırır ve yanlış alarmı büyütür; yanlış alarm oranı zaten 0,56'dır ve her artış inceleme
işini çoğaltır.

Sırrın kaynak ağacına hiç girmediği kalıp bu sorunu farklı bir yerden çözüyor. Değer çalışma anında
bir sır deposundan okunuyorsa kaynakta hiçbir zaman bir değer bulunmaz; kapsama, taramanın
bulduğuna değil, kaynakta hiç değer olmamasına dayanır. Yeni bir sır eklendiğinde kapsama
kendiliğinden sürer, çünkü değeri koda yazacak bir yer yoktur. Bu düzende tarama ortadan kalkmaz —
kalıbın **dışına çıkan** noktayı bulmak için kalır — ama artık tek savunma değildir. İki savunmanın
kapsaması aynı beş sırda ölçüldüğünde biri 4/5, öteki 5/5 verir ve aradaki bir satırlık fark, altmış
işlemlik bir geçmişte 38 işlemlik bir açıklığa karşılık gelir.

#### Özet

- Beş sır çalışma ağacında 124 işlem, depo geçmişinde 223 işlem görünüyor; gerçekten açık kaldıkları
  süre 198 işlem. Silme, doksan dokuz işlemlik bir aralığı kapatılmış gösteriyor.
- Alım servisinin anahtarı ağaçta dört işlem kalmış ama hiç döndürülmediği için 38 işlem açık;
  ağaçta kalma süresi açık kalma süresinden kısa olduğu sürece silme bir ölçü vermez.
- Önce döndürüp sonra temizleyen tek satır en kısa açıklığı üretti: 18 işlem.
- İşlenen değer on dokuz kopyaya ulaşıyor; tarihçe temizleme birine erişiyor, on sekizi dışarıda
  kalıyor. Döndürme on dokuzunu birden değersiz kılıyor.
- Anlık görüntü taraması beş sırdan yalnız birini görüyor; aynı kural tüm geçmişte beşini de
  buluyor ama bulgu 76,8 kat artıyor ve yanlış alarm oranı 0,69 oluyor. İçerik tekilleştirmesi
  bulguyu dokuza indiriyor, kapsamayı değiştirmiyor.
- Desen taraması yeni sırrı ancak eşiği geçerse kapsıyor (4/5); değeri kaynağa hiç yazmayan kalıp
  yeni noktayı kendiliğinden kapsıyor.

#### Sonraki Adım

Bu ders sırrın kaynak ağacında ve depo geçmişinde bıraktığı izi saydı. Değer kaynaktan çıkarılıp
bir depoya taşındığında bu iz kapanır — ama değer çalışma anında hâlâ süreçte durur ve süreç
konuşur. Bir istek hata verdiğinde geriye dönen yanıt, bir işlem yapıldığında yazılan günlük satırı,
bir çökme sonrası dosyaya düşen yığıt izi: hepsi sürecin içindeki değerlerin dışarı çıktığı
noktalardır. Sonraki ders bu noktaları kalem olarak sayar — kaç yanıt yığıt izi taşıyor, kaç günlük
satırı kimlik bilgisi ya da kişisel veri taşıyor, merkezî bir maskeleme katmanı kaçını kapatıyor ve
kaç kalem dışarıda kalıyor.

### Hata Mesajları ve Günlük Sızıntısı

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/hata-mesajlari-ve-gunluk-sizintisi

Hata yanıtı ve günlük satırındaki sızıntının kalem olarak sayılması: sekiz ucun doğrudan kipte kırk kalem-yanıt çifti taşıması, merkezî hata işleyicinin altı ucu örtüp iki ucu dışarıda bırakması, on bir günlük kaleminden üst düzey ad listesinin üçünü, her düzeyde çalışan listenin sekizini, biçime göre maskelemenin dokuzunu kapatması ve serbest metin ile bağlantı sorgusundaki iki kalemin hiçbir listeyle kapanmaması.

Önceki ders sırrın kaynak ağacında ve depo geçmişinde bıraktığı izi saydı. Değer kaynaktan
çıkarılıp bir depoya taşındığında bu iz kapanır — ama değer çalışma anında hâlâ süreçte durur ve
süreç konuşur. Bir istek hata verdiğinde geriye dönen yanıt, bir işlem yapıldığında yazılan günlük
satırı, bir çökme sonrası dosyaya düşen yığıt izi: hepsi sürecin içindeki değerlerin dışarı çıktığı
noktalardır.

Bu noktaların ortak yanı, hiçbirinin bir güvenlik kararı olarak yazılmamış olmasıdır. Yığıt izini
yanıta koyan satır hata ayıklamak için oradadır, günlüğe isteği bütün alanlarıyla yazan satır
sorunu bulmak için. Ölçü bu yüzden "hangi satır yanlış" değil, **kaç kalem dışarı çıkıyor** ve
merkezî bir katman bunların kaçını kapatıyor sorusudur.

#### Hata Yanıtındaki Kalemler

**KI10 — bir hata yanıtı, uygulama içi bilgi taşıyan kalemlerin toplamıdır; her kalem ayrı
sayılır.** Yığıt izi, iç dosya yolu, iç bileşen adı, hata sınıfının adı ve ham isteğin yankısı beş
ayrı kalemdir ve bir yanıt bunların hepsini birden taşıyabilir. **KI11 — merkezî bir hata işleyici
yalnızca kendisine ulaşan hatayı örter**; hatasını kendi yakalayıp kendi yanıtını yazan uç
kapsamanın dışındadır.

```js
// gunluk/yanit.mjs — gercek bir yerel sunucu ayaga kaldirilir, sekiz uca istek atilir ve
// donen yanit govdesinde ic bilgi tasiyan kalemler sayilir. Govdeler basilmaz (mutlak yol
// icerir); yalnizca kalem sayilari yazilir. "kendi" isaretli iki uc hatasini kendi yakalayip
// kendi yanitini yaziyor, yani merkezi isleyiciye hic ulasmiyor. Kurgu olcum agi uclari.
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';

// [yol, hatayi atan islev, kendi yanitini mi yaziyor]
const UCLAR = [
  ['/abone/ara?ad=kurgu-abone-118', () => { throw new Error('abone-dizini/arama.mjs: dizin yaniti bos'); }, false],
  ['/fatura/hesap?no=118', () => { throw new RangeError('fatura-hesap/donem.mjs: donem araligi disi'); }, false],
  ['/olcum/al?sayac=SAY-4417', () => { throw new TypeError('olcum-alim/paket.mjs: alan bekleniyordu'); }, true],
  ['/rapor/uret?tur=aylik', () => { throw new Error('rapor-uretici/sablon.mjs: sablon bulunamadi'); }, false],
  ['/is-emri/ata?ekip=3', () => { throw new Error('is-emri/atama.mjs: ekip kapasitesi asildi'); }, false],
  ['/saha/yukle?ad=olcum.csv', () => { throw new Error('saha-yukleme/depo.mjs: depo yaniti gecersiz'); }, true],
  ['/portal/giris?kullanici=kurgu-abone-118', () => { throw new Error('portal-kimlik/dogrula.mjs: karsilastirma basarisiz'); }, false],
  ['/bildirim/gonder?kanal=kisa', () => { throw new Error('bildirim-kuyruk/gonder.mjs: kuyruk dolu'); }, false],
];

let kip = 'dogrudan';
const sunucu = createServer((istek, yanit) => {
  const uc = UCLAR.find(([u]) => u === istek.url);
  try {
    uc[1]();
  } catch (hata) {
    const acik = kip === 'dogrudan' || (kip === 'merkezi' && uc[2]);
    const govde = acik
      ? { hata: hata.message, tur: hata.constructor.name, iz: hata.stack, istek: istek.url }
      : { hata: 'islem tamamlanamadi', kimlik: randomUUID() };
    yanit.writeHead(500, { 'content-type': 'application/json' });
    yanit.end(JSON.stringify(govde));
  }
});
sunucu.listen(8794, '127.0.0.1');

const ISARETLER = [
  ['yigit izi', (g) => /\bat\s+\S+\s*\(/.test(g)],
  ['ic dosya yolu', (g) => /\.mjs/.test(g)],
  ['ic bilesen adi', (g) => /(abone-dizini|fatura-hesap|olcum-alim|rapor-uretici|is-emri|saha-yukleme|portal-kimlik|bildirim-kuyruk)/.test(g)],
  ['hata sinifi', (g) => /"tur":"(Error|RangeError|TypeError)"/.test(g)],
  ['istek yankisi', (g) => /"istek":/.test(g)],
  ['korelasyon kimligi', (g) => /"kimlik":/.test(g)],
];

const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));
const D = [-32, 11, 15, 16, 12, 15, 20];
yaz(D, 'kip', ...ISARETLER.map(([a]) => a));

for (const k of ['dogrudan', 'merkezi', 'kendi yaniti kapatilmis']) {
  kip = k === 'kendi yaniti kapatilmis' ? 'kapali' : k;
  const sayac = ISARETLER.map(() => 0);
  for (const [yol] of UCLAR) {
    const govde = await (await fetch(`http://127.0.0.1:8794${yol}`)).text();
    ISARETLER.forEach(([, kural], i) => { if (kural(govde)) sayac[i] += 1; });
  }
  yaz(D, k, ...sayac);
}
sunucu.close();

const kalem = ISARETLER.length - 1;
const kendi = UCLAR.filter((u) => u[2]).length;
console.log(`\nuc ${UCLAR.length}, yanit basina olculen kalem ${kalem}, `
  + `dogrudan kipte kalem-yanit cifti ${UCLAR.length * kalem}`);
console.log(`merkezi isleyici ${UCLAR.length - kendi} ucu ortuyor, ${kendi} uc disarida kaliyor; `
  + `disarida kalan kalem-yanit cifti ${kendi * kalem}`);
```

```
kip                               yigit izi  ic dosya yolu  ic bilesen adi hata sinifi  istek yankisi  korelasyon kimligi
dogrudan                                  8              8               8           8              8                   0
merkezi                                   2              2               2           2              2                   6
kendi yaniti kapatilmis                   0              0               0           0              0                   8

uc 8, yanit basina olculen kalem 5, dogrudan kipte kalem-yanit cifti 40
merkezi isleyici 6 ucu ortuyor, 2 uc disarida kaliyor; disarida kalan kalem-yanit cifti 10
```

#### Merkezden Geçmeyen Uç

Birinci satır ölçünün başlangıç noktasıdır: sekiz ucun sekizi de beş kalemin beşini birden
taşıyor, yani **40 kalem-yanıt çifti**. Bu, kimsenin bir karar vermediği durumdur — hata nesnesi
olduğu gibi yanıta yazılmıştır ve yığıt izi sunucunun dosya düzenini, hata sınıfı iç mantığı, ham
istek yankısı ise gönderilen değerin geri döndüğünü söyler.

İkinci satır merkezî işleyicinin kapsamasını veriyor ve kursun kuralının en açık örneğidir. Sekiz
ucun **altısı** örtülüyor, kalem sayısı sıfıra iniyor ve yerine bir korelasyon kimliği geliyor.
Kalan **iki uç** — ölçüm alım ucu ile saha yükleme ucu — hatasını kendi yakalayıp kendi yanıtını
yazdığı için merkezî işleyiciye hiç ulaşmıyor. Bu iki uçta beş kalemin beşi de duruyor: dışarıda
kalan çift sayısı **10**. Kapsama 40'tan 10'a inmiştir, sıfıra değil.

Buradaki ayrım önemlidir: merkezî işleyici kusurlu değildir, örttüğü altı uçta kusursuz çalışır.
Kapsamayı düşüren şey işleyicinin kalitesi değil, **onu atlamanın mümkün olmasıdır**. Kendi hata
yanıtını yazan bir uç, gözden geçirmede çoğunlukla iyi bir alışkanlık gibi görünür — hatayı yerinde
ele almak. Üçüncü satır ise kuralı yazıyor: yerel yanıt yazımı kapatıldığında sekiz ucun sekizi de
korelasyon kimliğine iniyor ve dışarıda kalan kalem sıfır oluyor.

Korelasyon kimliği sütunu bu düzenin bedelini de taşıyor. Yanıttan çıkarılan beş kalem yok
olmuyor; bir kimliğe bağlanıp **içeri** taşınıyor. Bir arıza aramasında o kimlikle iç kayda gidilir
ve yığıt izi orada bulunur. Ölçünün söylediği şey, sızıntının kapatılmadığı, yalnızca yer
değiştirdiğidir: yanıt sekiz istemciye açıktır, iç kayıt belirli bir okuyucu kümesine. İkinci
ölçüm tam da bu taşınan yığının kendisini sayıyor, çünkü günlük yanıttan daha uzun süre saklanır ve
daha çok yere kopyalanır.

#### Günlük Satırındaki Kalemler

Yanıt dışarıya gider, günlük içeride kalır — ama günlük de bir yere yazılır, bir yere taşınır ve
çoğunlukla yanıttan daha uzun süre saklanır. **KI12 — maskeleme katmanı alan adına bakarak
çalışır; adı olmayan bir yerde duran değer hiçbir ad listesiyle kapatılmaz.**

```js
// gunluk/maskeleme.mjs — kurgu gunluk kayitlarinda maskeleme katmaninin kapsamasi (model).
// Degerler kurgudur; hicbiri gercek bir sir bicimi ya da gercek bir kisiye ait veri degildir.
const T = 'kurgubelirtec7f2a9c41d8b3e6';
const IMZA = 'kurguimza4b7d2e9a1c8f5306';
const P = 'kurgu-parola-9c1x', AD = 'A. Yilmaz', ADRES = 'Kurgu Mah. 12/4';
const TEL = '05000000000', KN = '10000000146';

// [kayit, o kayitta gecen duyarli deger listesi, sizinti turu]
const kayitlar = [
  [{ olay: 'giris', kullanici: 'kurgu-abone-118', belirtec: T }, [T], 'ust duzey alan'],
  [{ olay: 'kayit', abone: { aboneAdi: AD, adres: ADRES } }, [AD, ADRES], 'ic ice alan'],
  [{ olay: 'hata', mesaj: `dogrulama basarisiz: ${P}` }, [P], 'serbest metin'],
  [{ olay: 'istek', hedef: `/portal/giris?belirtec=${T}` }, [T], 'baglanti sorgusu'],
  [{ olay: 'toplu', kayitlar: [{ telefon: TEL }, { kimlikNo: KN }] }, [TEL, KN], 'dizi ogesi'],
  [{ olay: 'esitleme', ust: T }, [T], 'listede olmayan ad'],
  [{ olay: 'yenile', baglam: { oturum: { imza: IMZA } } }, [IMZA], 'ucuncu duzey'],
  [{ olay: 'bildirim', aboneAdi: AD, telefon: TEL }, [AD, TEL], 'ust duzey alan'],
];
const ZARARSIZ = ['a1b2c3d4e5f60718293a4b5c6d7e8f90', '9f3c2a1b8d7e6540c9b2a1f0e3d4c5b6'];
const zararsizKayitlar = [{ olay: 'izleme', istekKimligi: ZARARSIZ[0] }, { olay: 'yapi', surum: ZARARSIZ[1] }];

const ADLAR = ['belirtec', 'parola', 'imza', 'kimlikNo', 'telefon', 'aboneAdi', 'adres'];
const BICIM = (d) => typeof d === 'string' && /^[A-Za-z0-9]{20,}$/.test(d);

function maskele(deger, { derin, bicim }, duzey = 0) {
  if (Array.isArray(deger)) return derin ? deger.map((x) => maskele(x, { derin, bicim }, duzey + 1)) : deger;
  if (deger && typeof deger === 'object') {
    if (!derin && duzey > 0) return deger;
    return Object.fromEntries(Object.entries(deger).map(([k, v]) =>
      [k, ADLAR.includes(k) ? '***' : maskele(v, { derin, bicim }, duzey + 1)]));
  }
  return bicim && BICIM(deger) ? '***' : deger;
}

const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));
const KALEM = kayitlar.reduce((a, [, d]) => a + d.length, 0);
const D = [-34, 8, 10, 12, 14];
yaz(D, 'katman', 'kalem', 'kapanan', 'disarida', 'yanlis alarm');

const sonuc = {};
for (const [ad, ayar] of [
  ['katman yok', null],
  ['ad listesi, yalniz ust duzey', { derin: false, bicim: false }],
  ['ad listesi, her duzeyde', { derin: true, bicim: false }],
  ['ad listesi + bicime gore', { derin: true, bicim: true }],
]) {
  let kalan = 0;
  const kalanTur = {};
  for (const [kayit, duyarli, tur] of kayitlar) {
    const metin = JSON.stringify(ayar ? maskele(kayit, ayar) : kayit);
    const k = duyarli.filter((d) => metin.includes(d)).length;
    kalan += k;
    if (k) kalanTur[tur] = (kalanTur[tur] ?? 0) + k;
  }
  const alarm = ayar ? zararsizKayitlar.filter((k) =>
    !JSON.stringify(maskele(k, ayar)).includes(ZARARSIZ[zararsizKayitlar.indexOf(k)])).length : 0;
  sonuc[ad] = kalanTur;
  yaz(D, ad, KALEM, KALEM - kalan, kalan, alarm);
}

console.log('\ndisarida kalan kalemlerin turu:');
for (const [ad, tur] of Object.entries(sonuc)) {
  if (ad === 'katman yok') continue;
  console.log(`  ${ad.padEnd(30)}${Object.entries(tur).map(([t, n]) => `${t} ${n}`).join(', ') || '-'}`);
}
```

```
katman                               kalem   kapanan    disarida  yanlis alarm
katman yok                              11         0          11             0
ad listesi, yalniz ust duzey            11         3           8             0
ad listesi, her duzeyde                 11         8           3             0
ad listesi + bicime gore                11         9           2             2

disarida kalan kalemlerin turu:
  ad listesi, yalniz ust duzey  ic ice alan 2, serbest metin 1, baglanti sorgusu 1, dizi ogesi 2, listede olmayan ad 1, ucuncu duzey 1
  ad listesi, her duzeyde       serbest metin 1, baglanti sorgusu 1, listede olmayan ad 1
  ad listesi + bicime gore      serbest metin 1, baglanti sorgusu 1
```

#### Adı Olmayan Değer

Sekiz günlük kaydında on bir duyarlı kalem var ve bunlar iki ayrı sınıftan geliyor: bir bölümü sır
niteliğinde değerdir — belirteç, parola, imza — bir bölümü kişisel veridir. İki sınıfın kapatma
gerekçesi farklıdır. Sır sızıntısı bir yetkiyi devreder ve döndürmeyle kapanır; kişisel veri
sızıntısı devredilebilir bir şey vermez ama geri alınamaz, çünkü döndürülecek bir karşılığı yoktur.
Aynı maskeleme katmanı ikisini birden örter, dolayısıyla kalem sayımı ikisini ayırmadan yapılabilir;
dışarıda kalan kalemin **hangi sınıftan** olduğu ise önceliklendirmeyi belirler.

İkinci satır çoğu kurulumun başlangıç noktasıdır — bir ad listesi vardır ve yalnızca üst
düzey alanlara bakar. On bir kalemin **üçünü** kapatıyor, sekizi dışarıda kalıyor. Dışarıda
kalanların türleri alt tabloda: iç içe alan iki, dizi öğesi iki, üçüncü düzey bir, serbest metin
bir, bağlantı sorgusu bir, listede olmayan ad bir.

Üçüncü satır tek bir değişikliğin ne kadar getirdiğini gösteriyor: aynı ad listesi her düzeyde ve
dizi içinde de çalıştığında kapanan kalem üçten **sekize** çıkıyor. Yapısal bir düzeltmedir, yeni
bir kural gerektirmez ve tek başına kapsamanın yarısından fazlasını kazandırır. Geriye üç kalem
kalıyor ve üçü de aynı sebepten kalıyor: değerin durduğu yerin bir **alan adı** yok.

Dördüncü satır biçime göre maskelemeyi ekliyor — yirmi karakterden uzun, yalnız harf ve rakamdan
oluşan değerler maskeleniyor. Kapanan kalem dokuza çıkıyor; kazanılan tek kalem "listede olmayan
ad" satırıdır, çünkü değerin adı bilinmese de biçimi tanınıyor. Bedeli aynı satırda yazılı: **iki
yanlış alarm**. İzleme kimliği ve yapı sürüm damgası da aynı biçime uyduğu için maskeleniyor ve
bunlar tam olarak arıza aramak için okunan alanlardır. Maskeleme burada bir güvenlik kazancı
karşılığında bir teşhis kaybı satın alıyor.

Son iki kalem hiçbir düzende kapanmıyor. Biri serbest metin bir hata mesajının içine gömülü bir
parola, öteki bir bağlantının sorgu bölümündeki belirteçtir. İkisinin de bir alan adı yoktur;
biri bir cümlenin parçası, öteki bir dizgenin parçasıdır. Maskeleme katmanı bunları ancak dizge
içeriğini ayrıştırarak bulabilir ve o noktada artık bir maskeleme katmanı değil, bir metin
taramasıdır — kapsaması belirsiz, yanlış alarmı ölçülmemiş bir savunma.

#### Yeni Nokta Eklendiğinde

Üçüncü sütun iki ölçümde iki ayrı yanıt veriyor. Hata yanıtı tarafında kalıp **kendiliğinden
kapsıyor**: yerel yanıt yazımı kapatıldığında yarın eklenecek dokuzuncu uç da merkezî işleyiciden
geçmek zorundadır ve hiçbir karar gerektirmez. Kapsama uç sayısından bağımsız olarak tamdır.

Günlük tarafında ise kalıp kendiliğinden kapsamıyor. Her düzeyde çalışan ad listesi yeni bir
**kaydı** kapsar, ama yeni bir **alan adını** kapsamaz: yarın günlüğe yazılacak duyarlı bir alan,
adı listede yoksa görünmeye devam eder. Bu dersin ölçümünde bunun karşılığı "listede olmayan ad"
satırıdır ve biçime göre maskeleme onu ancak biçim tanınırsa yakalar. İki savunmanın farkı
buradadır: biri bir **yolu** kapatır, öteki bir **listeyi** günceller. Yol kapatan savunma yeni
noktayı kendiliğinden kapsar, liste güncelleyen savunma her yeni noktada bir kez daha
hatırlanmayı bekler.

#### Özet

- Sekiz ucun her biri beş iç bilgi kalemi taşıyor; hata nesnesi doğrudan yanıta yazıldığında
  dışarı çıkan kalem-yanıt çifti 40.
- Merkezî hata işleyici altı ucu örtüyor, kendi yanıtını yazan iki uç dışarıda kalıyor ve on çift
  ayakta duruyor; yerel yanıt yazımı kapatıldığında sayı sıfıra iniyor.
- Sekiz günlük kaydında on bir duyarlı kalem var; yalnız üst düzeye bakan ad listesi üçünü,
  her düzeyde ve dizi içinde çalışan aynı liste sekizini kapatıyor.
- Biçime göre maskeleme kapsamayı dokuza çıkarıyor ve iki yanlış alarm üretiyor; maskelenen iki
  değer arıza aramak için okunan izleme kimliği ile sürüm damgasıdır.
- Serbest metne gömülü değer ile bağlantı sorgusundaki değer hiçbir düzende kapanmıyor, çünkü
  ikisinin de bir alan adı yok.
- Yeni uç merkezî işleyici zorunluyken kendiliğinden kapsanıyor; yeni günlük alanı ise ad listesi
  güncellenene kadar kapsanmıyor.

#### Sonraki Adım

Bu ders sürecin dışarı konuştuğu noktaları saydı ve hepsinde ölçülen şey **kendi yazdığımız kodun**
davranışıydı: hangi satır yığıt izini yanıta koyuyor, hangi alan maskeleniyor. Oysa bir isteği
karşılayan kodun büyük bölümü ekibin yazdığı satırlar değildir. Aynı süreçte onlarca bileşen
yüklüdür, her biri başka bileşenleri çeker ve hiçbirinin adı bizim yazdığımız bir yerde geçmez.
Sonraki ders bu kümeyi bir çizge olarak ele alır: bağımlılıkların geçişli derinliği ne, tek bir
bileşen kaç paketi etkiliyor ve hiç çağrılmayan bir bileşendeki açık neden başka türlü
önceliklendirilir.

### Bağımlılık Kaynaklı Riskler

Source: https://academia.sh/tr/kurslar/guvenli-kodlama/bagimlilik-kaynakli-riskler

Bağımlılık çizgesinin kapsama ölçüsü: bildirim dosyasında sekiz ad geçerken süreçte otuz dört paketin bulunması, geçişli derinliğin altıya çıkması, tek bir bileşenin yirmi paketi etkilemesi, sekiz doğrudan bağımlılığın üçünün hiç çağrılmaması ve aynı önem derecesiyle gelen altı açık kaydının dördünün erişilebilir, ikisinin erişilmeyen bileşende olması.

Önceki ders sürecin dışarı konuştuğu noktaları saydı ve hepsinde ölçülen şey kendi yazdığımız
kodun davranışıydı: hangi satır yığıt izini yanıta koyuyor, hangi alan maskeleniyor. Oysa bir
isteği karşılayan kodun büyük bölümü ekibin yazdığı satırlar değildir. Portal servisinin süreci
ayağa kalktığında onlarca bileşen yüklenir; bir bölümü bir bildirim dosyasında adıyla geçer, geri
kalanı yalnızca başka bir bileşenin bağımlılığı olduğu için gelir.

Bağımlılık Riski dersi bu kümeyi bir yapılandırma kararı olarak ele almıştı: yükseltme politikası
ne kadar açık gün biriktiriyor, kaç sürüm kırıyor. Burada politika hiç konuşulmaz. Ölçülen şey
çizgenin **kendi yapısıdır** — kaç bileşen var, ne kadar uzakta duruyorlar, biri düştüğünde kaçı
etkileniyor ve kaçı süreçte yüklü olduğu hâlde hiç çağrılmıyor.

#### Bağımlılık Çizgesi

**KI13 — bildirim dosyasında adı geçen bileşen sayısı, süreçte yüklü olan bileşen sayısını
vermez**; ikincisi geçişli kapanıştır ve ayrı bir niceliktir. **KI14 — bir bileşenin etkilediği
alan, ona geçişli olarak bağlı olan paket sayısıyla ölçülür ve derinlikten bağımsızdır.**
**KI15 — yüklü olmak çağrılmak değildir**; kod yolundan erişilmeyen bir bileşen çizgede durur,
süreçte yer kaplar ve hiç çalışmaz.

```js
// bagimlilik/cizge.mjs — kurgu olcum agi portal servisinin bagimlilik cizgesi (model).
// Sekiz bilesen bildirim dosyasinda adiyla gecer; kalanlar gecisli olarak gelir. Kenarlar
// kendi uretecimizle uretilir, tohum gorunur. Paket adlari kurgudur (p01, p02, ...).
const TOHUM = 91237;
let d = TOHUM;
const rast = () => (d = (d * 1103515245 + 12345) % 2 ** 31) / 2 ** 31;

const N = 44, DOGRUDAN = 8;
const ad = (i) => `p${String(i).padStart(2, '0')}`;
const kenar = new Map(Array.from({ length: N }, (_, i) => [i, []]));
kenar.set(-1, Array.from({ length: DOGRUDAN }, (_, i) => i));   // -1 = bizim kodumuz
for (let i = 0; i < N; i++) {
  const sayi = i < DOGRUDAN ? 1 + Math.floor(rast() * 4) : Math.floor(rast() * 3);
  for (let k = 0; k < sayi; k++) {
    const hedef = DOGRUDAN + Math.floor(rast() * (N - DOGRUDAN));
    if (hedef !== i && !kenar.get(i).includes(hedef)) kenar.get(i).push(hedef);
  }
}

// Derinlik: bizim kodumuzdan en kisa uzaklik. Hic ulasilmayan paket cizgede yoktur.
const derinlik = new Map();
let sinir = [-1];
for (let seviye = 0; sinir.length; seviye++) {
  const sonraki = [];
  for (const u of sinir) for (const v of kenar.get(u) ?? []) {
    if (!derinlik.has(v)) { derinlik.set(v, seviye + 1); sonraki.push(v); }
  }
  sinir = sonraki;
}
const paketler = [...derinlik.keys()].sort((a, b) => a - b);

// Tek bilesenin etkiledigi paket: ona gecisli olarak bagli olan paket sayisi.
const tersErisim = (hedef) => {
  const gorulen = new Set();
  let degisti = true;
  while (degisti) {
    degisti = false;
    for (const u of paketler) {
      if (gorulen.has(u)) continue;
      if ((kenar.get(u) ?? []).some((v) => v === hedef || gorulen.has(v))) { gorulen.add(u); degisti = true; }
    }
  }
  return gorulen.size;
};

// Kod yolumuz sekiz dogrudan bilesenin besini gercekten cagiriyor; ucu yuklu ama cagrilmiyor.
const CAGRILAN = [0, 1, 3, 4, 6];
const erisilen = new Set();
sinir = [...CAGRILAN];
while (sinir.length) {
  const s = sinir.pop();
  if (erisilen.has(s)) continue;
  erisilen.add(s);
  for (const v of kenar.get(s) ?? []) sinir.push(v);
}

const yaz = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(''));
const dagilim = {};
for (const p of paketler) dagilim[derinlik.get(p)] = (dagilim[derinlik.get(p)] ?? 0) + 1;
console.log(`tohum ${TOHUM}: cizgede ${paketler.length} paket, ${DOGRUDAN} tanesi bildirim `
  + `dosyasinda adiyla geciyor, ${paketler.length - DOGRUDAN} tanesi gecisli`);
console.log(`derinlige gore dagilim: ${Object.entries(dagilim).map(([k, v]) => `${k}: ${v}`).join(', ')}`);
console.log(`en buyuk derinlik ${Math.max(...derinlik.values())}, `
  + `kenar sayisi ${paketler.reduce((a, p) => a + (kenar.get(p) ?? []).length, 0)}`);
console.log(`kod yolumuzdan erisilen paket ${erisilen.size}, yuklu ama erisilmeyen `
  + `${paketler.length - erisilen.size}`);
console.log(`kimsenin secmedigi derinlik: ${paketler.filter((p) => derinlik.get(p) >= 3).length} paket `
  + `uc ve daha uzak, ${paketler.filter((p) => derinlik.get(p) >= 5).length} paket bes ve daha uzak`);

const etki = paketler.map((p) => [p, tersErisim(p)]).sort((a, b) => b[1] - a[1]);
console.log('\nen cok pakete dokunan bes bilesen:');
const D = [-10, 10, 12, 18];
yaz(D, 'paket', 'derinlik', 'etkilenen', 'erisilebilir mi');
for (const [p, n] of etki.slice(0, 5)) {
  yaz(D, ad(p), derinlik.get(p), n, erisilen.has(p) ? 'evet' : 'hayir');
}

// Alti kurgu acik kaydi: hangi pakette oldugu disinda hicbir sey verilmemistir.
const acikPaketleri = [17, 29, 12, 4, 27, 7];   // kurgu kayitlar; yalnizca paket adi verilmistir
console.log('\nalti acik kaydi, ayni onem derecesiyle geldi:');
const E = [-10, 10, 12, 18, 8];
yaz(E, 'paket', 'derinlik', 'etkilenen', 'erisilebilir mi', 'sira');
const sirali = acikPaketleri
  .map((p) => ({ p, e: erisilen.has(p), n: tersErisim(p) }))
  .sort((a, b) => (b.e - a.e) || (b.n - a.n));
sirali.forEach((s, i) => yaz(E, ad(s.p), derinlik.get(s.p), s.n, s.e ? 'evet' : 'hayir', i + 1));
console.log(`erisilebilir pakette ${sirali.filter((s) => s.e).length}, erisilmeyen pakette `
  + `${sirali.filter((s) => !s.e).length}; ikisi de ayni onem derecesiyle geldi`);
```

```
tohum 91237: cizgede 34 paket, 8 tanesi bildirim dosyasinda adiyla geciyor, 26 tanesi gecisli
derinlige gore dagilim: 1: 8, 2: 15, 3: 6, 4: 2, 5: 2, 6: 1
en buyuk derinlik 6, kenar sayisi 44
kod yolumuzdan erisilen paket 28, yuklu ama erisilmeyen 6
kimsenin secmedigi derinlik: 11 paket uc ve daha uzak, 3 paket bes ve daha uzak

en cok pakete dokunan bes bilesen:
paket       derinlik   etkilenen   erisilebilir mi
p17                3          20              evet
p24                3          20              evet
p36                6          19              evet
p10                5          18              evet
p13                2          18              evet

alti acik kaydi, ayni onem derecesiyle geldi:
paket       derinlik   etkilenen   erisilebilir mi    sira
p17                3          20              evet       1
p29                4          18              evet       2
p12                2           6              evet       3
p04                1           0              evet       4
p27                2           3             hayir       5
p07                1           0             hayir       6
erisilebilir pakette 4, erisilmeyen pakette 2; ikisi de ayni onem derecesiyle geldi
```

#### Kimsenin Seçmediği Derinlik

İlk satır iki sayıyı yan yana koyuyor: bildirim dosyasında **sekiz** ad geçiyor, süreçte **otuz
dört** paket duruyor. Aradaki yirmi altı bileşeni kimse seçmemiştir; hiçbiri bir kararın konusu
olmamış, hiçbirinin adı ekibin yazdığı bir dosyada geçmemiştir. Kapsama açısından bu sayı
belirleyicidir: kod tabanında yapılan her denetim — gözden geçirme, kural, inceleme — sekiz adı
kapsar ve yirmi altısına dokunmaz.

Derinlik dağılımı uzaklığı ayrıntılandırıyor. Birinci düzeyde sekiz, ikincide on beş, üçüncüde altı
paket var; dördüncü, beşinci ve altıncı düzeylerde beş paket daha. **On bir paket üç ve daha uzak**
düzeyde, üçü beş ve daha uzakta. Altıncı düzeydeki bir bileşene ulaşmak için beş ayrı bileşenin
bağımlılık listesini izlemek gerekir; o listelerin hiçbiri bizim değildir ve her biri bağımsız
olarak değişebilir.

Derinliğin kendi başına bir risk ölçüsü olmadığını da yazmak gerekiyor. Derinlik yalnızca **kararı
kimin verdiğini** söyler: birinci düzey bizim kararımız, ikinci düzey doğrudan bağımlılığımızın
kararı, altıncı düzey beş kararın bileşkesidir. Riski ölçen sütun bir sonrakidir.

#### Tek Bileşenin Dokunduğu Alan

İkinci tablo ters yönde bakıyor: bir bileşen kaç paketin bağımlılık zincirinde geçiyor. En üstteki
iki bileşen otuz dört paketin **yirmisini** etkiliyor; birinde bir açık bulunduğunda yirmi paket
birden ilgili hâle geliyor. Üçüncü satır bu ölçünün derinlikten bağımsız olduğunu gösteriyor:
altıncı düzeydeki bileşen on dokuz paketi etkiliyor, yani çizgedeki en uzak bileşen aynı zamanda en
çok dokunanlardan biridir. Uzaklık ile etki birbirini izlemez ve ikisi ayrı ayrı ölçülmelidir.

Çizgenin kenar sayısı bu yığılmanın nereden geldiğini söylüyor: otuz dört paket arasında **kırk
dört** kenar var, yani paket başına ortalama 1,3 çıkan kenar. Sayı küçüktür, ama çizge bir ağaç
değildir — aynı bileşene birden fazla yoldan gelinir. Bir ağaçta bir yaprağın etkilediği paket
sayısı sıfır, kökün etkilediği sayı ise tamamıdır ve arada düzgün bir azalma olur. Paylaşımlı bir
çizgede ortadaki bir düğüm de yirmi pakete dokunabilir; etki sütunundaki yığılma bu paylaşımın
doğrudan sonucudur ve az sayıda kenarla da doğar.

Birinci düzeydeki doğrudan bağımlılıkların etkilenen sayısı sıfırdır; bu bir kusur değil, ölçünün
tanımıdır. Onlara bağlı bir paket yoktur, yalnızca bizim kodumuz vardır. Bir doğrudan
bağımlılıktaki sorun tek bir bileşeni ilgilendirir, derin bir ortak bileşendeki sorun yirmi
bileşeni — ve ikisi aynı kayıt biçiminde, aynı önem derecesiyle gelir. Kaydın kendisi bu farkı
taşımaz; farkı yalnızca çizge verir.

#### Çağrılmayan Bileşendeki Açık

Son ölçüm çizgeye ikinci bir süzgeç ekliyor. Sekiz doğrudan bağımlılığın beşi kod yolumuz
tarafından gerçekten çağrılıyor, üçü yüklü ama çağrılmıyor. Bu üçün geçişli kapanışıyla birlikte
**altı paket** süreçte durup hiç çalışmıyor; erişilen paket sayısı yirmi sekiz.

Altı açık kaydı aynı önem derecesiyle geldiğinde tablo bir sıralama üretiyor ve sıralamayı iki
sütun belirliyor: önce erişilebilirlik, sonra etkilenen paket sayısı. İlk sıradaki kayıt hem
erişilebilir bir bileşende hem de yirmi paketi etkiliyor. Dördüncü sıradaki kayıt da erişilebilir
bir bileşende ama etkilenen paket sayısı sıfır — birinci düzey bir bağımlılık. Beşinci ve altıncı
sıradaki iki kayıt hiç çağrılmayan bileşenlerde; biri üç paketi etkiliyor, öteki hiçbirini.

Erişilmeyen bileşendeki açığın neden farklı önceliklendiğini açıkça yazmak gerekir, çünkü bu ayrım
kolaylıkla yanlış okunur. Erişilmeyen bileşendeki açık **yok sayılmaz**: bileşen süreçte yüklüdür,
yarın bir kod değişikliği onu çağırabilir ve erişilebilirlik ölçümü bizim çizgemize göre yapılmış
bir kestirimdir, kesin bir sonuç değil. Ayrım kapatma kararında değil, kapatma sırasındadır. Altı
kaydın altısı da kapatılır; ilk üçü önce kapatılır, çünkü onların açık kalması bugün çalışan bir
kod yolunu ilgilendirir.

Kapsama açısından asıl sayı şudur: aynı önem derecesiyle gelen altı kaydın **dördü** erişilebilir,
**ikisi** erişilmeyen bileşendedir. Ayrımı yapmayan bir kurulum altı kaydı da eşit görür ve
elindeki inceleme zamanının üçte birini hiç çalışmayan koda harcar. Ayrımı yapan bir kurulum ise
iki soruyu birden yanıtlamak zorunda kalır: hangi bileşen çağrılıyor ve bu ölçüm en son ne zaman
tazelendi. İkinci soru bu kursun kapsamının dışındadır.

#### Özet

- Bildirim dosyasında sekiz ad geçerken süreçte otuz dört paket duruyor; yirmi altı bileşen hiçbir
  kararın konusu olmamıştır ve kod tabanındaki hiçbir denetim onlara dokunmaz.
- Derinlik dağılımı birinci düzeyde sekiz, ikincide on beş paket veriyor; on bir paket üç ve daha
  uzak, üçü beş ve daha uzak düzeyde. Derinlik riski değil, kararı kimin verdiğini ölçer.
- Tek bir bileşen otuz dört paketin yirmisini etkiliyor ve altıncı düzeydeki bileşen on dokuzunu;
  uzaklık ile etki birbirini izlemez.
- Sekiz doğrudan bağımlılığın üçü yüklü ama çağrılmıyor; geçişli kapanışıyla birlikte altı paket
  süreçte durup hiç çalışmıyor.
- Aynı önem derecesiyle gelen altı açık kaydının dördü erişilebilir, ikisi erişilmeyen bileşende;
  ayrım kapatma kararını değil, kapatma sırasını belirler.

#### Kurs Kapanışı

| Ders | Kaç nokta | Kalıbın örttüğü | Dışarıda kalan |
|---|---|---|---|
| Girdi Doğrulama Kalıpları | 15 alan noktası | izin listesi 15 | 0 nokta (ret listesi 14) |
| Çıktı Kodlama | 14 çıkış noktası | bildirimden 14 | 0 nokta (dağınık 5) |
| Enjeksiyon Savunması | 16 sorgu noktası | parametreli 11 | 5 kenar akış |
| Şablon ve İfade Enjeksiyonu | 12 şablon, 65 ad | dar ortam 40 ad | 25 ad, 5 şablon |
| Dosya Yükleme Güvenliği | 5 yükleme noktası | merkezî 5 nokta | 4 nokta, 10 katman |
| Sunucu Tarafı İstek Sahteciliği | 12 giden hedef | adres kısıtı 8 | 0 hedef (ad listesi 3) |
| Güvensiz Seri Durumdan Çıkarma | 9 tür, 11 düğüm | izin listesi 5 tür | 0 yan etkili (açık 4) |
| Kimlik Doğrulama Gerçekleştirim Hataları | 8 nokta, 3 kanal | merkezî doğrulayıcı 8 | 0 nokta (dağıtık 5) |
| Oturum Yönetimi Hataları | 7 akış | 3 akış | 4 akış |
| Bozuk Erişim Denetimi | 14 uç | 9 uç | 5 uç |
| İş Mantığı Açıkları | 625 yol | durum makinesi 625 | 0 yol (adım başına 501) |
| Yarış Koşulları | 7 kontrol noktası | 3 nokta | 4 nokta, 27 fazla geçiş |
| Kriptografi Kötüye Kullanımı | 14 çağrı noktası | 7 nokta | 7 nokta, 5 kusurlu |
| Rastgelelik | 9 belirteç noktası | 3 nokta | 6 nokta, 5 sonuçlu |
| Kodda Gömülü Sırlar | 5 sır, 60 işlem | desen taraması 4 | 1 sır, 198 açık işlem |
| Hata Mesajları ve Günlük Sızıntısı | 8 uç, 11 kalem | 6 uç, 9 kalem | 2 uç, 2 kalem |
| Bağımlılık Kaynaklı Riskler | 34 paket | 8 doğrudan ad | 26 geçişli, 6 erişilmeyen |

Tablo aynı soruyu on yedi kez soruyor ve on yedi kez farklı bir birimle yanıtlıyor: alan noktası,
çıkış noktası, sorgu noktası, şablon, yükleme noktası, giden hedef, akış, uç, yol, kontrol noktası,
çağrı noktası, sır, günlük kalemi, paket. Birimlerin farklı olması ölçünün değişmesi değildir —
üçüncü sütun her satırda aynı şeyi söyler: bugün savunmasız olan değil, **yarın savunmasız
olabilecek** nokta sayısı.

Orta sütun da tek başına okunmamalıdır. Bazı satırlarda kalıbın örttüğü sayı ilk sütuna eşittir —
izin listesi on beş alan noktasının on beşini, durum makinesi 625 yolun 625'ini örter. Bazı
satırlarda ise eşit değildir ve fark, kalıbın kapsamadığı bir nokta sınıfını gösterir: parametreli
arayüz on altı sorgu noktasının on birini örter, çünkü beş kenar akış onu hiç çağırmaz. İlk
durumda savunma bir yol kapatmıştır, ikincisinde bir seçenek sunmuştur.

Kursun kuralı da buradan okunur. Güvenli kodlama bir kural listesi değil, bir kalıbın kaç noktayı
**kendiliğinden** kapattığıdır. Tablonun üçüncü sütununda sıfır yazan satırların hiçbirinde kural
sayısı fazla değildi; hepsinde savunma, noktanın dışına çıkmanın mümkün olmadığı bir yere
konmuştu — tür çizelgesine, bağlam bildirimine, parametreli arayüze, durum makinesine, tek yazarlı
kuyruğa, tek dosyaya kapatılmış ilkel çağrıya. Sıfır olmayan satırlarda ise savunma doğru yazılmıştı
ama her yeni noktada elden geçmesi gerekiyordu. **Her yeni noktada elle tekrarlanması gereken
savunma, eninde sonunda unutulacak savunmadır**; tablonun dışarıda kalan sütunu o unutmanın
bugünkü sayısıdır.

Bu kursun sormadığı iki soru var. Birincisi, bütün bu kapsamayı **her değişiklikte kimin
sayacağıdır**: buradaki on yedi ölçüm elle kurulmuş envanterler üzerinde koştu ve envanterin
kendisi kod değiştikçe eskir. İkincisi, ölçümlerin tamamının kod tabanının içinde kalmasıdır. Son
dersin çizgesi bunun sınırını gösterdi: yirmi altı bileşen bizim yazmadığımız satırlardan gelir ve
bir kod incelemesi onlara ulaşmaz. Aynı şey yapının çıktısı, dağıtılan imaj ve o imajın içine giren
her katman için de geçerlidir; hiçbiri bu kursta sayılmadı. **M25/K03 Güvenlik Otomasyonu ve Tedarik
Zinciri** bu iki soruyu birlikte ele alır: kapsamayı her değişiklikte kim sayar ve kodun dışında
kalan katman nasıl ölçülür.

