CHANGELOG

Sürüm Notları

Framework'ün sürüm tarihli değişiklik kaydı. Toplam 8 sürüm.

v0.9.7 güncelleme: 2026-10-14
v0.9.1

Kısa özet — canlı geçişte bulunan 4 düzeltme: Bu sürüm, canlı geçiş sırasında 4 gerçek framework hatasının bulunmasıyla çıktı. Dört düzeltme de canlı yüklemesi sonrası ortaya çıktı. Kırıcı değişiklik yok: hiçbir imza, şema, izin veya metot imzası değişmedi; güvenlik sınırları (kök sınır fail-closed davranışı dahil) aynen duruyor. Her madde kendi birim testiyle önce kırmızı, sonra yeşil doğrulandı.

Fixed

  • Dış varlık vekili (asset proxy) beyaz listesi artık proje ayarından okunuyor (AssetController). Dış CDN hedefleri (media/<base64> dalı) beyaz listede olmadığı için güvenlik gereği reddediliyor, Location üretilmiyor ve istek 404 ile bitiyordu. Beyaz listenin tek kaynağı framework sabitiydi; motor genel kalsın diye sabit boş bir varsayılan olarak bırakıldı ve ikinci kaynak olarak aktif projenin ayarı (project-settings.proxy_allowed_hosts) okunmaya başlandı — host listesi framework'e değil, projenin kendi ayar dosyasına yazılır. Eşleşme kuralı değişmedi: tam host veya . son ek. Güvenlik kontrolleri gevşetilmedi: şema beyaz listesi (yalnız http/https), kontrol karakteri/CRLF ve ters bölü normalizasyonu aynen durur, reddedilen hedef Location üretmez (fail-closed). Geçersiz ayar girdisi (dizi olmayan, şema/port/yol/boşluk/kontrol karakteri içeren eleman) sessizce yok sayılır; ayar okunamazsa liste boş kalır.

  • Büyük/küçük harfe duyarlı yollar artık küçük harf 301'ine uğramıyor (RedirectManager::forceLowercaseUrl()): /media/<base64>/<dosya> ve /fw-proxy/<upload|export>/<Dosya> gibi opak (base64) segment taşıyan yollar, istek öncesinde küçük harfe çevrilip 301 yazılıyordu; hedef çözülemediği için istek 404 ile bitiyordu (canlı: 301 → 404). Muafiyet listesi tek yerde, değişmez bir sabitte toplandı (LOWERCASE_EXEMPT_PREFIXES) ve ön ek sınırı denetimi eklendi: /media muafken ön ek-benzeri /media-arsivi muaf değildir; böylece normal sayfa yollarındaki SEO kanonik küçük harf davranışı korunur.

  • ViewEngine::path('module', ...) artık hiçbir @import('module', ...) ça�Yrısını reddetmiyor (KRİTİK). path() içindeki array_shift($args) ifadesi PHP'de REFERANSLA çalı�Ytı�Yı için $args dizisini kalıcı olarak kısaltıyordu; bir sonraki satırda $baseArgs = $args zaten kısaltılmı�Y diziyi kopyaladı�Yı için array_shift($baseArgs) dosya yolunu "modül adı" sanıyordu. Böylece kök sınırı kontrolü (guardRoots) izin kökü olarak modülün K�-K�o yerine görünür bir dosya yolu alıyor ve her @import('module', ...) ça�Yrısı fail-closed reddediliyordu. Sonuç: HTTP 200 ama View::__toStringin yuttu�Yu içerik olarak [Render Error] gövdesi. Artık modül adı ve kalan yol path() ba�Yında �-NCEDEN ayrılır, $args hiç kı�Ylanmaz (kısaltılmaz) ve iki match aynı de�Yerleri kullanır. Kök sınırı fail-closed KALIR: .. ile kök dı�Yına çıkan yol reddedilir, kapsam daralmadı. Yan hata olarak tek/argümansız ça�Yrı, array_shiftin bo�Y dizide null döndürmesi nedeniyle Paths::module(null) TipHatası fırlatıyordu; o da kapandı. Metot imzası DEĞİŞMEDİ.

  • Yerel giri�Y noktası (domains/<site>/index.php) PHP sürüm kapısını gerçekten açar (20 dosya). Sürüm kapısı blo�Yu, yolu $rbnKapsiAdayi de�Yi�Ykenine yazıyor ama is_file($rbnKapisiAdayi) ile kontrol ediyordu; is_file(null) false döndü�Yü için PhpVersionGate hiç bulunamıyor ve sessizce devre dı�Yı kalıyordu (geli�Ytirme ortamında ayrıca her istek sayfaya Warning/Deprecated metni basıyordu). De�Yi�Yken adı 20 dosyada $rbnKapisiAdayi olarak düzeltildi; dosyaların kodlaması ve geri kalan içeri�Yi bayt-bayt de�Yi�Ymedi. Yerel PHP 8.3.35 ile kap geçer ve tüm yerel siteler 200 + temiz gövde verir.

Added

  • Sürümleme: proje sürümünün TEK kaynağı master veritabanı; rbn version:check ve rbn version:next komutları. Proje sahibi kararı (2026-10-05): APP_VERSION projenin sürümüdür ve proje sürümü project-settings.php'den GELMEZ, hiçbir yerel dosyaya da yazılmaz — tek kaynak master DB projects.version kolonudur; değer açılışta proje veri önbelleğine yazılır (ProjectDataMapper → project_data('version')) ve her yer oradan okur.

    • Yeni Core\Support\Bridges\Helpers\Library\ProjectVersionResolver: TEK çözücü. Geçerli A.B.C değeri aynen döner; değer yoksa veya bozuksa (iki parçalı 1.0, dört parça, v öneki, 1.17.0 gibi) standart başlangıç sürümü Version::initial() (0.1.1) döner. Bozuk kayıt görüldüğünde LogThrottle::once() ile saatte bir uyarı yazılır. Sınıf durumsuz ve tamamen statiktir (new kullanılmaz); istisna fırlatmaz, karar değişmez.
    • app_version(), SeoResolver (module-version meta), AuthHandler (siteVersion) ve RbnAdminController (stats.app_version) aynı çözücüyü kullanır. Tekrar eden iki parçalı 1.0 varsayılanları kaldırıldı.
    • PreBoot: define('APP_VERSION', '3.5.0') kalıntısı kaldırıldı. Zamanlama gerekçesi koddan okunarak yazıldı: detectEnvironment() orchestrate() içinde 0. adımda çalışır, proje veri önbelleği ise 0.5. adımda (ProjectDiscovery) dolar — yani eski noktada projects.version okunamazdı. Yeni PreBoot::defineAppVersion() initPaths() sonrasında çağrılır. Giriş noktasındaki elle define('APP_VERSION', '1.17.0') satırı da kaldırıldı (1.17.0 kural ihlaliydi: B basamağı 17 > 9).
    • Bileşen sürümleri üç parçalı ve v ön eki olmadan: FRAMEWORK_CLI_VERSION 2.3 → 2.3.0, SHIELD_VERSION v2.1 → 2.1.0, ADMIN_VERSION 1.2 → 1.2.0, AUTH_VERSION 2.2 → 2.2.0. Gösterim yerleri (PanelHandler, AuthHandler, sidebar.rbn.php, RbnCli başlığı) sabitleri okuduğu için elle değiştirilmedi. FrameworkIdentity::FRAMEWORK_VERSION (0.9.1) değiştirilmedi.
    • applications sürüm kapısı (şema değiştirilmedi): MasterApplicationRepository::assertVersion() yazma sırasında Version::isValid() kontrolü yapar ve geçersiz değeri InvalidArgumentException ile açık hata olarak reddeder; MasterApplicationsService::versionGate() okuma sırasında geçersiz sürümlü kaydı getByAppKey() için null, list() için listeden çıkar (saatte bir uyarı). Panel bozulmaz, "sessizce yanlış sürüm" durumu oluşmaz.
    • rbn version:check — salt-okunur denetim; tablo + çıkış kodu (0 temiz, 1 sapma). FRAMEWORK_VERSION, dört bileşen sürümü, CITATION.cff, CHANGELOG Son sürüm başlığı ve master DB'deki proje/uygulama sürümlerini karşılaştırır. rbn version:next <project_key> — sürümü Version::next() ile hesaplar; varsayılan kuru koşu, --apply ile yazar (yazma yalnız repository üzerinden, Anayasa §8).
    • Veri geçişi: yeni Core\Database\Migrations\Master\NormalizeProjectVersions (rbn master:migrate, yalnız elle). Önce projects_version_backup tablosuna yedek alır, sonra tüm projeleri başlangıç sürümü 0.1.1'e çeker; karar tek çözücüden (ProjectVersionResolver) gelir. down() yedekteki değerleri geri koyar, yedek tablo silinmez. projects şeması değiştirilmedi (kolon tipi ve DEFAULT '1.0' aynen durur); yerel ölçüm: 19 proje, hepsi 1.0 (geçersiz) → 19 satır 0.1.1; geri alma denendi ve 19 satır 1.0'a döndü. applications tablosu bu migration'a dokunmaz.
    • MasterProjectsService artık proje kaydında 'version' => '1.0' yazmaz; Version::initial() yazar.
  • Marka varlıkları için TEK adlandırma kuralı ve tek çözücü (Core\Support\Definitions\Render\AssetConvention). Kural: favicon = images/favicon-<project_key>.{svg,png,ico}, OpenGraph görseli = images/og-image-<project_key>.{png,jpg,webp}. Motor geneldir: framework'e proje/müşteri adı yazılmaz, <project_key> çalışma anında gelir (Anayusa §9). Aday listesi ve öncelik sırası artık tek yerde; dört kapı (SeoResolver meta etiketleri, AssetController sanal /project-assets/<ad> servisi, RedirectManager::selfHealingAssets (/favicon.ico → gerçek dosya), SchemaResolver::resolveDefaultImage (JSON-LD image)) aynı çözücüyü çağırır — yinelenen mantık yoktur.

  • Sürümleme kuralı için standart sayaç yardımcısı (Core\Support\Bridges\Helpers\Library\Version + global fonksiyonlar). Proje sahibi kuralı (.agents/rules/versioning.md): biçim A.B.C, birinci basamak sınırsız (9 → 10, 11…), ikinci ve üçüncü basamak yalnız 0…9 (asla 10), başlangıç 0.1.1, geçerli desen ^(0|[1-9][0-9]*)\.[0-9]\.[0-9]$. Sürüm artık elle sayı yazılarak değil next() ile hesaplanır: 0.1.1 → 0.1.2, 0.7.9 → 0.8.0, 0.9.9 → 1.0.0, 9.9.9 → 10.0.0, 99.9.9 → 100.0.0. Metotlar: isValid(), parse(), next(), compare(), initial(); geçersiz girdide InvalidArgumentException. Sınıf durumsuz ve tamamen statiktir (metot adları İngilizce, Anayasa §10); new kullanılmaz (§1) ve framework'e proje adı yazılmaz (§9). Global eşleyiciler next_version(), version_is_valid(), version_compare(), version_parse(), version_initial() yalnızca sınıfı çağırır — yinelenen mantık yoktur. 2000 adımlık zincirde hiçbir adım geçersiz sürüm üretmiyor ve çıktı kesin artıyor. Davranış değişikliği yoktur: mevcut hiçbir sürüm sabiti değiştirilmedi.

Fixed

  • og:image meta etiketi artık ölü adres üretmiyor. Ölçülen kök hata: SeoResolver::getVirtualResourceRaw() yalnız favicon-*/logo-* çözüyordu ve AssetController sanal kaynak dalını yalnız .svg uçlantısında açıyordu; raster og görseli fiziksel keşfe düşüyor, dosya images/ altında olduğu için istek Asset not found ile bitiyordu. Altı sitede ölçüldü (og:image 404/500 → sosyal paylaşım önizlemesi görselsiz). Artık sanal og-image-<key>.<png|jpg|webp> adı doğru Content-Type ile servis edilir (yol güvenliği AssetConvention::resolveVirtualOgImage() içinde: .., mutlak yol, ters bölü ve kök dışına çıkış reddedilir — AssetController'un mevcut güvenlik sertliğiyle aynı).

  • /favicon.ico yönlendirmesi artık var olmayan uzantıya gitmiyor. selfHealingAssets favicon hedefini her zaman .png yazıyordu; favicon-<key>.svg olan projede /favicon.ico → 301 → /images/favicon-<key>.png → 404 ölçüldü. Hedef artık projenin gerçek favicon dosyasıdır (aynı çözücü), bulunamazsa framework'ün nötr varlığına gidilir.

  • og üzerinde iki kaynak/sapma kapandı. SchemaResolver::resolveDefaultImage() sabit /images/og-image.png ve project-routemap og_image anahtarını okuyordu (og_image anahtarı projelerde yazılı değil, yalnız og-image yazılı — hiç eşleşmediği için sabit ad kullanılıyordu); SchemaPreset de ikinci bir sabit yazıyordu. Artık üçü de aynı çözücüyü kullanır.

  • Dosyası olmayan projede uydurma görsel adresi üretilmiyor. Projede kurala uyan (ve geriye uyum adı da) hiçbir og görseli yoksa og:image meta etiketi hiç üretilmez ve JSON-LD'de "image":"" yerine alan tamamen düşer (SchemaBuilder). /og-image.png standart yolu da uydurma adrese yönlenmez.

Deprecated

  • Anahtarsız eski varlık adları (images/favicon.png|.svg|.ico, images/og-image.png|.jpg|.webp) "kullanımdan kalma" adımındadır; canlıda çalıştıkları için "son geri dönüş" olarak korunmuştur. Yeni dosyalar için yalnız kural adı kullanılmalıdır. Sıra: kural adı → eski adlar (yalnız kural bulunamazsa). Aynı şekilde project-routemap.php içindeki favicon, og-image/og_image anahtarları tolere edilir ama artık öncelikli değildir; proje sahibi bu anahtarları routemap'ten silmek istiyor — bu görevde routemap dosyalarına dokunulmadı, kaldırma kararı proje sahibine aittir.

Changed

  • project-routemap.php artık panel ön eki için TEK kaynak; project-settings.php içindeki has_route_map ve dashboard_prefix anahtarları motor tarafından artık OKUNMAZ. (Proje sahibi kararı 05.10.2026; kırıcı değil.) İki düzeltme:

    1. Config.php — has_route_map kapısı KALDIRILDI. project-settings.php yüklenirken, aktif project_key için project-routemap.php view_mapping[<key>] değerleri her zaman project-settings üstüne birleşir. Önceden birleştirme yalnızca has_route_map anahtarı doğruysa çalışıyordu; anahtar yoksa view_mapping hiç uygulanmıyordu — bu yüzden canlıda has_route_map yazılmayan projelerde (ornek-proje-1, ornek-proje-2) ayarların siteye özel kısımları hiç okunmuyordu. Birleştirmenin gövdesi, getRouteConfig($activeKey) çağrısı ve yazma sırası değiştirilmedi; yalnızca && !empty(...['has_route_map']) koşulu kalktı.

    2. SystemGuardHandler — bakım modu panel ön eki project-settings'ten okunmuyor. Birincil kaynak project_data('dashboard_prefix', '') = project-routemap.php → ProjectDataMapper::assemble() → BootCache → project_data() yolu (bu yol zaten kapısızdı); kaynak boşsa RouteBlueprint::DASHBOARD_PREFIX (dashboard) devreye girer. Config::get('project-settings.dashboard_prefix') geriye uyum yedeği olarak silindi. Muafiyet listesi (RouteBlueprint::SYSTEM_ALLOWED_PATHS), S-1 yol ayrıştırma koruması ve S-2 boş-IP koruması aynen duruyor.

    Geriye uyum (canlı dosyalar bozulmaz): project-settings.php içinde has_route_map veya dashboard_prefix kalsa bile motor çalışır; bu iki anahtar artık yalnızca YOK SAYILIR, hiçbir davranışı etkilemez. Yani canlı dosyaları silmek zorunlu değildir — silmek isteyenler temizlik için yapabilir. Kaldırılırsa panel ön eki routemap'te tanımlı olmalıdır; routemap'te yoksa RouteBlueprint::DASHBOARD_PREFIX (dashboard) uygulanır.

Added

  • RbnCommon CSS motoru eksik siniflarla tamamlandi (gorsel degisiklik yok, yeni kural yok). rbn-master.css kapanisindaki on eksiz (katman B) yardimci sinif katmani yarim birakilmis; motor Bootstrap'in islevlerinin tamamini kendi var(--rbn-*) token'lariyla sunmuyordu. Kapsanan isler: (1) .rbn-empty-state — her liste/tablo/panel ekraninda yapisal olarak bulunabilen bos-durum blogu; "hic kayit yok" ve "filtre sonucu yok" icerik modlari ayni sinifla, farkli icerikle ayrilir. (2) .rbn-drawer-right + .rbn-drawer-body / .rbn-drawer-title — sag ray 992px altinda gizlenir ve mevcut overlay drawer'a devreder; tetikleyici icin yeni bir toggle sinifi yazilmadi, mevcut .rbn-btn + [data-rbn-drawer] kullanilir. (3) .rbn-card-media — yalniz yuzey + oran tanimlar (kenarlik yok, ust koseler yuvarlak, alt koseler 0); gorsel view'da gercek <img> olarak yazilir; oran 16/9 kilitlidir, -portrait (4/5) ve -square (1/1) varyantlari vardir; .rbn-card-subtitle / -text / -link ve object-fit-* yardimcilari eklendi. (4) Yeni core/utilities.css (katman B): tek yonlu bosluk (pt/pb/ps/pe), mx/my, sifir resetleri, negatif bosluk, overflow-*, z-*, order-*, sizing, visually-hidden, tipografi, align-self-*, konum/kenar, gx/gy/row-gap/column-gap. (5) Form: .input-group / .input-group-text / -sm / -lg ve .form-control-sm/lg, .form-select-sm/lg. (6) Renk ve sinir: rounded ailesi, border-1..5 ve kenar/sifir varyantlari, border-opacity-*, opacity-*, eksik bg-* / text-* renkleri. Uyum: sayi olcegi motorun mevcut olcegiyle birebir ayni tutuldu (p-3 = 16px gibi), boylece class="p-3 pt-5" tutarli calisir; tum degerler mevcut var(--rbn-*) token'larindan okunur, yeni renk ve yeni token tanimlanmadi. Mevcut siniflarin hesaplanan stilleri dogrulandi: 22 temsilci sinifin getComputedStyle ciktisi degismedi; duman testi REGRESYON=0, 6 yerel site 200, kabul kosusu KIRMIZI 0 / YESIL 125 / ELLE 7 / HATA 0. Framework surumu degistirilmedi.

  • Proje veritabanı bilgisi artık project-settings.php içinde İKİ profille durabilir: DB_PROFILES. Yeni çözücü ProjectDbProfileResolver (Core/System/Config/Engine/Database/) DB_PROFILES.local / DB_PROFILES.production bloklarından çalıştığı ortama göre birini seçer. Geriye uyumlu: DB_PROFILES yoksa eski düz DB_* anahtarları aynen çalışır (kırıcı değişiklik yok, mevcut canlı dosyaları etkilenmez). Dört okuyucu — DatabaseConfig::fromRaw(), DatabaseGuardProvider::loadCredentials(), ProjectDataMapper::buildProjectPdo() ve ProjectCleanupJob — artık tek çözücüyü kullanır; ikinci bir profil seçme kodu yazılmadı.

    Ortam kararı sunucunun kimliğine bakar, isteğe değil: ortam değişkeni RBN_DB_PROFILE (local/production; başka değer "karar yok" sayılır) → framework kökündeki tam localhost yol segmenti. Çalışılan dizin (getcwd()), alan adı ve REMOTE_ADDR/Host başlığı bilerek kullanılmaz: is_local() bir tarayıcıya dönük yardımcıdır (istek kimliğine bakar) ve veritabanı seçimine temel yapılırsa aynı sunucuda istek kimliğine göre farklı veritabanlarına bağlanılırdı. Belirsizlik her zaman production sayılır (fail-closed).

    Fail-closed: seçilen profil yoksa, profil içinde zorunlu anahtar (DB_HOST/DB_NAME/DB_USER/DB_PASS/DB_CHARSET) eksikse ya da bir değer __DOLDUR__ yer tutucusuysa AÇIK hata verilir; motor diğer profile ya da root/boş parolaya sessizce düşmez. Hata metni yalnızca anahtar/profil adı yazar, sır değeri yazmaz. RBN_DB_PROFILE ortam değişkeni EnvKeys::RBN_DB_PROFILE olarak kayıtlıdır.

  • Kiracı kapsamının istisnası artık modele taşındı: BaseModel::$projectScopeIncludes. QueryModelTrait içinde cm_sys_ip_blocks tablo adı sabiti gömülüydü; tek bir tablo için yazılmış istisna büyüdükçe ikinci bir tablo eklemek kabuğu düzenlemek olurdu. Artık istisna modelin kendi dosyasında beyan edilir (CmSysIpBlocksModel → ['GLOBAL']). Kural kabukta tek: varsayılan [], bildirilen dizi varsa kapsam project_key IN (aktif, …istisnalar), yoksa project_key = aktif. Kapsam süzgeci artık tablo adı ile nitelikli yazılır (z_settings.project_key); JOIN'li sorgularda iki tabloda da project_key varsa MySQL 1052 "ambiguous" vermeden önce bu gerekliydi.

  • Kapsamı açıkça daraltan kapı: BaseModel::withProjectScope(?string $projectKey). Kapsamlı bir modelde elle where('project_key', X) yazmak ikinci kez süzgeç çalıştırır ve iki koşul birbirini yok ederdi. Yeni kapı kapsamı genişletmez, yalnızca istenen anahtara daraltır (fail-closed) ve klon döndürür (kaynak model değişmez). Metot imzaları değişmedi; withoutProjectScope() aynen çalışır.

  • BaseModel::isProjectScoped() ve BaseModel::resolveScopeProjectKey(). Çağıranlar (repository/service/provider) bir hedef modelin kapsamlı olup olmadığını sorgular; kapsamlıysa withProjectScope(), kapsamsızsa eski elle süzgeç yolu kullanılır. Bu ayrım sayesinde kapsamı kapalı modellerde hiçbir davranış değişmedi.

  • Zorunlu token kasası migrasyonu artık depoda. UPGRADING 0.9.0 Adım 5'in her proje veritabanına kod dağıtımından önce uygulanmasını istediği 111-z_user_tokens.sql dosyası repoda bulunmuyordu; tabloyu kuran başka bir yol da yoktu. Dosya rbnframework/Core/Database/Migrations/111-z_user_tokens.sql altına eklendi: CREATE TABLE IF NOT EXISTS (eklemeli, tekrar çalıştırılabilir, mevcut tabloyu/satırı değiştirmez), InnoDB + utf8mb4_unicode_ci, purpose alanı ENUM('email_verification','password_recovery'), token_hash üzerinde UNIQUE uq_user_token_hash, idx_user_purpose_pending (user_id, purpose, used_at) ve idx_expires_at. Kapsam dışıdır: asw_* tabloları, ortak (cm_*) şema ve master şeması (developers, ip_blocks) hiçbir şekilde etkilenmez. Depo kökündeki .gitignore içindeki *.sql (veritabanı dökümü) kuralı framework migrasyonlarını da yutuyordu; migrasyon dizinine yönelik dar bir istisna eklendi.

  • Kapsamlı bağlantı yardımcısı: ConnectionTrait::connectionScoped(string $name, callable $islem). Bağlantıyı geçici olarak başka bir adla çalıştırıp iş bitince finally ile geri alan "kaydet/geri al" deseninin tek yazılışı eklendi. Mevcut connection() imzası değişmedi; yalnız elde kodla birebir aynı davranan yeni bir yardımcı eklendi: geri alma finally içindedir (istisna olsa da aktif bağlantı eski haline döner), istisna yutulmaz, işin dönüş değeri aynen geçer, iç içe kullanılabilir. Master geçişi yapan yardımcı kendi elindeki elle deseni silip bu çağrıya devrediyor; metot silinmedi ve imzası korundu. Kırıcı değildir, şema değişikliği yoktur.

  • RbnCommon CSS motoru iki pakete bolundu ve kalan 306 eksik islev motor dosyalarina tasindi. rbn-master.css TEK bir @import zinciridir ve AssetBundles::STACK_MAP uzerinden her sayfaya gider; bu yuzden yeni islevleri mastera eklemek gzip butcesini patlatiyordu. Karar: **masterin kapanisi degistirilmedi, yeni siniflar istege bagli paketlere** konuldu.

    (1) Yeni paket rbnExtended — optional/rbn-utilities-extended.css (312 secici) + optional/rbn-components-extended.css (153 secici). Bu paket STACK_MAP icine GIRILMEDI: hicbir sayfaya otomatik yuklenmez, yalnizca proje/gorunum acikca assets => ['rbnExtended'] (ViewMap) veya $this->addAsset('rbnExtended') (Controller) dediginde yuklenir. AssetBuilder hic degistirilmedi — collect() zaten bundle adini styles listesine cozuyordu. Kapsanan isler: offset-*, row-cols-*, col-auto / col-{sm,md,lg,xl}-auto, duyarli d-* / flex-* / justify-* / items-* / order-* (sm·md·lg·xl·xxl), g-0, container-md/xl, rounded-sm/xl, shadow-xs/hover, fs-xxs, link-*, *-opacity-10/100, text-*-emphasis, .rbn-visible / .rbn-invisible / .rbn-visually-hidden aliaslari,.is-valid/.was-validated/.rbn-form-feedback-; bilesenlerderbn-btn-{block,info,light,outline-dark,outline-info,outline-light,outline-secondary},rbn-alert-{dark,info,light,heading,link}, form on ekleri (rbn-form-control,-sm/-lg,-plaintext,-color,rbn-form-select,rbn-form-range,rbn-form-text,rbn-form-floating,rbn-input-group-{sm,lg}),rbn-dropdown-{start,end,text,toggle},rbn-modal-{backdrop,dialog,content,header,centered,scrollable},rbn-navbar,rbn-list-,rbn-breadcrumb,rbn-ratio{,-1x1,-16x9,-4x3},rbn-progress-track,rbn-spinner-{sm,grow},rbn-table-{borderless,row-active,responsive},rbn-display-1..6,rbn-tooltip,rbn-popover,rbn-toast-{header,body},rbn-carousel*`.

    (2) core/rbn-auth.css — Resources/Views/RbnAuth/Layouts/auth_header.rbn.php icindeki 12,5 KBlik view-ici<style>blogu (17rbn-auth-*sinifi, 29 hardcoded hex) motor dosyasina tasindi;rbn_core_authpaketi artik bu dosyayi da istiyor. **29 hex yerine mevcut--rbn-*tokenlari ve 20 yeni --rbn-auth-* saydamlik tonu** kullaniliyor. Diger framework viewlarina (Resources/Views/`) dokunulmadi.

    Uyum ve olcu: rbn_master paketinin istedigi dosyalar birebir ayni kaldi, rbn-master.css kapanisi 19 dosya / 47 884 B gzip olarak DEGISMEDI (butce asimi yok). Yeni paketler ayri olculdu: rbn-auth.css 2 682 B gzip, rbnExtended 8 242 B gzip. Dogrulama: mapping JSONundaki 863 hedefin **863u de tanimli (once 306 eksikti), ters uyusmazlik 0; 107 yeni sinif getComputedStyle ile 375/768/1280de olculdu; 44 mevcut sinifin hesaplanan stili 132/132 ayni; login sayfasi 375/768/1280de once-sonra karsilastirmasi 26/26 ayni, PNG farki 0; 4 yerel site ana sayfa + iletisim DOM/computed-style/PNG karsilastirmasi fark 0 (ornek-site-1 ve ornek-proje-1'taki kucuk PNG farklari kod tabanli olmayan sayfa ici canli icerik; ayni kod tabaniyla iki kez calistirildiginde ayni farklar tekrar uretiliyor). Duman testi REGRESYON=0, 6 yerel site 200, kabul kosusu kotuylesmedi, birim testi yeni: fw_css_motor_2_bundle 13/13 yesil. Framework surumu degistirilmedi.**

Changed

  • 13 çok kiracılı modelde kiracı izolasyonu AÇILDI (KIRICI — model bazında ölçülerek). FW-ALTYAPI-1 H'de ölçülen ve tek tek taşınan modeller: FaqsModel, PagesModel, FrontendMenusModel, SettingsModel, SidebarCategoriesModel, SidebarMenusModel, ContentCategoryModel, ContentDraftModel, CronLogsModel, RssSourceModel, RssBlacklistModel (framework) + AAProductModel (ornek-proje-5) + IcerikModel (rbncore) → protected bool $scoped = true. BaseModel::$scoped false olarak kaldı; 68 somut model kapsam dışı kalmaya devam ediyor. Ölçülen etki: ornek-kiraci kiracısı ornek-proje-3 veritabanından ornek-proje-3'in SSS ve sayfa kayıtlarını görebiliyordu; kapsam sonrası göremiyor. Kapsamlı model artık create()/update() ile project_key değerini sunucu bağlamından yazar (B-20) ve okuma süzgecini otomatik uygular. Ayrıntı ve geri alım için UPGRADING.md.

  • project_key yazma yolu artık 'default' sentinelini ASLA yazmıyor. Ölçülen gerçek: yedi yerel veritabanındaki 74 kolonda project_key='default' satırı yoktur; yani bu değer ne okumada işe yarar (her zaman 0 satır) ne yazmada (görünmez, sahipsiz satır). Bağlam çözülemediğinde (getActiveProjectKey() yedeğine düşüldüğünde) kapsamlı model artık hiçbir project_key yazmaz; çağıran bir değer verdiyse o korunur, vermediyse kolon NULL kalır (hiçbir kiracının kapsamına girmez — fail-closed).

  • Kenar çubuğu önbellek anahtarı artık kiracıyı içeriyor. SidebarProvider önbelleği "sidebar_{rol}" anahtarını kullanıyordu; anahtar tüm kiracılarda ortaktı, yani bir projenin paneli başka projenin kenar çubuğunu gösterebiliyordu. Artık "sidebar_{kiraci}_{rol}".

  • Request::form() artık yalnız doğrulanmış alanları döndürür (KIRICI). form() önce array_merge($this->all(), $doğrulanmış) ile tüm istek gövdesini döndürüyor, yani kural dizisinde olmayan alanlar da servise/modele geçebiliyordu; artık dönen dizi yalnız kural dizisinde tanımlı VE gönderilmiş alanları içerir. nullable/optional kuralı olup gönderilmemiş alanlara null yazılmaz (kolon olduğu gibi kalır). Geri açma anahtarı security.form_input_mode (off|log|enforce, varsayılan enforce; bozuk değer fail-closed olarak enforce'a düşer). Kuralsız yazma yolunun açık karşılığı yeni metot Request::rawAll(): alan doğrulaması yapmaz ama form() ile aynı kalkan zincirini (method kontrolü, CSRF, Origin, bot, hız sınırı, dosya güvenliği) çalıştırır. filter() da kendiliğinden daraldı. Ayrıntılı etki listesi ve çağrı envanteri için UPGRADING.md.

  • Açık ham yol: Request::rawAll(). Kural dizisi tanımlamadan yazan çağıranlar artık form([]) yerine rawAll($seçenekler) kullanır; metot RequestInterface sözleşmesine de yazıldı. Güvenlik alanları (ör. parola tekrar doğrulaması) rawAll()'a taşınmaz; onlar kural dizisine (beyaz listeye) alınır.

  • Bilinmeyen veritabanı bağlantı adı artık sessizce kabul edilmiyor (KIRICI). Bağlantı üretilirken tanımlı olmayan bir ad verilirse framework o adı sessizce database_project kümesine eşlemek yerine istisna fırlatır. Gerekçe: çok projeli (çok kiracılı) kurulumda sessizce eşleme, proje A'nın verisinin proje B'nin veritabanına yazılması demekti. database_project / database_master / database_common ve eski default adı aynen çalışır; bağlantı adlarının listesi değişmedi. connection() ve connectionScoped() imzaları değişmedi.

  • Veritabanı bağlantısı değiştirmek için TEK yazım. Bağlantı üretme isteyen tüm sorgu ve yazma yolları tek bir kapsam yardımcısı üzerinden gidiyor: iş bitince aktif bağlantı eski haline döner. Önce doğrudan global aktif bağlantıyı ayarlıyor ve geri almadan bırakıyordu; bir model sorgusu, kendisinden sonra gelen ham sorguların hangi veritabanına gideceğini belirleyebiliyordu. Yalnızca okuma yapan yardımcı çağrıları da artık hiçbir şeyi sabitlemiyor. Davranışları aynı, yan etkileri (kapsam dışı durum sızması) kapandı.

  • Durum yazımı için anlamı açık yeni adlar: setStatus() ve setStatusById(). toggleStatus adı iki katmanda iki farklı sözleşme taşıyordu (model/sağlayıcıda 2. parametre alan adı, serviste istenen değer) ve istenen değer sağlayıcı katmanına giderken düşüyordu. Yeni adlar bu sözleşmeyi tekilleştirir; eski ad silinmedi, imzası değişmedi, @deprecated işaretlendi ve kullanımı saatlik günlüğe düşüyor. Panelde "durum değiştir" isteği artık istenen değeri yazıyor; null değer verildiğinde eski "değiştir" anlamı birebir korunur. Ayrıntı için UPGRADING.md.

Fixed

  • Bakım modu muafiyeti panel ön ekini yanlış kaynaktan okuyordu (SystemGuardHandler). Panel ön eki iki ayrı yerden geliyordu: rotalar ve önbellek tek kaynaktan (Core/Config/project-routemap.php → view_mapping[<project_key>] → ProjectDataMapper → project_data(), bu yolda hiçbir kapı yok) okurken, bakım modu muafiyeti ikinci bir yerden (project-settings.dashboard_prefix) okuyordu. İkisi ayrıştığında bakım modunda panel yolu kilitlenir: rota /rbn altındayken muafiyet /dashboard'a bakardı (veya tersi). Artık sıra tek: önce project_data('dashboard_prefix') (tek kaynak), yoksa project-settings.dashboard_prefix (geriye uyum yedeği korundu), hiçbiri yoksa framework sabiti. Yardımcı function_exists ile korunur (CLI/erken boot). Güvenlik sınırları gevşetilmedi: sorgu parametresi içinde taşınan istisna metinleri, statik varlık istisnaları, sistem izinli yollar ve boş IP'nin VIP sayılmaması aynen durur; kırıcı değişiklik yoktur.

  • UNIQUE (project_key, <slug>) varlığı yanlış ölçülüyordu (veri bütünlüğü). Ölçüm, indeksin varlığını COLUMN_NAME IN ('project_key', <slug>) filtresiyle soruyordu; üç kolonlu bir UNIQUE (ör. project_key, type, slug) indeksi de bu filtreyle iki satır döndürüp "iki kolonlu UNIQUE var" sanılıyordu. Sonuçta iki kolonlu UNIQUE hiçbir tabloya ekleniyordu. TenantKeyMigration artık indeksin tüm kolonlarını okuyup listesini sırayla birebir karşılaştırıyor (kolon sayısı da); ekleyici UNIQUE (project_key, <slug>) ekleyen applyUnique() yalnız çakışma yoksa çalışıyor, çakışmalı tabloyu skipped_conflict durumuyla atlayıp sayısını raporluyor. Kırıcı değildir; mevcut indekslere dokunmaz, yalnız eksik olanı ekler (revertUnique() yalnız kendi ürettiği ada sahip indeksi düşürür).

  • GeoIP hata yolu sessizdi. Coğrafi konum sağlayıcısına ulaşılamadığında, yanıt beklenenden büyük geldiğinde ya da istisna oluştuğunda istek düşürülmeden ülke bilgisi atlanıyordu ama hiçbir kayıt bırakılmıyordu. Artık bu yollar kısaltılmış (15 dakikada bir tek) bir warning kaydı yazıyor. Dönen değer değişmedi: yine BILINMEYEN_ULKE ('XX'); hiçbir koşulda boş değer dönmez, istisna yeniden fırlatılmaz. Kayıt anahtarı yalnız hata sınıfını taşır; istemci IP'si veya başka bir değer kayda girmez.

  • Kategori güncellemesinde ham alan yazılabiliyordu. İçerik kategorisi kaydederken, kayıt nesne döndürmediği durumda korumalı alanlar beyaz listeden geçirilmeden yazılıyordu. Artık aynı beyaz liste her iki yolda da uygulanıyor.

  • Tablosu olmayan modelde tablo adı isteyen sessiz hata. Böyle bir modelde tablo adı soran bir çağrı artık neden başarısız olduğunu yazan bir hata veriyor ve tablosuz modeller için ayrı bir okuma yolu öneriyor. Metot imzası değişmedi; zaten hata veren yolun hatası netleştirildi.

  • Kayıt adımını tanımlamayan sağlayıcılar sessizce atlanıyordu. Bu durum artık saatlik günlüğe bir satır olarak yazılıyor (sağlayıcı sınıf adıyla). İstisna fırlatılmıyor: hiçbir sağlayıcı çalışmaya devam eder, hiçbir istek düşmez.

Security

  • İstemci IP'si kaynağı: X-Forwarded-For / CF-Connecting-IP başlıklarına bilinçli olarak GÜVENİLMEZ (fail-closed). Proxy veya Cloudflare arkasında IP tabanlı kontroller (allowed_origins, IP ban) REMOTE_ADDR'e bakar; güvenilir proxy modeli ayrı bir karardır.

  • Çıkış için asıl yol artık POST + CSRF. Yeni rota POST /auth/logout (auth.logout) eklendi; CSRF belirteci zorunludur ve doğrulanamazsa istek reddedilir (fail-closed). Eski GET /logout ve GET /cikis yolları kaldırılmadı (meşru çıkış bağlantıları ve otomatik testler çalışmaya devam eder); her kullanımda security kanalına yalnız gözlem amaçlı bir "eski çıkış yolu" sayacı düşer. Geriye uyumlu GET yolunda çapraz site isteği (Sec-Fetch-Site: cross-site ya da başka alan adından gelen Referer) oturumu kapatmaz ve 403 döner. İstemci bu iki başlığı hiç göndermiyorsa (komut satırı araçları, eski istemciler) çıkış yine çalışır. Çıkış mantığının kendisi (kimlik eşleşmesi, master anahtar koruması) değişmedi.

  • Başlatma aşamalarındaki sessiz atlamalar artık görünür. Çekirdek kayıt aşamasında çözülemeyen servis, kaydedilemeyen hata servisi, kaynak sınıfı bulunmayan ve hedef adı zaten kullanımda olan sınıf takma adı kayıtları artık günlüğe yazılıyor (saatte bir, LogThrottle kapısından geçerek). Daha önce bu durumlar hiçbir iz bırakmadan geçiliyordu, teşhisi zorlaştırıyordu. İstek düşürülmez, istisna fırlatılmaz, yanıt ve akış değişmez — yalnız teşhis satırı eklenmiştir. Günlük satırları proje adı, anahtar, kullanıcı veya istek verisi içermez.

  • Tarayıcı tarama tuzaklarında gerçek sayfa kaybı giderildi. Saldırgan tarama tuzağı listesinde /admin, /test, /demo, /site, /ip gibi uygulama önekleri de vardı; bu adresler bir projede gerçek bir sayfaya karşılık geliyorsa istek kalıcı olarak ana sayfaya yönlendiriliyordu. Tuzak listesi değiştirilmedi; bunun yerine yalnız bu önekler için, istek gerçek bir rotaya eşleşiyorsa yönlendirme yapılmıyor. Gerçek rota yoksa, rota koleksiyonu boşsa veya bir hata oluşursa eski davranış aynen korunur (fail-closed): yani tuzak hiçbir projede zayıflatılmıyor, yalnız gerçek sayfalar kurtarılıyor. Diğer tüm tuzak kuralları (alt dizge eşleşmesi, eski yazı tipi, .php/.html/.htm uzantı taramaları) değişmedi.

Fixed

  • Yetkisiz form isteği artık düz metin yerine standart 403 sayfası gösteriyor. FormRequest::failedAuthorization() önceden 403 Forbidden - Unauthorized Request metnini doğrudan basıyordu; bilgi sızdırmıyordu ama kullanıcı framework'ün gerçek hata ekranını görmüyordu. Artık tek merkezden (shield()->forbidden()) diğer tüm 403 yollarıyla aynı kalkan sayfası basılıyor. Durum kodu yine 403'tür. Çok erken başlatma veya birim testi gibi shield() yardımcısının olmadığı durumlar için eski 403 + sonlandırma davranışı yedek olarak korundu; istek hiçbir koşulda 200/boş dönmez.

  • Çekirdek başlatma aşamalarının sırası ve bağımlılıkları kodda belgelendi. Altı aşamanın (oto yükleme → tanılama → veritabanı → servisler → oturum → yönlendirme) her biri için "hangi aşamaya bağımlı" gerekçesi yorum olarak yazıldı; sıra değişirse hangi adımın kırıldığı belge okunarak bulunabiliyor. Davranış değişmedi.

  • Sayfalama, liste ve yazma katmanı onarımları (çekirdek). (1) Sayfalayıcı artık ham $_GET / $_SERVER okumuyor: geçerli sayfa, arama metni ve sorgu parametreleri istek nesnesinden alınıyor; taban adres istek yolundan türetiliyor, bu yüzden komut satırında istek adresi tanımsız olsa da sayfalama istisna atmıyor. Sayfalama bağlantılarındaki adresler HTML kaçışından geçiriliyor (taban adresten gelen betik enjeksiyonu kapatıldı) ve sayfa numarası adres içinde yalnızca bir kez yazılıyor (sorgu içeren taban adreslerde oluşan çift "?" ve taşınan eski sayfa giderildi). (2) Durum aç/kapa ve model güncelleme çağrıları artık gerçek sonucu döndürüyor: var olmayan bir kayıt üzerinde çalışıldığında "başarılı" görünüp hiçbir şeyin değişmediği durum sona erdi. (3) İçerik listeleme yardımcısı, proje anahtarı çözümlenemediğinde tanımsız yardımcı çağırmak yerine güvenli bir geri düşüşe geçiyor; sorgu sonucu hem koleksiyon hem düz dizi biçiminde destekleniyor. (4) Ham kayıt ekleme yolu, kayıt oluşturma yoluyla aynı alan süzgecinden geçiyor: beyaz listesi tanımlayan bir modelde liste dışı alan artık yazılmıyor. Beyaz listesi tanımlamayan modellerde davranış değişmedi. (5) Yapılandırma okuma yolundaki "özyineleme" koruması ve hata yakalama artık sessizce yutmuyor; düşen anahtar ve istisna ayrıntısı sunucu günlüğüne yazılıyor, dönen değer ve istisna fırlatma davranışı değişmedi. (6) Denetleyici kurulumundaki boş hata yakalama blokları, çözümlenen ad, istisna sınıfı ve mesajı günlüğe yazıyor; servis/model çözülemezse denetleyicinin kurulmaya devam etme davranışı korunuyor. (7) İlişki tanımlayıcıları tek bir üretim noktasından geçiyor ve birincil anahtar, modelin genel erişimcisi üzerinden okunuyor; dönen değerler değişmedi. (8) Aynı işlemde kullanılan iki dev örneğin (depolama motoru, ana servis) test izolasyonu için sıfırlama çağrıları eklendi; çağrılmadıkları sürece mevcut tekil davranışı aynen sürüyor.

Changed

  • Tanılama günlükleri LogThrottle ile saatte bir. Yeni, tek yardımcı sınıf LogThrottle (Core/Support/Bridges/Helpers/Library/LogThrottle.php): LogThrottle::once(string $key, int $ttlSeconds = 3600): bool aynı anahtar için TTL içinde yalnız ilk çağrıya true döner, hiçbir koşulda istisna fırlatmaz. Depolama sırası Storage/cache/log-throttle/<sha256>.throttle (flock, süreçler arası birincil kaynak) → APCu (yalnız dosya kullanılamıyorsa) → fail-closed (false: satır yazılmaz, istek bozulmaz, günlük şişmez). Anahtar yalnız kalem etiketi + sınıf adı taşır (değer/PII yok). Bağlanan "her istekte yazan, tanılama amaçlı" kalemler: B-92 DI uyarısı, YP-1 MODEL_NOT_SCOPED, PROXY_DISCOVERY_SWALLOWED, F-10/F-14 LOG_ONLY_SAYAC (sayacın değeri değişmedi; yalnız yazılan satır sınırlı). Gerçek hata/denetim kayıtları (ApiGuard machine-api denetimi, BaseConfig, Database::connection, BaseController catch) ve mevcut kilidi olan toggleStatusCompat sayaçları dokunulmadı. Ölçülen etki (162 isteklik duman koşusu): nginx-error.log +596 → 0, MODEL_NOT_SCOPED 2098 → 0, LOG_ONLY_SAYAC 72 → 0; gerçek güvenlik olayı olan MASS_ASSIGNMENT_BLOCK kayıtları korunuyor. Test/ölçüm kapısı: RBN_LOG_THROTTLE=0 (EnvKeys'te kayıtlı, Env üzerinden okunur). Görünürlük korunur — uyarı saatte bir yine yazılır; RBN_DEV kapısı değiştirilmedi.

  • Katmanlar arası kaydetme imzası geriye uyumlu hâle getirildi. Kaydetme yolu servis katmanında iki zorunlu argüman isterken sağlayıcı katmanında tek argüman istiyordu; tek argümanlı çağrı istisna fırlatıyordu. Servis katmanındaki imza genişletildi: hem eski iki argümanlı hem tek argümanlı çağrılar çalışıyor, tek argüman dizi geldiğinde veri olarak yorumlanıyor (bir alt servis bu uyarlamayı zaten elle yazmıştı). Sağlayıcı katmanındaki imzalar değiştirilmedi. Durum aç/kapa yolunda servis katmanı, çağırdığı sağlayıcıya alan adını doğru konumda geçiriyor; önceden alan adı sessizce düşüyordu. Çağıranın verdiği durum değeri sağlayıcıda karşılıksız olduğunda bu durum artık sessizce yutulmuyor, sunucu günlüğüne yazılıyor.

  • Çekirdek proxy ve nitelik erişimi. Proxy üzerinden metot çağrıları artık verilen argümanları hedefe iletiyor (önceden argümanlar sessizce düşüyor ve hedefin kendi hatası maskeleniyordu); metot bulunamazsa eski davranış korunuyor. Nitelik sınıfları (#[Module], #[SubModule], #[Component]) framework servislerine erişebiliyor; çözümleme başarısız olduğunda istisna fırlatılmadan önceki davranış korunuyor ve ayrıntı günlüğe yazılıyor.

  • Orta/düşük şiddetli güvenlik sertleştirmeleri (üçüncü dilim, on altı madde; çekirdek, yönlendirme, form güvenliği, şablon ve hata ekranı). (1) URL güvenlik denetiminde yasaklı alan adı eşleşmesi artık yalnız ana makine adı üzerinde ve tam/son-ek/etiket olarak yapılıyor (yol ve sorgudaki rastlantısal metin eşleşmesi kalktı); DNS çözümü geçersiz adlarda yapılmıyor, süreç içinde önbelleğe alınıyor ve süreç başına sorgu sayısı sınırlı. (2) Çekirdek ön-uçuş (boot) hata sayfası üretimde ipucu ve ham istisna mesajını göstermiyor; ayrıntı sistem günlüğüne yazılıyor, yerel geliştirmede eskisi gibi. (3) Şablon verisi artık motorun yerel değişkenlerini (derlenmiş dosya yolu, düzen çözücü) ezemiyor: veri izole bir kapsamda açılıyor, ayrılmış adlarla çakışma günlüğe yazılıyor; ham PHP görünüm yolunda da mevcut değişkenler korunuyor. (4) Hata sayfaları üretimde framework ve kalkan sürüm numarasını göstermiyor (marka adı kalır). (5) ?rbn_diag=1 dizin dökümü artık yalnız geliştirme ortamı kararına (RBN_DEV) bağlı. (6) PHP sürüm başlığı (X-Powered-By) kaldırılıyor; X-Content-Type-Options: nosniff ve Referrer-Policy yoksa ekleniyor, proje kendi değerini koyduysa ezilmiyor (HSTS/CSP/X-Frame-Options bilerek eklenmedi: sunucu/proje kararı). (7) İçerik listesi önbellek anahtarı sıralama seçeneklerini de içeriyor (farklı sıralama aynı önbelleği döndürmüyor); yükseltmede içerik listesi önbelleği bir kez soğuk başlar. (8) Rota eşleştiricisi, özel düzenli ifade kümesi içeren parametre bloklarını (örneğin onaltılı anahtar + sabit miktarlı tekrar) artık doğru ayrıştırıyor; önceki iki aşamalı ayrıştırma bu bloğu bozuyor ve ilgili doğrulama ucu sürekli 404 dönüyordu. Ayıraç karakteri hem sabit yolda hem özel düzenli ifadede kaçışlanıyor; derlenemeyen bir desen hiçbir isteği eşleştirmeyen bir desene düşüyor. (9) Yönlendirme muafiyeti artık yolun herhangi bir yerinde alt dizge aramak yerine segmentin tam eşleşmesine bakıyor; böylece gerçek webhook/API uç noktaları korunurken, ad içeren meşru sayfalar 301 normalizasyonundan ve kanal adı (canonical host) zorlamasından muaf olmuyor. (10) Panel-kapalı projelerde giriş/kayıt/parola sıfırlama alt yolları da kapatılıyor (tam eşleşme ile alt yollar korumayı geçiriyordu); ön ek benzeri yollar kapsam dışı. (11) Rota hedefi çözümlenemediğinde üretilen hata mesajı üretimde sınıf ve metot adını taşımıyor; ayrıntı sistem günlüğüne yazılıyor. (12) Proje keşfi alan adı normalizasyonu artık yalnızca sondaki yerel geliştirme ekini (test/local) atıyor; önce metin içinde her yerde silindiği için alan adının ortasında bu ekleri içeren adlar bozulup yanlış projeye bağlanıyordu. Geçersiz alan adı artık boş değer dönüyor (kapalıya kapalı). (13) Oturum temizliği ve modül rota yüklemesi sessizce atlandığında artık günlük yazılıyor; istek düşürülmüyor, davranış değişmiyor. (14) İndirme başlığındaki dosya adı kaçışlanıyor (indirme başlığı için zaten var olan tek kaçış yardımcısı kullanılıyor; ikinci bir kaçış kodu oluşturulmadı). (15) Sanal varlık tanılama başlığına istek yolundan gelen ad ham basılıyordu; artık kontrol karakterleri ayıklanıyor ve yolun son segmentiyle sınırlandırılıyor. (16) 404 sayfasında yansıtılan komut çalıştırma açığı: sayfa başlığı mesajı istisna metnini kaçışlamadan yazıyordu ve istisna metni istek yolunu içeriyordu; böylece bir sayfa adresindeki istek, o sayfada etiket olarak geri yansıyordu. Hata ekranlarındaki tüm mesaj/ipucu/başlık değerleri artık HTML kaçışlanıyor; panik sayfasında daha önce uygulanan koruma kardeş ekranlara da yaygınlaştırıldı.

  • IP güvenlik katmanı (Gatekeepers) sertleştirmesi. (1) WAF ihlalinde yazılan yasak kaydı artık 7 günlük ve global değil: süre mevcut aktif yasak sayısından türetilen kademeli bir cezadır (ilk ihlal 15 dakika, tekrarında 1 saat, sonrasında 24 saat) ve kayıt bulunulan projenin kapsamında yazılır — aynı NAT adresindeki diğer projeler etkilenmez. Bu, form katmanında daha önce uygulanan kademeli/proje kapsamlı cezayla aynı kuralı paylaşır. IP enforce modu bu değişiklikle açılmaz. (2) Saldırı imzası taraması yeniden düzenlendi: istek yolu (kaynak kimliğidir) artık yalnız dizin gezinme denemelerine karşı taranır; sorgu dizesi ve gövde kendi desen kümeleriyle taranır; meşru metin, Türkçe içerik ve URL parçalarında yanlış pozitif azaltıldı. Gerçek saldırı göstergelerinin tümü korunmuştur (birim testi: 53 meşru örneğin 53'ü de temiz, saldırı örneklerinin tamamı yakalanıyor). (3) Konum servisine gönderilen adres kümesi genişletildi: CGNAT (100.64.0.0/10), belirtilmemiş adresler, çok noktaya yayın/reserve IPv4 aralıkları ve IPv6 özel aralıkları artık üçüncü tarafa gönderilmiyor; belirtilmemiş adresler "Türkiye" sayılmıyor. (4) Proje ön bilgisi okunurken tüm tablo sütunları çekilmek yerine kullanılan sütunların açık listesi okunuyor; okuma sonucu uygulama bağlamına konduğu için lisans anahtarı artık bu satırla taşınmıyor. (5) Veritabanı koruma katmanı, kullanıcıya gösterilen hata metinlerine ham bağlantı/sorgu hatalarını yapıştırmıyor; kök neden yalnızca sunucu günlüğüne yazılıyor. İstisna sınıflandırması, hata türü ve kabul testleri değişmedi: bağlantı/tablo/yazma yetkisi hataları yine ön koşulu engelleyerek (fail-closed) karşılanır.

  • Orta/düşük şiddetli güvenlik sertleştirmeleri (ikinci dilim, yedi madde). (1) Güvenlik ayarı önbellek anahtarı artık proje bazlı (ShieldSettingsRepository::settingsCacheKey()); okuma ve silme aynı anahtarı kullanır, ayar yazımı kendi projesinin önbelleğini düşürür. (2) Honeypot denetimi trim((string) $v) !== '' ile yapılıyor ("0" değeri artık bot sayılır). (3) Log/geçici dizinler 0777 yerine 0775 ile oluşturuluyor (Windows'ta izin biti etkisizdir). (4) Giriş akışında var olmayan veya pasif hesap için de sabit maliyetli sahte parola doğrulaması yapılıyor (zamanlama ile kullanıcı sayımlama). (5) Oturum politikasının Host eşleştirmesi geçerli alan adı karakterleri dışındakileri eşleştirmiyor. (6) Kilit ekranında POST yalnız kilit açma uçlarına (auth/login, auth/authenticate) izinli; denetim yolda "login" geçen isteklerin geçtiği alt dizge listesinden önce yapılıyor. (7) Panel SQL konsolu yalnız geliştirici/süper yönetici/master geliştiriciye açık, çoklu ifadeyi reddediyor ve her çalıştırmayı security kanalına değersiz (metin/sayı maskeli, ilk 200 karakter) kaydediyor.

  • Orta/düşük şiddetli güvenlik sertleştirmeleri (altı paket). (1) Origin/Referer denetimi bozuk URL'de artık hata (500) vermeden isteği reddediyor; joker alan adı eşleşmesi başa ve sona sabitlendi. (2) Üretimde hata/yol sızıntıları kapandı: iki repository'nin sistem günlüğüne ham veritabanı mesajı yazılmıyor (sınıf adı + kod; ayrıntı yalnız debug kanalında), View render hatası ve ExceptionHandler çözüm ipucu yalnız yerel geliştirmede ayrıntı gösteriyor, indirme başlığındaki dosya adı (Content-Disposition) satır sonu/tırnak kaçışlı. (3) Dosya yüklemede çift uzantı için yerel geliştirme muafiyeti kaldırıldı; gömülü <?php taraması 1 MB'a genişletildi. (4) SchemaBuilder kimlik kaçışı tek geçitte, convert() collation değeri doğrulanıyor. (5) custom_path dosya yoluna girmeden önce doğrulanıyor (geçersizse "yapılandırma yok" yoluna düşer, istisna atmaz). (6) Çıkışta silinen çerezler oluşturulurken kullanılan bayraklarla gönderiliyor; başarılı girişte eski parola özeti sessizce yenileniyor (CryptoHelper::needsRehash()).

  • CRUD girişi artık model beyaz listesine bağlı: CrudControllerTrait::create()/update() ham istek gövdesini servis/modele aktarmıyor. Standart CRUD uçları request->form([]) çağırıyordu; kuralsız bu çağrı tüm istek alanlarını döndürür (CSRF/iç anahtarlar düşülmüş haliyle) ve veri doğrudan servise akıtılıyordu. Artık veri hedef modelin $fillable listesine göre budanır. Geri uyumluluk kilidi korunur: hedef model çözülemiyorsa veya $fillable tanımlı değilse veri hiç değişmeden geçer — beyaz listesi olmayan modelin davranışı birebir aynıdır. Birincil anahtar (id) her zaman geçer. Düşürülen alanlar security kanalına değer taşımadan CRUD_INPUT_FIELD_DROPPED olarak loglanır. Altı modülün (ornek-proje-5, ornek-proje-3, ornek-proje-4, ornek-proje-6, ornek-proje-2, ornek-proje-1) 35 yazma modeli beyaz listeye bağlandı; listeler gerçek veritabanı şemasından ölçülmüştür ve panelin gerçekten gönderdiği alanların tamamını kapsar.

  • Süzgeçten geçen yazma yolları da beyaz listeye bağlandı (filterFillable()). model->query()->insert()/update() yolları CrudModelTrait'ten geçmediği için toplu atama süzgeci kendiliğinden çalışmıyordu. MassAssignmentTrait::filterFillable() eklendi ve ornek-proje-6 (PbProgramRepository::saveProgram) ile ornek-proje-1 (OfisProperty/OfisCustomer/OfisContract/OfisPersonel/OfisCash/DsaPropertyRepository) repository'lerindeki bu tür yazma yollarında açıkça çağrıldı. Bayrak kapalıyken veya liste tanımsızsa veri yine dokunulmaz.

  • RbnAdmin profil ekranı gerçek şemaya uyarıldı. z_users tablosunda firstname/lastname/phone_number kolonları yok; profil formu bu alanları gönderdiği için ekran "Unknown column" ile kırık kalıyordu (ve T4 sonrası email de düşüyordu — email UsersModel::$guarded içindedir). Ekran tek name kolonuna göre düzeltildi; email açık bir yetkili yazıcıya taşındı (UserRepository::updateEmail() → authorizeFields(['email']), UserRepository::updateProfile() → UserManager::updateProfile()).

  • UserSecurityRepository::saveSecurityData() create yolunda artık doğru dönüş değerini veriyor. Motor LAST_INSERT_ID() değerini döndürüyor; z_users_security tablosunda user_id birincil anahtar ve AUTO_INCREMENT değil olduğu için değer 0 geliyor ve (bool) 0 = false oluyordu — satır yazılıyordu ama çağrıya yanlış negatif dönüyordu. Artık satır yazıldıktan sonra doğrulanarak dönülüyor.

  • masterProjects repository takma adı registry'ye eklendi. MasterProjectsService kendi repository'sini suffix çözümlemesiyle istiyordu ama registry'de yalnızca noktalı master.projects anahtarı vardı; proje kaydı/lisans anahtarı üretimi bu yüzden diagnostic hatasıyla ölüydü. Aynı desen (masterLicence) zaten çözülmüştü.

  • Scoped modellerde project_key her zaman sunucu bağlamından yazılır (kiracı izolasyonu). Kapsamlı modelde create() yalnızca project_key boşsa aktif anahtarı yazıyordu; kullanıcı dolu bir değer gönderdiğinde o değer kabul ediliyordu. update() yolunda ise hiçbir işlem yapılmadığı için bir kayıt project_key değiştirilerek başka bir kiracıya taşınabiliyordu. Artık her iki yolda da project_key aktif uygulama bağlamından yazılır ve istekteki değer yok sayılır. Bilerek başka bir kiracıya yazması gereken tek yer (güvenlik ayarlarının GLOBAL kaydı) artık açık ve loglanan writeAsProject() kaçış kapısından geçiyor. Kapsam dışı modellerin davranışı değişmedi.

  • Toplu atama koruması: beş güvenlik modelinin korumalı alanları artık toplu atamayla yazılamaz (security.mass_assignment varsayılan AÇIK). UsersModel (role, email, username), UserSecurityModel (user_id), MasterDevelopersModel (role, password), MasterProjectsModel (license_key, status) ve MasterLicenceModel (status) modelleri artık $guarded tanımlıyor; create()/update()/save() bu alanları süzüyor. Bu alanlara yazması gereken her meşru çağıran yetkili yola taşındı: rol ataması UserManager::setRole() (rol beyaz listesiyle), parola UserRepository::updatePassword() (özet sunucuda üretilir), güvenlik kasası UserSecurityRepository::saveSecurityData() (user_id daima metot imzasından gelir), lisans durumu MasterLicenceRepository::create()/updateStatus() (beyaz listeli sabit), proje kimliği MasterProjectsRepository::createProjectIdentity()/updateProjectIdentity(). Yetkili yolların ortak kapısı authorizeFields()'tır: her çağrı security kanalına kayıt düşürür ve istekten gelen alan adını kabul etmez. Kapsam dışı modellerin ve $fillable/$guarded tanımlamayan 76 modelin davranışı değişmedi; geçici geri alma security.mass_assignment=false ile tek anahtardır.

  • IP güvenlik katmanı döngü (loopback) adresleri için kademeli ceza/ban kaydı yazmaz; yerel geliştirme ve aynı sunucudan gelen istekler kendi kendini banlamaz. Form katmanındaki şüpheli-User-Agent kontrolü, yerel döngü adreslerinden gelen isteklerde de çalışıyor ve global IP ban tablosuna kayıt yazıyordu. Test ve duman trafiği ile aynı sunucudan gelen sağlık/planlanmış-görev istekleri bu yüzden kendi kendini banlıyordu: katman bugün yalnızca gözlem (log_only) yaptığı için engellemiyor, ancak zorlama moduna geçildiğinde yerelden hiçbir giriş geçemezdi. Muafiyet yalnızca 127.0.0.0/8, ::1 ve IPv4-eşlemeli karşılıkları için geçerlidir; RFC1918 özel ağlar, bağlantı-yerel ve tekil-yerel adresler muaf değildir, çünkü ofis iç ağı gerçek bir istemci olabilir. Döngü adresleri için gözlem günlüğü de yazılmaz — 48 saatlik yanlış-pozitif ölçümünün kendi test trafiğimizle bozulmasını önlüyor. Katmanın modu değiştirilmemiştir. enforce moduna geçmeden önce eski ban kayıtlarının temizlenmesi önerilir.

  • "Beni hatırla" token'ı artık süre sınırlı ve sunucu tarafında doğrulanıyor (30 gün). Token'ın içine imzalı bir bitiş zamanı gömülüyor; imza mevcut uygulama anahtarı yolundan (HMAC) üretiliyor, yeni bir sır tanımlanmıyor ve veritabanı şeması değişmiyor. Önceden süre yalnızca çerezin ömrüydü; sunucu tarafında "süre doldu mu" denetimi hiç yoktu. Reddedilen bir token için tarayıcıdaki çerez de düşürülüyor. Aynı akışta "beni hatırla" normal kullanıcılarda hiç çalışmıyordu: kullanıcı kaydı e-posta/kullanıcı adıyla aranıyordu, oysa elde olan sayısal kullanıcı kimliğiydi; bu yüzden oturum yalnızca master geliştirici hesaplarında yeniden kurulabiliyordu. Artık kullanıcı kimliğinden getiriliyor. Geçiş: bu sürümden önce üretilmiş süresiz tokenlar 30 gün boyunca kabul edilir, sonra kullanıcıdan yeniden giriş istenir (bkz. UPGRADING.md).

  • Çıkış artık yalnızca çıkış yapan kullanıcının kendi oturumunu kapatıyor. Çıkış akışı, master geliştirici olmayan bir kullanıcı çıktığında da onun numarasıyla eşleşen master geliştirici hesabının "beni hatırla" tokenını siliyordu; yani bir kullanıcının çıkışı başka bir kimliğin oturumunu düşürebiliyordu. Master tokenı artık yalnızca oturum gerçekten o master kimliğiyle açıldıysa temizleniyor. Kullanıcı kimliği her hâlükârda oturumdan okunuyor, istek verisinden değil.

  • Giriş akışı global (master) IP ban listesini artık izliyor — ama engellemiyor. Giriş öncesi ban denetimi yalnızca siteye özel listeye bakıyordu. Global liste de okunuyor, ancak bir kayıt bulunduğunda giriş durdurulmuyor, yalnızca denetim kaydına "would-block" notu düşüyor. Ölçüm bunun için yapıldı: global listede 505 aktif kayıt var ve tamamı yerel IP adresine ait; bağlansaydı tüm yerel sitelerin girişi kapanırdı. Ban kayıtlarının temizliği ayrı bir karardır.

  • QueryBuilder SQL enjeksiyon yüzeyi kapatıldı: orderBy() yön beyaz listesi, sütun adı kaçışı, updateBatch() placeholder'ları. Sorgu kurucusunun dört ayrı yeri kullanıcı girdisini doğrulamadan SQL'e gömerdi. orderBy() yön parametresi artık yalnız ASC/DESC kabul ediyor (başka her değer istisna fırlatır); sütun adlarındaki backtick'ler wrapColumn() içinde ikilenerek kaçılıyor, böylece tırnak kapatma girişimi SQL'i kıramıyor. updateBatch() kimlik değerlerini tırnak içine gömerek yazmak yerine adlandırılmış yer tutucuya geçirdi ve tam sayı olmayan kimliği kaynakta reddetti — katı olmayan sunucu ayarlarında sessizce yanlış satırın güncellenmesi artık mümkün değil. sum()/avg() de artık yalnızca düz sütun ya da tablo.sütun adı kabul ediyor. Mevcut geçerli orderBy/sum/avg/updateBatch çağrılarının SQL anlamı korunuyor.

  • Çerez üreten yardımcılar (cookie(), rbn_alert) artık HTTPS'te Secure ve SameSite bayrağı taşır. Oturum çerezi Secure/SameSite/HttpOnly taşırken framework'ün geri kalan çerez üreticileri bayraksız kalmıştı: cookie() imzasındaki bool $secure = false varsayılanı her çerezi bayraktan arındırıyordu, rbn_alert çerezi ise hiç bayrak taşımıyordu. Secure kararı artık tek kaynaktan (RequestEnvironment::isHttpsRequest(), A0-4 ile aynı) gelir ve düz HTTP'de Secure üretilmez — yerel geliştirme akışı bozulmaz. cookie() imzası sona isteğe bağlı SameSite parametresi kazanmıştır; konumsal argümanlı mevcut çağıranlar çalışmaya devam eder. rbn_alert çereğinde HttpOnly bilerek yoktur: rbnAlert.js çereyi document.cookie ile okur, HttpOnly konulsaydı uyarı gösterilemezdi.

  • Yeni parola belirlemede minimum 8 karakter zorunlu (giriş etkilenmez). Parola politikasında taban bir minimum uzunluk yoktu: CredentialHandler::validatePassword() yalnız yaygın parola kara listesi ve sıra/klavye deseni kontrolü yapıyordu, tek uzunluk kuralı ise form katmanında dağınık min:6 idi — bu yüzden 6 karakterlik zayıf parola kayıt ve şifre sıfırlama akışlarından geçiyordu. Kural artık tek yerde tanımlı (PasswordValidations::MIN_PASSWORD_LENGTH) ve hem form doğrulaması hem iş kuralı bu sabite bağlı; boş veya yalnızca boşluktan oluşan parola da reddediliyor. Giriş (login) akışı değişmedi: girişte parola uzunluğu denetlenmiyor, mevcut kullanıcıların kısa parolaları geçerli kalıyor.

  • E-posta alan adı doğrulaması artık canlı DNS'e bağlı değil. Alan adı, MX kaydı olup olmadığına bakmak için checkdnsrr() ile canlı DNS sorgusuna gönderiliyordu; PHP bu çağrıya zaman aşımı veremediği için yanıt vermeyen bir çözümleyici isteği — yani kayıt/giriş formunun kritik yolu ile giden e-posta akışını — asılı bırakabiliyor, ayrıca DNS'e bağlı karar zamanlama yan kanalı oluşturuyordu. Karar artık yalnızca e-posta biçimi, alan adı yazımı ve kara listelerine dayanıyor (DNS çağrısı yok); gerçek teslimat denetimi kendi zaman aşımı olan SMTP katmanında yapılıyor.

  • IP katmanı ön koşulları kapatıldı: beyaz liste bot kuralını da muaf tutuyor, form ihlali cezası proje kapsamlı, ajan uçları ban/WAF bloklamasından muaftır, WAF istek gövdesini de tarıyor. Dört ayrı düzeltme: (1) IpGuardHandler::check() içinde ip_whitelist denetimi bot_user_agent kuralından sonra geliyordu; yönetici tarafından açıkça eklenen (güvenilir) bir IP sitebot User-Agent ile geldiğinde yine de engelleniyordu. Beyaz liste kontrolü bot kuralından önceye alındı; beyaz listedeki IP artık bot_user_agent ve ip_blacklist kurallarından muaftır. WAF'tan muaf değildir: WAF bir IP itibarı kuralı değil, istek yüzeyi taramasıdır. (2) FormGuardHandler::applyGlobalBlock() kademeli cezayı project_id = 0 (global) olarak yazıyordu; ban denetimi global VEYA aktif proje kapsamını taradığı için aynı NAT IP'sindeki tüm projeler tek bir form ihlaliyle susturuluyordu (kurban banlanıyordu). Ceza artık bulunulan projenin kapsamında yazılıyor; ban satırının şeması değişmedi (mevcut project_id kolonu kullanılıyor), kademeli süreler (15 dk / 1 saat / 24 saat) ve log_only/enforce ayrımı aynen korunuyor. (3) /api/agent/* ve /api/telegram/webhook uçları kendi kimlik doğrulaması ve kendi hız sınırıyla gelen uçlardır; IP katmanı ban/WAF uyguladığında ajan susturuluyordu. Bu iki yol artık IP banı ve WAF bloklamasından muaftır (loglanan tek satır: "muaf: ajan uç"). Muafiyet yalnız bu iki yol grubundadır, IP'ye veya User-Agent'a güvenilmez; yol normalleştirmesi sorgu dizesi ve parçayı atar, kodu çözer, ..////\ içeren yollar ile büyük harfli yollar fail-closed olarak muaf sayılmaz. (4) IpGuardService::detectAttack() yalnız REQUEST_URI, QUERY_STRING ve $_POST'u tarıyordu; Content-Type: application/json ile gelen gövde $_POST'a düşmediği için JSON taşıyan saldırılar WAF'a görünmüyordu. php://input gövdesi de taranır: 64 KB byte sınırı (aşan gövde taranmaz), SQLi için yapısal kalıplar ve path traversal; <script>/javascript:/onerror= gibi XSS kalıpları gövdede uygulanmaz çünkü gövde meşru HTML/JS içeriği taşır (XSS'in asıl yeri form katmanındaki InjectionHandler::detectXss()'tir). 25 örneklik meşru JSON korpusu (panel kaydetme, HTML içerik, Türkçe metin, log satırları) ölçüldü ve hiçbiri engellenmiyor. Gövde file_get_contents('php://input') ile okunduğu için akış tüketilmez ve controller'ın Request::getJsonData() okuması bozulmaz. Katmanın modu değiştirilmedi: ölçüm amacıyla log_only olarak kalıyor, enforce ayrı bir karardır.

Fixed

  • Doğrulama kurallarında accepted kuralı eklendi; karşılığı olmayan kural artık sessizce atlanmıyor. Validator::applyRule() eşleşen bir doğrulama metodu bulamayınca hiçbir iz bırakmadan geçiyordu: yazım hatalı ya da framework'te unutulmuş bir kural güvenlik kuralıymış gibi görünüyor, uygulanmıyordu. En görünür sonucu, KVKK açık rıza onay kutularının ('privacy' => 'accepted') sunucu tarafında hiç doğrulanmamasıydı. Artık accepted gerçek bir kural (kabul kümesi: on/yes/1/true/accepted; onaylanmayan kutu reddedilir) ve karşılığı olmayan her kural error_log uyarısı bırakıp Validator::unknownRules() ile gözlenebilir hâle geliyor. Tarama sonucu karşılığı bulunmayan diğer kurallar da uygulandı: string, array, in, same; nullable/optional yalnızca tanınmış işaretleyici olarak kaydedildi (mevcut kurallar zaten boş değerde atladığı için davranış değişmedi). Bir canlı kullanımda kural adı yerine rol değeri yazıldığı için bilinmeyen kuralda işlem durdurulmuyor, uyarı verilip devam ediliyor.

  • Çözülemeyen middleware'de rota çalıştırılmıyor (Dispatcher). Bir rotanın middleware'i çözülemediğinde (yüklenmemiş yapılandırma, yanlış ad, silinmiş sınıf) koruma hiç uygulanmadan controller çağrılıyordu; Route::middleware('admin') yazan bir rota, admin çözülemezse yetkisiz kullanıcıyı doğrudan controller'a götürüyordu. Artık çözülemeyen middleware için istek hata ile düşürülüyor ve controller hiç çağrılmıyor. Framework ve projelerde kullanılan tüm middleware adları önceden tarandı ve hepsinin çözülebildiği görüldü; meşru rotaların davranışı değişmedi.

  • XSS algılama kalıpları genişletildi. detectXss() yalnızca iki kalıpla çalışıyordu; ölçülen saldırı vektörlerinin çoğu bu ağdan geçiyordu (scripting şemaları, data:text/html yükleri, object/embed/iframe/svg etiketleri, expression() ve srcdoc=). Kalıp kümesi bu vektörleri kapsayacak şekilde genişletildi ve formun tamamı tarandığı için serbest metinde geçen kelimeleri engellememeye özen gösterildi (şema kalıpları yalnızca bağlantı niteliği bağlamında ya da boşluksuz çağrı biçiminde aranıyor; olay nitelikleri yalnızca etiket içinde aranıyor). Ayrıca iç içe dizi gönderilen alanlar (örneğin alan[alt][]) daha önce hiç taranmıyordu; artık özyinelemeli taranıyor.

  • checkPrefixGuard() yönlendirmesi güvenli-hedef süzgecinden geçiyor. Panel ön eki değiştiğinde üretilen Location başlığı doğrudan $_SERVER['REQUEST_URI'] yolundan kurulup doğrulamasız basılıyordu; bu, RedirectManager için eklenen güvenli-hedef kapısından geçen ikinci bir yönlendirme yoluydu. Hedef üretimi saf bir adıma taşındı ve çıktı mevcut güvenli-hedef süzgecinden (guvenliHedef()) geçirildi; yeni bir süzgeç yazılmadı. Güvenli hedef üretilemezse yönlendirme yapılmıyor, mevcut kendini-iyileştirme yolu devreye giriyor. Meşru ön ek yönlendirmesi aynı çalışıyor.

  • having() artık var; orWhere() tek başına ve iki argümanla doğru çalışıyor. having() koşulu QueryBuilder'da hiçbir trait'te tanımlı değildi, ama alan taşınıyor ve derleyici kullanıyordu; model katmanındaki proxy bu metoda gittiği için çalışma anında hata veriyordu — metot eklendi ve değerleri bağlanmış yer tutucuya çeviriyor. orWhere() koşulu ilk sırada olduğunda WHERE OR ... biçiminde geçersiz SQL üretiyordu; OR öneki artık konuma göre konuyor (ilk koşulda yok). orWhere('sütun', $değer) iki argümanlı biçim de where() ile aynı kuralı uyguluyor (eşitlik); üç argümanlı biçim ve where() zincirleri önceki anlamını koruyor.

  • Giriş hız sınırı sayacı artık CSRF doğrulamasından sonra artar. FormGuardHandler sayacı CSRF kontrolünden önce güncelliyordu; böylece token'ı olmayan, başka bir siteden gelen çapraz-site bir istek bile kurbanın hız sınırı sayacını tüketebiliyor, kurban doğru bilgileriyle giriş yapamıyordu. Artık "zaten bloklu mu?" kontrolü yerinde kalır, sayacı artıran adım CSRF doğrulamasından sonra çalışır. Geçerli token'lı başarısız giriş denemeleri sayılmaya devam eder; hız sınırının kendisi (5 istek / 900 sn) değişmedi ve zayıflamadı.

Changed

  • F-08: Token taşımayan framework form'larına @csrf eklendi (Backstage E-posta/Ayarlar/Sidebar modalları, Webhub Politika modalı, RbnAdmin kullanıcı rolü + Hostmailhub, RbnStudio Posts/News) ve panel başlığına csrf-token meta etiketi kondu; böylece CSRF zorunlu hale geldiğinde framework'ün kendi form ve AJAX akışları geçerli bir token taşır. rbnService.js değişmedi: token'ı zaten otomatik ekliyordu, sadece kaynağı (meta[name="csrf-token"]) artık mevcut.

Added

  • Modellerde opsiyonel $fillable/$guarded desteği (security.mass_assignment, varsayılan AÇIK). Model bu iki özellikten birini tanımlıyorsa create()/update()/save() veriyi bu listeye göre süzer ($fillable = beyaz liste, $guarded = kara liste; ikisi birlikte tanımlıysa $fillable baskın). Tanımlamayan modelde hiçbir şey süzülmez — 81 modelin hiçbiri kırılmadan eklenebilir. Beş güvenlik modeli bu sürümde $guarded tanımı kazandı (bkz. Security). Anahtar off/false/log_only yapılırsa motor tamamen kapanır.

  • Korumalı alanlar için log-only izleme (security.protected_field_log, varsayılan AÇIK). Toplu atamada role, is_admin, password, password_hash, license_key, project_key, user_id alanlarından biri gelirse security kanalına alan adı yazan bir uyarı düşer; değer yazılmaz (parola sızmaz). Yazma davranışı hiç değişmez — yalnız ölçüm. status ve is_active bilinçli olarak listede değildir (meşru çağıranları kırmamak için). Sayaç yeni tablo gerektirmez: Storage/logs/security/ altındaki MASS_ASSIGNMENT_BLOCK satırları sayılır.

  • Database::transaction(callable): başarıda commit, hatada rollback ve istisna yeniden fırlatılır; iç içe çağrılar dış işleme katılır. Model katmanı TransactionModelTrait::transaction() üzerinden uçtan uca çalışır.

  • S-1/S-2 birim testi: E:\tmp\_araclar\fw-regresyon\birim\fw_system_guard_bypass.php (framework içine Tests/ açılmadı): sorgu parametresine gizlenmiş istisna metni, gerçek statik varlık yolları, boş/dolu REMOTE_ADDR, boş beyaz liste kaydı, gerçek VIP/yerel VIP ve CLI yolu senaryoları. Handler yalnız birim seviyesinde, sahte girdilerle sınanır (bakım modu ayarına dokunulmaz, DB yazılmaz). Taban sürümde 7 kontrol kırmızı, düzeltmede yeşil.

  • FW-INSTALLER-KALDIR-2: Birim testi E:\tmp\_araclar\fw-regresyon\birim\fw_installer_kaldir.php (framework içine Tests/ açılmadı): Installer kalıntı taraması (CHANGELOG/UPGRADING tarihçesi hariç sıfır eşleşme), Installer/ klasörü ve InstallerStage.php yokluğu, hayatta kalan üç sınıfın (KernelFactory, SystemHandlers, SystemLogicMapTrait) php -l temizliği ve autoload ile çözülmesi, bypass'ın yerine yeni kısa yol yazılmadığının kanıtı (BaseStage::isInstallerBypass, BaseGuard::isInstallerBypass, GuardInterface imzası yok) ve ölü komut kayıtlarının temizliği. Taban sürümde kırmızı, bu dala geçince yeşil.

  • FW-INSTALLER-KALDIR-2: fw_r1_r2_gomulu_sir.php R2'nin "installer view bulundu" varsayımı güncellendi: Installer kaldırıldığı için view dosyası yoktur ve gömülü parola imkânı da yoktur; dosya yeniden gelirse içerik taraması yine devreye girer.

  • FW-TEK-SECRETS-DOSYASI-161: Birim testi E:\tmp\_araclar\fw-regresyon\birim\fw_tek_secrets.php: tek dosya okuma (sahte dizinle), eksik alan → adı söyleyen anlamlı hata, CHANGE_ME/boş reddi, kalıntı taraması (kodda master-secrets.php / cpanel-secrets.php / gömülü DB adı / host / SMTP host / kullanıcı sabiti yok), şablonda gerçek değer 0, .gitignore secrets.php'yi kapsıyor ve git tarafından takip edilmiyor.

  • FW-TEK-SECRETS-DOSYASI-161: secrets.example.php şablonu HER alanı Türkçe yorumla açıklar ("buraya NE yaz, örnek NE"); en üstte 3 satırlık kullanım. Secrets/README.md 10 satıra indirildi.

  • FW-TEK-SECRETS-DOSYASI-161: projects/*/Core/Config/project-settings.php üst yorumlarındaki düz metin user : / pass : / admin_pass : kalıntıları kaldırıldı (yalnız yorum satırları; return dizisine dokunulmadı, değerler hiçbir çıktıya yazılmadı).

  • FW-ENV-KAYIT-160: Ortam değişkenleri / kill-switch'ler artık TEK kapıdan okunur. Core/System/Config/Env.php (Env::string() / Env::flag() / Env::int()) tek okuyucudur; kaynak sırası $_ENV → $_SERVER → getenv ve değerler tembel (bir kez çözülüp önbelleğe alınır). Ağaçta getenv( geçişi yalnız Env.php içinde kaldı (birim testi kalıntı taramasıyla doğrular).

  • FW-ENV-KAYIT-160: Core/System/Config/Definitions/EnvKeys.php — tüm RBN_* / APP_* adlarının TEK kayıt listesi. Her satır tur (string/flag/int), varsayilan, gizli, aciklama, kullanan, onerilen_yer alanlarını taşır. Kayıtsız ad Env'den okunamaz (fail-closed RuntimeException): yazım hatası sessizce "ortam değişkeni yok" sayılmaz. Gizli işaretli adların değeri hata mesajına/loga asla yazılmaz.

  • FW-ENV-KAYIT-160: Core/System/Config/README.md — "Gizli = Secrets, Bayrak/ortam = Env + EnvKeys; yeni anahtar eklemek = EnvKeys'e bir satır" kuralı, Env API'si ve kayıtlı ad tablosu.

  • FW-ENV-KAYIT-160: Birim testi E:\tmp\_araclar\fw-regresyon\birim\fw_env_kayit.php (24 kontrol, 6 küme): (A) getenv( kalıntı taraması, (B) kodda okunan her RBN_*/APP_* adının kayıt kapsamı, (C) Env::flag kapalı listesi ShieldSettingsRepository::normalizeSwitch() ile birebir aynı ve 13 girdide aynı karar, (D) kayıtsız ad → fail-closed, (E) gizli ad sızdırmaz, (F) okuma davranışı (kaynak sırası, boş değer, int, tembellik). kabul-kosu.ps1 bunu otomatik keşfeder (dokunulmadı).

  • FW-SIRLAR-TEK-CIKIS-149: Sırlar tek çıkıştan okunur: Secrets::masterDb(), ::smtp(), ::cpanel(), ::api($ad), ::section($ad), ::appKey(). Yeni dış servis (ödeme/SMS) eklemek artık ikinci bir sır dosyası değil, secrets.php içindeki api bölümüne bir satır eklemektir. Sır dosyasını require eden başka yer yoktur (kalıntı taraması bunu doğrular).

  • FW-SIRLAR-TEK-CIKIS-149: Bölümlü tek sır dosyası Core/System/Config/Secrets/secrets.php (bölümler: master_db, smtp, cpanel, api, üst düzey app_key) ve gerçek değer içermeyen şablonu secrets.example.php. Geçiş isteğe bağlıdır: secrets.php varsa tek başına okunur; yoksa mevcut master-secrets.php + cpanel-secrets.php çalışmaya devam eder (sıfır kesinti, sır rotasyonu yok). Yayın sırası ve geçiş tuzağı: .github/UPGRADING.md → 0.9.0 Adım 7.

  • Core/System/Config/Definitions/ConfigFile.php + ConfigFileLoader trait'i: dosya teknikleri (bul → 0600 izin kontrolü → <?php/return biçim kontrolü → require → dizi/anahtar doğrulama → tembel önbellek) tek yazımda toplandı; Secrets ve ConfigFile aynı mantığı paylaşır. Hata mesajlarında hiçbir sır değeri yazılmaz (yalnız dosya/bölüm/anahtar adı). Secrets.php komsu dosya require etmez; bootstrap'ta ve izole kopyada tek başına çalışır.

  • Birim testi E:\tmp\_araclar\fw-regresyon\birim\fw_sirlar_tek_cikis.php (41 kontrol): kalıntı taraması, şablonda gerçek değer 0, gerçek sır dosyalarının git'e takip edilmemesi (check-ignore + ls-files), bölümlü okuma, eski iki dosya geriye uyumu, öncelik kuralı, fail-closed (eksik dosya/bölüm/anahtar, CHANGE_ME, boş değer) ve hata mesajında sır sızdırmama ölçümleri — hepsi sahte dizinle.

  • Core/System/Config/Secrets altında fail-closed çalışan sır okuyucusu (Config/Secrets.php): master veritabanı ve master SMTP parolaları artık kaynak kodda değil, ayrı bir sır dosyasından okunur. Okuyucu dosyayı bulamazsa veya gerekli anahtarlardan biri eksikse hata fırlatır; boş değere sessizce düşmez.

  • Core/System/Config/Secrets/master-secrets.example.php — gerçek değer içermeyen şablon (yer tutucu). Gerçek sır dosyası bu şablon kopyalanarak sunucuda oluşturulur.

  • Core/System/Config/Secrets/README.md — sır dosyasının ne olduğu, nasıl oluşturulacağı, dosya izni ve "depoya girmez" kuralı.

  • Core/System/Config/Definitions/DbProfiles/README.md — veritabanı kimlik profilleri klasörünün ne olduğu, hangi sınıf ne yapar, neden koruma katmanında değil, kim kullanır (klasör standardı: ne girer / ne girmez / nereye yazılır).

  • .gitignore kuralı: gerçek sır dosyası sürüm takibine girmez; şablon ve açıklama dosyası girer. Yayın paketi gerçek sır dosyasını içermez.

  • Yönlendirme doğrulaması: redirect() artık yalnızca beyaz listedeki site içi hedeflere izin verir, liste proje taban adresi + proje grubunun kendi alan adlarından oluşur. Reddedilen hedefler site içi güvenli adrese düşer.

  • Form korumasında kademeli ceza: kalıcı uzun süreli engelleme yerine ihlal sayısına göre artan süreli kademeler; yeni tablo/kolon eklenmedi.

  • IP güvenlik katmanı için shield_ip_guard_mode çalışma modu ayarı (log_only / enforce; varsayılan log_only) ve buna bağlı Storage/logs/ipguard/ altına would-block kaydı. Mod ayarı tek yerde tutulur.

  • Core/System/Config/Secrets/cpanel-secrets.example.php - gercek deger icermeyen sablon (yer tutucu). Gercek dosya bu sablon kopyalanarak sunucuda olusturulur ve depoya girmez.

  • Kabul testi E:\tmp\_araclar\fw-regresyon\kabul\B1-cpanel-sir-koddan-cikar.php (B1 kalemi): kaynak kodda gomulu cPanel kimlik bilgisi olmamasi, tek kapi kurali ve fail-closed davranisi.

  • Tek kullanimlik, sureli e-posta dogrulama ve parola sifirlama token kasasi (Core/Database/Models/Project/UserTokenModel.php + Core/Database/Repositories/Project/UserTokenRepository.php, tablo z_user_tokens). Ham token veritabaninda saklanmaz; yalnizca ozeti tutulur, kullanim aninda mumkun olmayan sekilde denetlenir ve her satiri kullanim/sure bilgisi tasir. Kayit project.userToken girdisiyle tek merkezden yapilir. Migrasyon: 111-z_user_tokens.sql (koddan once calistirilir).

  • Parola sifirlama formu gonderimine token alani eklendi; sifre yenileme sayfasi gecerli bir baglanti olmadan form gostermez.

  • Lisans kapısı doğrudan licences tablosundan (FW-LICENCE-G): Ara katman LicenceGate kaldırıldı; DatabaseGuardHandler artık proje kimliğiyle MasterLicencesService::forSubject() üzerinden lisans kaydını okur. Metin tabanlı anahtar kontrolü (projects.license_key içinde serbest arama) kaldırıldı. Kural: tier=FREE + status=active + süresi dolmamış kayıt kısıtsızdır; kayıt yok, okunamıyor veya servis hatası durumunda ücretsiz sayılmaz ve proje durum kontrolü uygulanır (fail-closed, hata günlüğe yazılır, anahtar değeri hiçbir yere yazılmaz). projects.license_key sütunu değişmedi.

  • Merkezi lisans (PARÇA B — servis katmanı): Yeni MasterLicencesService (karar katmanı; verify, issue, revoke, forSubject) ve MasterApplicationsService (getByAppKey, list, register, setCurrentVersion) eklendi; ikisi de yalnız repository üzerinden çalışır, veritabanına doğrudan erişmez. Veri erişimi MasterLicenceRepository / MasterApplicationRepository sözleşmelerine birebir uyar. Data/MasterLicenceConfig.php yalnızca sabit tutar (metot içermez); lisans durumları, kademeler (FREE/LIFETIME/PRO), özne türleri, anahtar öneki ve fail-closed doğrulama sebepleri buradan gelir. Doğrulama fail-closed: bilinmeyen anahtar, dondurulmuş/iptal kayıt, süresi dolmuş kayıt ve depolama hatası geçersiz sonuç verir; anahtar üretimi RBN-XXXX-XXXX-XXXX-XXXX biçiminde ve benzersizlik için en çok 5 denemede yapılır.

  • Merkezi lisans (PARÇA A — tablolar + migration + repository): Master veritabanına applications ve licences tabloları eklendi; TEK 3 tablo kuralı (proje + uygulama aynı sistem), application_licenses / application_releases / application_installations / application_audit_log yok. projects tablosundan hiçbir sütun kaldırılmadı. Yeni migration yolu: Core/Database/Migrations/Master/ + MasterMigrationService (kayıt tablosu master_migrations); asla otomatik çalışmaz, yalnız elle rbn master:migrate [--rollback] ve rbn master:migrate:status. Aynı migration projects.license_key değerlerini licences tablosuna kopyalar (tier ön eki kuralı: RBN-LIFETIME- → LIFETIME, RBN-FREE-/FREE-OSS → FREE, diğer → PRO; suspended dışındaki her durum → active; boş anahtar → satır yazılmaz, adet raporlanır). Sürüm ürüne aittir: projects.version ve applications.current_version kalır, licences'ta sürüm sütunu yoktur. Yeni veri katmanı: MasterLicenceModel / MasterApplicationModel + MasterLicenceRepository (findByKey, findForSubject, create, updateStatus, bindDevice, isKeyUnique, list) ve MasterApplicationRepository (findByAppKey, list, create, updateVersion), registry alias'ları master.licence / master.application. Yazım kuralı: Licence (İngilizce) her yerde — tablo licences, sütun licence_key, sınıf MasterLicenceRepository. Anahtarlar veritabanında saklanır; ed25519/imza/anahtar klasörü kalıbı yok.

  • Yazım düzeltmesi (proje sahibi kararı): Yazılım lisansı yazımı İngilizce Licence olarak tekilleştirildi. Core/Services/Gatekeepers/LicenseGate.php → LicenceGate.php (sınıf LicenseGate → LicenceGate, git mv ile), değişken $licenseKey → $licenceKey. Davranış, ölçüt ve veritabanı sütun adı değişmedi: projects.license_key sütunu ve 'license_key' dizi anahtarı/sorgu dizeleri AYNEN kalır.

  • Kimlik/sürüm verisi TEK merkezde: RbnShield, RbnAdmin ve RbnAuth ad/sürüm/slogan bilgileri Support/Definitions/System/FrameworkIdentity içine toplandı (SHIELD_*, ADMIN_*, AUTH_*); ShieldMetadata/PanelIdentity/AuthIdentity içindeki kopya NAME/VERSION/SLOGAN/TITLE sabitleri kaldırıldı; okuyanlar (AuthHandler, PanelHandler, ModuleData, BaseExceptionProvider, SurvivalProvider) merkezden okur; görünümler RbnSystemInfo::get(...) ile aynı kaynağa bağlı.

  • Hosting verisi (proje sahibi düzeni): Core/Services/Hosting/Data/CpanelData.php artık yalnız SABİT taşır (DEFAULT_QUOTA, DESTINATIONS; metot yok); kimlik okuma CPanelProvider içinde (host()), dışarıya CPanelService::host() ile açılır (ayrı sınıf yok); Hosting/Models kalktı.

  • Config yapısı (proje sahibi düzeni): Engine/Database/ klasörü eklendi (DatabaseConfig, DbProfileResolver, SmtpProfileResolver); BaseDbData KALDIRILDI; MasterDbData/CommonDbData/ProjectDbData artık hiçbir sınıftan türemez (final class, yalnız public const); ProjectDataMapper profil kontrolü açık listeyle yapılır.

Changed

  • FW-TEK-SECRETS-DOSYASI-161 (KIRICI): Sunucuda TEK dosya: secrets.php (eski 3 adımın yerine). Core/System/Config/Secrets/secrets.php TEK sır dosyasıdır; bölümleri düz ve hepsi opsiyonel değil zorunludur: master_db {host, port, name, user, pass}, smtp {enabled, host, port, secure, user, pass, from_address, from_name}, cpanel {host, user, token}, app_key, api {hizmet => alanlar}. Eski master-secrets.php + cpanel-secrets.php dosyaları, "geriye uyum" kipi ve MASTER_DB_USER / MASTER_DB_PASS / APP_KEY ortam değişkeni ile geçersiz kılma yolları TAMAMEN KALDIRILDI (üç yol yerine tek yol). Canlı sunucuda sır dosyası yoktur; operatör secrets.example.php'ten kopyalar. Yayın sırası ve komutlar: .github/UPGRADING.md → 0.9.0 Adım 8.

  • FW-TEK-SECRETS-DOSYASI-161 (KIRICI): MasterDbData içindeki gömülü bağlantı sabitleri DB_NAME, SMTP_ENABLED, SMTP_HOST, SMTP_PORT, SMTP_USER, EMAIL_FROM_ADDRESS, EMAIL_FROM_NAME KALDIRILDI; hepsi artık Secrets::masterDb() / Secrets::smtp() üzerinden metotla okunur (host(), port(), dbName(), user(), pass(), smtpEnabled(), smtpHost(), smtpPort(), smtpSecure(), smtpUser(), smtpPass(), emailFromAddress(), emailFromName()). MasterDbData::DB_NAME kullanan çağıranlar dbName()'a geçirildi (CronLogRepository, SystemHandlers, EmailConfigHandler). Proje veritabanı dokunulmadı: her proje kendi project-settings.php dosyasından okumaya devam eder.

  • FW-TEK-SECRETS-DOSYASI-161: Fail-closed hata metni artık alan adını söyler ve değeri yazmaz: secrets.php: master_db.user tanimli degil. Üç yönlendirme (klasör + iki eski dosya + "isteğe bağlı geçiş") satırı kaldırıldı.

  • FW-CONFIG-YAPISI (güvenlik incelemesi): Ortak dosya tekniği Secrets.php'ten Engine/'ye taşındı. Engine/ConfigFileGuard.php (izin tavanı 0600 + <?php/return sızıntı kontrolü + operatör yol/ipucu üretimi) ve Engine/ConfigFileLoader.php (bul → require → dizi doğrula → anahtar doğrula → tembel önbellek) ayrı trait'ler oldu; ikincisi birincisini use eder. Secrets.php bunları göreli require_once ile yükler (otoloader henüz hazır olmayan kernel/bootstrap aşaması ve izole dizin kopyaları için zorunlu). Gece yazılmış Türkçe metot adları İngilizceye çevrildi: sablonYolu()→examplePath(), ipucu()→hint(), izinTavani()→permissionCeiling(), ilkReturnKonumu()→firstReturnPosition(), onbellekAnahtari()→cacheKey(). Davranış birebir aynıdır (aynı hata metinleri, aynı fail-closed davranışı, aynı önbellek).

  • FW-CONFIG-YAPISI (güvenlik incelemesi): Secrets.php İNCE CEPHEYE bölündü (1009 → 249 satır). Sorumluluklar ayrıldı: Definitions/SecretsSchema.php (yalnız public const, metot yok — alan adları, izin tavanı 0600, CHANGE_ME işaretçisi, bölüm kuralları), Engine/SecretsLoader.php (yol çözümü + ham dosya okuma; TEK okuma kapısı), Engine/SecretsValidator.php (bölüm adı biçimi + "var ama okunamıyor" ayrımı), Engine/SecretsSections.php (bölüm okuma/doğrulama + düz anahtar haritaları), Engine/SecretsFlatApi.php (eski düz anahtar yüzeyi all()/get()/optional()/masterDbPass()/masterSmtpPass(); trait olarak Secrets üzerinde use edildiği için imzalar korunur). Secrets::masterDb()/smtp()/cpanel()/api()/appKey()/section() imzaları değişmedi, çağıranlar kırılmaz. Hiçbir `Config/dosyası 250 satırı geçmiyor.** Gece yazılmış Türkçe kod adları (hepsi,ipucu,sablonYolu,izinTavani,ilkReturnKonumu,onbellekAnahtari,bolumAdiDogrula,okunabilirDogrula,ustDuzEkler,gevsekDuz,apiTum,ham`) İngilizceye çevrildi. Davranış birebir aynıdır — aynı fail-closed hata mesajları; hata metinlerinde hiçbir sır değeri yazılmaz.

  • FW-ENV-KAYIT-160: getenv( / $_ENV / $_SERVER ile ortam değişkeni okuyan 11 nokta Env'e taşındı (davranış korunarak): CryptoHelper::ortamDegiskeni(), BaseDbData::ortamDegiskeni() (MASTER_DB_* / COMMON_DB_* / DB_*), SystemGuardHandler::resolveFailClosed() (RBN_GUARD_FAILCLOSED), PreBoot::envOverrideRequested() (RBN_DEBUG / RBN_DEV), AiUsageManager + rbn CLI + DebugHelper (APP_ENV), MediaIngestService (RBN_UPLOADS_DIR, RBN_UPLOADS_URL_BASE), bir proje modülünün jeton/dizin okumaları (ilgili ortam değişkenleri ve REDIRECT_ önekli yedekleri artık kayıtlı ad; sonradan kaldırıldı, bkz. [0.9.4]), Telegram test router'ı (TG_SEND_DELAY_MS).

  • FW-ENV-KAYIT-160 (fail-closed / kill-switch semantiği DEĞİŞMEDİ): RBN_GUARD_FAILCLOSED artık Env::flag($ad, true) ile okunur — tanımsız/boş → true (fail-closed), 0|false|off|no|hayir → false; yorum yine TEK merkezden (normalizeSwitch) gelir, ikinci bir parser yazılmadı. RBN_DEBUG/RBN_DEV bilerek Env::flag DEĞİL, kendi 1|true|on|yes|development listesiyle okunmaya devam eder: bu kapının varsayılanı KAPALI'dır, normalizeSwitch "belirsiz → AÇIK" dediği için kullanılsa hata ayıklama modu yazım hatasıyla açılırdı. RBN_ALLOW_LEGACY_SALT de eski 4-değerlik listesiyle korundu (geriye uyum). A0-2, A0-3, R1/R2 öncelik sıraları ve Secrets bölümü ↔ Env ilişkisi değişmedi.

  • FW-SIRLAR-TEK-CIKIS-149: Çağıranlar yeni çıkışlara taşındı — MasterDbData::pass() → Secrets::masterDb()['pass'], ::smtpPass() → Secrets::smtp()['pass'], CommonDbData::pass() master'a düşerken Secrets::masterDb()['pass'], CpanelData::host()/user()/token()/credentials() → Secrets::section('cpanel'). Geriye uyum: all(), get(), masterDbPass(), masterSmtpPass(), optional(), cpanel(), cpanelHost(), cpanelUser(), cpanelToken() isimleri ve davranışları korundu; Secrets::cpanel() dizisinin cpanel_host/cpanel_user/cpanel_token anahtarları değiştirilmedi (kabul testi bunu sabitler) — kanonik bölüm biçimi section('cpanel') üzerinden okunur. Ortam değişkeni önceliği (MASTER_DB_PASS, COMMON_DB_PASS) ve bağlantı şeması değişmedi.

  • Entegrasyon (integ/fw-asama0): A0-8 (gömülü DB parola/kullanıcı sabitlerinin kaldırılması) ve A0-9 (@import kök sınırı) kalemleri feat/fw-a0-8-9-sirlar-viewimport dalından --no-ff ile birleştirildi; yukarıdaki iki madde bu birleşimle geldi. Değişen PHP dosyalarında php -l temiz, fw-regresyon/birim paketi 8/8 yeşil.

  • A0-9 / R-02: ViewEngine::path() ve ViewEngine::compile() artık kök sınırı denetler: realpath() + .. normalizasyonu sonrası çözülen yol, ilgili tipin kök dizini (proje / framework / public / modül / suite) dışına çıkıyorsa RuntimeException ile reddedilir. @import artık ViewEngine::import() üzerinden tek güvenli girişten geçer (ViewConfig). @import('../../../../evil.php') ile kök dışı dosya require edilemez. Kırıcı: geçersiz bir view yolu artık sessizce boş/yarım sayfa yerine kontrollü hata üretir.

  • A0-8: BaseDbData/CommonDbData/ProjectDbData içindeki gömülü PASS sabitleri ve MasterDbData/CommonDbData içindeki USER sabitleri kaldırıldı. Parola pass() (ortam değişkeni → Secrets:: → fail-closed), kullanıcı adı user() (ortam değişkeni → Secrets::optional() → en az ayarlanmış varsayılan) üzerinden tek çıkıştan okunur. Secrets::optional() opsiyonel kimlik anahtarları için eklendi (zorunlu sırlar fail-closed kalır). Bağlantı şeması (anahtar adları ve sırası) değişmedi.

  • MasterDbData artık gömülü parola sabitlerini taşımıyor; veritabanı ve SMTP parolalarını sır okuyucusundan alıyor. Bağlantı şeması (anahtar adları ve sırası) değişmedi; projelerin kendi *DbData sınıfları sır dosyasına bağımlı değil, davranışları aynen korundu.

  • IP güvenlik katmanı artık log-only modda çalışıyor: kararı üretir ve kaydeder, ancak hiçbir isteği engellemez. enforce modu ayrı bir yayın olarak etkinleştirilir; o zamana kadar kullanıcı etkilenmez.

  • Kara liste/beyaz liste sorguları group() yerine desteklenen iç içe koşul biçimini kullanıyor; hız sınırı ayarı okuması kayıtlı ayar deposu üzerinden yapılıyor.

  • Mail yapılandırması master SMTP parolasını sabit yerine erişim metot üzerinden okuyor. Projenin kendi SMTP yapılandırması varsa master sırına başvurulmuyor (davranış değişmedi).

  • Kaynak kodda parola yazan yorum satırları kaldırıldı; sürüm takibinde çalışma ağacında gömülü sır eşleşmesi 0.

  • Dört veritabanı kimlik sınıfı (BaseDbData, CommonDbData, MasterDbData, ProjectDbData) Core/Services/Gatekeepers/Models/ → Core/System/Config/Definitions/DbProfiles/ altına taşındı; ad alanları Rbn\Framework\Core\System\Config\Definitions\DbProfiles oldu. Sınıf adları değişmedi, bağlantı şeması ve tüm sabitler aynen duruyor. Taşımanın nedeni mimari: bu sınıflar kimlik/yapılandırma verisidir (koruma mantığı değil) ve çekirdek katman onları üst katmandan çağırıyordu; bu bağımlılık yönü giderildi.

  • cPanel kimlik bilgisi (sunucu, kullanici, API tokeni/parola) artik kaynak kodda sabit degil; Core/System/Config/Secrets/cpanel-secrets.php dosyasindan okunur. CpanelData icindeki HOST/USER/TOKEN sabitleri yerine ayni adlari tasiyan host()/user()/token() erisim metotlari kullanilir. Sir dosyasi tembel (gecikmeli) yuklenir: bir cPanel islevi cagrilana kadar okunmaz, bu yuzden cPanel kullanmayan siteler bu dosyaya ihtiyac duymaz.

  • cPanel baglantisini kuran CPanelProvider kimlik bilgilerini acilista degil, ilk gercek kullanimda cozer. Sir dosyasi eksik veya eksik anahtar iceriyorsa cPanel islevi acik hata verir; sessizce yapilandirilamis sayilmaz.

Fixed

  • G-03: Syshub bakım modu anahtarı ve zorlayıcı katman artık aynı kuralla (ShieldSettingsRepository::normalizeSwitch(), fail-closed) yorumlanır. Önceden panel === 'on' ile, zorlayıcı katman normalizasyonla bakıyordu; aynı değer iki zıt karara dönüşüyordu. Bakım ayarı okuması/yazması tek doğruluk kaynağı olan ShieldSettingsRepository'ye taşındı; yazma sonrası ayar önbelleği düşürülür. Zorlayıcı katmanın kararı değiştirilmedi.

  • G-02: Bakım moduna beyaz liste IP'si ekleme ekranı çalışıyor: kayıt defterinde olmayan bir model takma adı çağrısı kaldırıldı (çağrı sessizce boş sonuç dönüyordu).

  • FW-DB-2 (D-01): Hazırlanmış ifade önbelleği anahtarı PDO nesne kimliğini içerir: bağlantı değişince eski bağlantının ifadesi kullanılmaz.

  • FW-DB-1 (D-02): QueryBuilder increment() / decrement() artık SQLSTATE[HY093] vermiyor. Sayaç miktarı konumsal ? yerine adlandırılmış yer tutucu ile gider; SET ve WHERE parametreleri adla eşleştiği için karışmıyor. Model katmanındaki atomik sayaçlar (view count, hit sayacı vb.) çalışır duruma geldi.

  • FW-DB-1 (B-26): QueryBuilder update() / updateAffected() WHERE'siz çağrıldığında hata verir. delete() ile aynı güvenlik sözleşmesi paylaşılır. Dönüş sözleşmesi korunur: update() her zaman bool true döner (0 satırda da), etkilenen satır sayısı updateAffected() ile alınır.

  • Mevcut projeler lisans tablosuna LIFETIME olarak kopyalanır (önek tahmini kaldırıldı); verify() expired/revoked/suspended durumunu tarihten bağımsız geçersiz sayar

  • Veritabanı kimlik bilgisi okuma yolu, master sır dosyası yoksa sessizce boş değere düşmek yerine açık hata veriyor. (Bu, tüm sitelerin aynı anda devreye girmesini engelleyen "güvenli kapalı" davranıştır; doğrulandı.)

  • Sır dosyasındaki eksik anahtar, tip uyuşmazlığı ve okunamayan dosya durumları ayrı ayrı teşhis edilen hatalara dönüştür; "boş proje" gibi yanlış okuma üretmez.

  • IP güvenlik katmanının kullandığı ayar sağlayıcısı ve model adları kayıt defterine eklendi; bu katman artık okuma zincirini kurabiliyor. Daha önce hiçbir istekte etkin değildi.

  • IP katmanının kendi hata yakalama kolonu artık hatayı kayda geçiriyor; önceki sessiz davranış kaldırıldı.

  • robots.txt gerçek sayfa ön eklerini engellemiyor: /sayfa saldırı tarayıcı tuzağı listelerinden çıkarıldı, böylece yasal/içerik sayfaları arama motorlarına ve AdSense'e açık kalıyor.

  • robots.txt sitemap adreslerini ve gizli yönetim yolunu içermez.

  • Hız sınırı, geçersiz veya eylem adı dışı bir çağrıda sessizce devre dışı kalmıyor; kanalizasyon tek noktadan yürüyor.

  • Oturum çerezi bayrakları (oturum sabitleme karşı önlemi) artık istek başına yeniden üretiliyor.

  • Tarihçe (artık geçerli değil): Lisans kapısı bir ara dönemde ürün adı/ön eki beyaz listesine (FREE-OSS / RBN-FREE-) dayanıyordu; bu yaklaşım kaldırıldı. Karar artık licences tablosundaki kayda (tier + status + süre) göre verilir.

  • İstemci tarafı yönlendirme koruması (AJAX/JS) ve tek kademeli kaynak doğrulaması eklendi.

  • E-posta dogrulama ve parola sifirlama baglantilari artik tek kullanimlik ve sureli: dogrulama 24 saat, sifre sifirlama 60 dakika gecerli; kullanilmis, iptal edilmis veya suresi dolmus baglantilar yeniden kullanilamaz. Ayni kullanici icin uretilen yeni bir baglanti, o amactaki bekleyen baglantilari gecersiz kilar. Yeni bir baglanti uretildiginde eski baglantilar gecersiz sayilir; mevcut ve daha once dogrulanmis hesaplar etkilenmez.

  • Parola sifirlama isteginin sonucu, hesabin kayitli olup olmadigindan bagimsiz olarak ayni mesaji dondurur; boylece kullanici sayimina (enumeration) dayali bilgi sizmaz.

  • Sifre yenileme formu gonderimi artik kisisel bilgi koruma (CSRF) dogrulamasi ve hiz siniri uygular; hiz siniri eylem adi gecerli adim listesiyle eslesti, bu yuzden sinir gercekten devreye giriyor.

  • Sifre degisince o kullaniciya ait "beni hatirla" isareti gecersiz kilinir.

  • Dogrulama ve sifirlama baglantilari bos, hatali veya suresi dolmus ise kullaniciya acik hata gosterilir; hata sayfasi uretilmez.

  • B-2 — bilinmeyen hiz siniri eylemi 500 degil: RateLimitValidations::getLimit() ve RateLimitHandler tarafinda eylem adi tanim listesinde yoksa daha once InvalidArgumentException firlatiliyordu; bu istisna framework hata sayfasina dustugu icin bilinmeyen bir eylem adi 500 uretiyordu. Artik tanimsiz eylem guvenli tavana (safe ceiling) normalize edilir; isBlocked() istisna atmaz ve limitsiz kalmaz. Geriye donuk yazim tutarliligi icin recovery ve password_reset ayni kanonik kovada sayilir (cift sayim olmaz). Geliştirici API (getLimit()) geçersiz argümanda fırlatmaya devam eder — çalışma zamanı yolu ile API yolu bilinçli olarak ayrıdır.

  • B-3 — tek kullanimlik token mührü atomik: UserTokenRepository::consume() istegi once SELECT edip sonra UPDATE ediyordu; eszamanli iki istek ayni tokeni iki kez gecebiliyordu. Artik kosullu UPDATE tek bir ifadeyle calisiyor ve CrudTrait::updateAffected() eklenerek etkilenen satir sayisi (0/1) olculebiliyor. Kural: kosulsuz UPDATE yazmaz. Yarista ikinci istek 0 satir etkilendigi icin basarisiz olur.

  • Y-4 — başı / olmayan göreli redirect hedefleri mutlak yola çevriliyor: dashboard/x, sayfa/ekle, iletisim gibi göreli hedefler daha önce güvenli varsayılana (/) düşüyordu — kullanıcı sessizce ana sayfaya gidiyordu. Artık mutlak yola normalize ediliyor. Atlatma listesi korunuyor: //, \, javascript:, @, CRLF, açık port içeren girdiler yine reddediliyor; beyaz listeye giren aynı-origin hedefler geçiyor. Düzeltme RedirectTrait içinde tek choke noktasında yapıldı — redirect() ve AJAX JSON yolu aynı guvenliHedef() süzgecinden geçiyor.

  • B-5 — boş beyaz listede 301 yazılmıyor (kalıcı önbellek riski): RedirectManager::redirect() beyaz liste boşken hedefi denetimden geçirmeden 301 yazıyordu; bu durumda yönlendirme kalıcı olarak istemci/ara katman önbelleğine alınıyordu ve sonradan düzeltilemiyordu. Artık boş beyaz listede kural uygulanabilirliği denetleniyor ve yanlış kabul edilmiş hedef yazılmıyor.

  • B-10 — IP kill-switch yönü düzeltildi: IP koruma ayarı okunamıyordu; önceki yol "okuma hatası"yı "koruma kapalı" olarak yorumluyordu, yani ayar bozulduğunda koruma sessizce kapanıyordu (fail-open). Artık okunamayan ayar korumayı kapatmıyor; "kapalı" anlamına gelen degerler tek bir liste üzerinden belirleniyor, böylece listeye eklenmeyen her deger korumayı açar durumda kalıyor.

  • A0-6 — token/recovery akislarindaki 500 kapatildi (fail-closed + jenerik hata + 302): Bundles/RbnSuite/RbnAuth/Services/RecoveryService.php icinde veritabanina dokunan dort nokta (findByIdentity, reset, verify, peek, consume) try/catch ile sarildi. Sema uyumsuzlugu / eksik kolon / erisim hatasi artik framework'un gelistirme hata sayfasina dusmuyor; kullanicinin gormesi mumkun olan jenerik mesaj donuyor ve handleResult 302 yonlendirmesi yapiyor. Davranis degisikligi: kullanici var/yok bilgisi sizmaz (A-26), istek "gonderildi" gorunur, yalnizca token uretilmez. Yakalanmayan Throwable sessizce yutulmaz — belirtilen hata mesaji (password_recovery / email_verification) kullaniciya gider. Kirici: token akislari sema hatasinda da eski hata sayfasi yerine yonlendirme donuyor.

  • Lisans geçerlilik kuralı tek yerde toplandı: bozuk expires_at değeri ve NULL status / tier alanları artık kısıtlı veya geçersiz sayılır (fail-closed); daha önce tarih çözümlenemediğinde kayıt süresiz sayılabiliyordu. Eksik/bozuk kayıt ücretsiz (kısıtsız) sayılmaz, proje durum kontrolüne girer.

  • licences tablosunda (subject_type, subject_id) UNIQUE: aynı özne için ikinci bir lisans satırı yazılması veritabanı düzeyinde engellenir; karar katmanının "hangi kayıt geçerli" sorusu tekilleşir.

  • Rutin lisans kısıtlaması günlüğe yazılmaz: beklenen "kısıtlı" kararları log gürültüsü üretmiyor; yalnız anormal durumlar (kayıt yok, okunamıyor, şema hatası) günlüğe geçiyor.

Security

  • F-08: Developer/master-developer rolleri artık CSRF doğrulamasından muaf değildir; tüm roller token ile doğrulanır. CsrfHandler::verify() içindeki "developer bypass" bloğu kaldırıldı ve FormGuardHandler::audit() bu rollere verdiği diğer koruma muafiyetlerini (bot/orijin/user-agent/hız sınırı) korurken CSRF adımını artık atlamaz. Token üretimi, geçerlilik süresi ve diğer rollerin davranışı değişmedi; doğrulama fail-closed'dur (token yoksa/yanlışsa/süresi dolmuşsa istek reddedilir). Etki: developer, admin, superadmin ve master-developer oturumuyla açılan bir tarayıcı, artık kurbanın tarayıcısı üzerinden CSRF ile işlem yaptıramaz.

  • FW-DB-1: Sorgu katmanındaki toplu yazma yolu kapatıldı: WHERE koşulu yazılmadan update(), updateAffected(), increment() veya decrement() çağrıldığında istek hata ile durur ve hiçbir satır değişmez. Böylece tek bir eksik koşul tablonun tamamını ya da tüm sayaçları yanlışlıkla değiştiremiyor.

  • FW-INSTALLER-KALDIR-2: İstek adresi içeriğine bağlı olan ve bir alt dizgeye göre karar veren Installer bypass kaldırıldı; çekirdek tanılamalar (asset, bakım, izin, sistem, shield, veritabanı) artık yalnızca kendi gerçek koşullarında çalışır ve istisna durumunda kapatılan yönde davranır.

  • SystemGuardHandler istisna ve IP kuralları sıkılaştırıldı: (1) framework/proje statik varlık istisnası artık yalnız URL yolunun başına bakar (parse_url(..., PHP_URL_PATH) + önek eşleşmesi, AssetDoctor ile aynı kalıp); sorgu parametresi içine gizlenen bir istisna metni bakım modunu ve VIP kontrolünü atlamıyor. (2) REMOTE_ADDR boşsa istek artık VIP sayılmıyor — daha önce istek hiç reddedilmeden bakım ve VIP kontrolü atlanıyordu; boş IP fail-closed yönde ilerler (bakım açıkken engellenir, kapalıyken serbest kalır). Konsol/cron yolu (PHP_SAPI === 'cli', ör. master:migrate) eski davranışını korur. Kritik yol (/auth, /rbn-admin, /api, dashboard öneki) eşlemesi de ham adres yerine yola çevrildi. Davranış değişikliği yalnız daha kapalı yöndedir: gerçek statik varlık ve gerçek VIP istekleri değişmedi.

  • Hata ayıklama modu ortam tespiti sertleştirildi; hata sayfasında hassas alanlar maskelendi

  • Kaynak koda gömülü veritabanı ve SMTP parolaları framework dışına çıkarıldı ve sır dosyasına taşındı (sır okuyucu fail-closed).

  • IP güvenlik katmanı okuma zinciri düzeltildi ve etkinleştirildi. Durum: log-only — ilk sürüm kararı üretip kaydeder, hiçbir isteği engellemez; gerçek engelleme ayrı bir ayrı yayın olarak etkinleştirilir.

  • Veritabanı parolası sabiti (PASS) artık dışarıdan okunamaz: parolaya yalnızca pass() erişim metodu üzerinden ulaşılır. Daha önce alt sınıflar bu sabiti miras yoluyla sessizce boş değere düşürebiliyordu.

  • Açık yönlendirme doğrulaması sertleştirildi (aynı-origin beyaz listesi, çoklu alan adı destekli). Durum: bekliyor — yamalar çalışma ağacında hazır ve testli, ancak sürüm takibine commit edilmedi; yayın öncesi commit + kabul testi zorunlu.

  • Form koruması kalıcı uzun engelleme yerine kademeli cezaya geçirildi (yanlış pozitif ve meşru kullanıcı kilitlenmesi riskini azaltır). Durum: bekliyor — commit edilmemiş.

  • Açık yönlendirme koruması medya/JS isteklerine de uygulanıyor; yönlendirme tek kademeli kaynak doğrulamasıyla sınırlandırıldı.

  • Tarihçe (artık geçerli değil): Lisans kapısının kapsam dışı paketleri geçirmemesi bir ara beyaz listeyle sağlanıyordu; bu yöntem kaldırıldı ve yerini tablo tabanlı geçerlilik kontrolüne bıraktı.

  • PermissionDoctor kullanılmayan zorunlu kod katmanı klasörlerini açmıyor; çalışma zamanı katmanları (Storage, public) korunarak her zaman açılıyor, mevcut klasörler silinmiyor.

  • R1 — fail-closed şifreleme tuzu: Core/Support/Bridges/Helpers/Library/CryptoHelper.php içindeki gömülü (sabit string) şifreleme tuzu kaldırıldı. Tuz artık APP_KEY → ENCRYPTION_KEY → sır dosyası app_key sırasıyla okunur. YAYIN UYARISI: bu üçünden hiçbiri tanımlı değilse şifreleme artık sessizce zayıf anahtarla devam etmez, RuntimeException fırlatır. Yayından önce her projede anahtarın tanımlı olduğunu doğrula. Geçici geriye-uyum kill-switch'i RBN_ALLOW_LEGACY_SALT ve RBN_LEGACY_SALT ortam değişkenleridir; ikisinden yalnız biri yetmez, kalıcı yapı değildir (ayrıntı: güvenlik incelemesi raporu).

  • R2 — gömülü kurulum parolası kaldırıldı (tarihçe; bu yol artık yok): Installer, gömülü varsayılan yönetici parolasını içeren ekranıyla birlikte tamamen kaldırıldı (FW-INSTALLER-KALDIR-2). Bu madde yalnızca o aracın neden kaldırıldığını anlatır; bugün geçerli bir kurulum parolası üretme adımı yoktur.

  • B71-#4 doğrulama notu (kademeli ceza sayacı): FormGuardHandler::kademeliCezaDakika() artık metin taramasıyla değil metot çalıştırılarak ölçüldü (5 senaryo, master DB, RFC5737 IP'leri, geçici kayıtlar finally ile silindi): 4 dolmuş + 0 aktif → 15 dk, +1 aktif → 60 dk, +2 aktif → 1440 dk, 0 kayıt → 15 dk, 5 aktif → 1440 dk. "Kullanıcı 25 saat sonra yine 24 saat yasak alıyor" senaryosu artık mümkün değil. Bilinçli değiştirilmedi: sayaç master.ipBlock tablosunun tamamını sayar; blockIp() kaydı project_id'li yazdığı için aynı IP'nin başka projedeki aktif kaydı da kademeyi yükseltir. "Global" ceza için muhtemelen doğru, ama bilinçli olarak değiştirilmedi.

  • G-08 — GeoIP sağlayıcı seçimi: kapatıldı: ipwho.is (TLS). GeoIPHandler artık ücretsiz ve TLS destekleyen ipwho.is sağlayıcısına bağlanıyor; düz HTTP kalemi kapandı. Taban URL GEOIP_UC_NOKTA sabitine çıkarıldı: https://ipwho.is/{ip}?fields=success,message,country_code (eski düz-HTTP ip-api.com yerine). JSON alan adı değişti: ip-api'nin countryCode'i değil, ipwho.is'in country_code alanı okunuyor. Sertifika doğrulaması verify_peer/verify_peer_name açık yazıldı ve kapatılması yasak (kapatılırsa G-08 düz-HTTP'ye sessizce geri döner); SNI için ayrıca GEOIP_SUNUCU = 'ipwho.is' sabiti var. Korunan çerçeve: follow_location => 0 (uç nokta yönlendirirse istemci sessizce düz HTTP'ye düşmez), 2 sn timeout, 30 günlük önbellek (ipwho.is kotası istemci IP başına 1000 istek/gün ile uyumlu), BILINMEYEN_ULKE sabiti ('XX') ve hata yolunda isteğin durdurulmaması (fail-closed değil — istek akışı devam eder, yalnız ülke bilgisi atlanır). G-08'in (a) ip-api PRO satın alma, (c) ülkeyi bilerek XX yapma seçenekleri uygulanmadı; seçilen yol (b) idi. Birim testi: fw-regresyon\birim\fw_geoip_ipwho.php (ağ isteği atmaz, 0 başarısız). Dış istek doğrulaması yapılmadı; karar sağlayıcının kendi dokümanına dayandırıldı.

  • Sırlar tek çıkış + B-4 bütünleşti — entegrasyon kaydı: feat/fw-sirlar-tek-cikis (90ed0c8 + 488965b) integ/fw-asama0 dalına git merge --no-ff ile birleştirildi (7644202). Yalnız ortak dosya .github/CHANGELOG.md idi ve git otomatik birleştirdi; elle çakışma çözümü gerekmedi. R1 ile uyum denetimi: R1 CryptoHelper fail-closed (gömülü tuz yok) ve yeni Secrets::appKey() aynı optional('app_key') yolunu kullanır ve ikisi de çözülemeyen anahtarda RuntimeException fırlatır — ikisi birbirini zayıflatmaz. Doğrulama: değişen 17 PHP dosyasında php -l temiz; fw-regresyon\birim paketinin tamamı yeşil (13 dosya, fw_sirlar_tek_cikis.php 41 kontrol 0 başarısız 0 atlandı; fw_sirlar_tek_cikis birleşim öncesi 0/41 idi).

  • Denetim bulgu düzeltmeleri (B-2 / B-3 / Y-4 / B-5 / B-10) birleştirildi: feat/fw-denetim-bulgular-1 (b135aef) ve feat/fw-denetim-bulgular-2 (8d8b8da + eded702) integ/fw-asama0 dalına --no-ff ile çakışmasız birleştirildi (09ba3ee, 4e5af1d). Etkilenen dosyalar: CrudTrait, UserTokenRepository, RedirectTrait, RateLimitHandler, RateLimitValidations, ShieldSettingsRepository, RedirectManager, IpGuardHandler, SystemGuardHandler. Birim testleri: fw_banu_b2_b3_y4.php ve fw_banu_b5_b10.php — ikisi de yeşil (0 başarısız). Ayrıntılı gerekçeler Fixed bölümünde. Sırlar tek çıkış (feat/fw-sirlar-tek-cikis) o anda hazır olmadığı için bu birleşime alınmamıştı; aşağıdaki bütünleşik kaydı ile ayrı bir --no-ff birleşimiyle (7644202) alındı ve fw_sirlar_tek_cikis birim testi yeşildir.

  • Onarım planındaki ele alınmamış güvenlik kalemleri (fail-closed varsayılanlar, oturum sertleştirme, hız sınırı eylem eşlemesi, token doğrulaması, view yol geçişi kontrolü) bu sürümde kapsam dışıdır; ayrı yama görevlerinde ilerliyor. Bu dosya o kalemler için "bitti" anlamına gelmez. (Token dogrulama kalemi bu yamada kapandi; "hiz siniri eylem eslemesi" yalnizca bu kalemin kendi eylem adinda duzeltildi.)

  • B-4 — Secrets::optional() artık güvenlik hatalarını yutmaz (fail-closed): optional() all()'in tüm hatalarını catch(\Throwable) ile yutuyordu; sır dosyası 0644 (fazla geniş izinli), bozuk biçimli, dizi döndürmeyen ya da okunamaz olduğunda null dönüyor ve çağıran (BaseDbData::pass(), Secrets::appKey()) kendi genel "çözülemedi" hatasını atıyordu — yani operator asıl sorunu (geniş izin / bozuk dosya) hiç görmüyor, sır dosyasını yeniden yazıyor ve sorun bitmiyordu (yanlış teşhis + sessiz düşüş). Artık optional() yalnız "sır/bölüm TANIMLI DEĞİL" durumunu null ile yutar; dosya düzeyindeki her sorun (okunamama / fazla geniş izin / biçim / dizi değil) açık RuntimeException fırlatır. İç yol gevsekDuz() all() ile aynı düz-anahtar haritasını üretir ama zorunlu bölüm doğrulaması uygulamaz (aksi halde optional('app_key'), dosyada tanımlı olan anahtarı null yapardı); okunabilirDogrula() dosya var ama okunamıyorsa "bulunamadı" değil açık "OKUNAMADI" güvenlik hatası verir (Linux 0600 reddi dâhil). CryptoHelper::sirDosyasindanAnahtar()'daki kendi catch de kaldırıldı: kaynak hata (izin/biçim) yukarı kaybolmaz. Geriye uyum korunur — optional() yalnız "tanımsız" durumunda null döndüğü için mevcut çağıranlar bozulmaz; davranış değişikliği yalnız hata yutma katmanındadır. Doğrulama: fw_sirlar_tek_cikis.php F1–F11 (opsiyonel yok → null, bozuk/okunamaz dosya → istisna, tanımlı → değer) 0 başarısız 0 atlandı.

  • B-4 kırıcı etkisi (yayın öncesi okunmalı): sır dosyasının izni fazla geniş veya dosya bozuk olan kurulumlarda bu sürüm daha önce sessizce çalışan, artık açık hata veren akışlara dokunur. Bu kasıtlıdır (fail-closed), ama chmod 600 uygulanmamış kurulumlar yayın sonrası hata görecektir. Yükseltme adımı: UPGRADING.md 0.9.0 Adım 7.

Removed

  • FW-INSTALLER-KALDIR-2: rbnframework/Installer/ klasörünün tamamı kaldırıldı (kullanılmayan, üretici olmayan araç). InstallerStage silindi ve KernelFactory boot hattından çıkarıldı; Installer\Services\InstallationService ve beş Installer\Handlers\* kaydı SystemLogicMapTrait sınıf/kayıt haritasından elle silindi. Autoload PSR-4 Installer\ girdisi, FolderMatrix INSTALLER girdisi, FrameworkContext::installer() erişimcisi ve RouteBlueprint::SYSTEM_ALLOWED_PATHS içindeki web kurulum yolu kaldırıldı. Ölü konsol komutları (system:check, install:project, shield:sync) SystemHandlers kayıtlarından ve rbn master komut listesinden silindi. Yeni bir kurulum/kısa yol YAZILMADI: yeni projeler elle kurulur.
  • FW-CONFIG-YAPISI (güvenlik incelemesi): Boş Core/System/Config/Definitions/ConfigFile.php kabuğu kaldırıldı (yalnız ConfigFileLoader trait'ini use ediyordu, tek satır tanım dışında kullanıcısı yoktu). Sır OLMAYAN yapılandırma dosyaları için ortak giriş noktası artık Engine/ConfigFileLoader trait'idir; Definitions/ yalnız sabit/şema sınıflarını barındırır (proje sahibi kuralı: "sabitler ayrı dosya, metotlar ayrı dosya").
  • FW-TEK-SECRETS-DOSYASI-161: Core/System/Config/Secrets/master-secrets.example.php ve cpanel-secrets.example.php şablonları; rbnframework/.gitignore içindeki iki sır dosyası satırı; Secrets.php içindeki iki eski dosya sabiti ve tüm "geriye uyum" okuma yolu (eskiMasterDuz(), eskiCpanelDuz(), eskiBolum()).
  • B-02: SessionSandboxStage içindeki defined('RBN_INSTALL_REQUIRED') dalı kaldırıldı: sabit ağaçta hiçbir yerde tanımlı değil, yalnız burada okunuyordu. Bu dal, tanımlı olsaydı oturum aşamasını tamamen atlayacaktı; kaldırılınca oturum/çerez bayrakları her zaman uygulanır (daha kapalı yön). İlgili iki doküman satırı da güncellendi. Kanıt: kalıntı taraması 0 eşleşme, php -l temiz, duman testi REGRESYON=0 (çerez bayrağı senaryosu 18 sitede PASS).
  • B-03: rbn-terminal.css içindeki .rbn-terminal-btn-install (+ :hover) kuralları kaldırıldı: tek kullanıcısı, silinmiş Installer ekranıydı; ağaçta başka kullanım yok.
  • Kaynak kodundaki parola sabitleri ve parolaları yansıtan yorum satırları kaldırıldı.
  • Gerçek sır dosyası sürüm takibinden ve yayın paketinden çıkarıldı.
  • Alti haneli kodla dogrulama ekrani kaldirildi; parola sifirlama akisi tek bir guvenli baglanti uzerinden yuruyor. Eski (kod iceren) baglantilar gecersizdir.

  • Gelen makine API korumasi ApiGuard icinde tek merkezde: beyan, anahtar kimligi, kapsam, hiz/govde siniri, denetim kaydi ve makine-okunur hata kodu.

    1. machine-api beyani (MachineApiRegistry). "Hangi uc makine API'sidir?" sorusunun once uc bagimsiz cevabi vardi (isAgentEndpoint() sabit listesi, session-policy.php yol onekleri, route middleware = yok). Artik tek beyan tablosu var; IP katmani once beyani sorar, olmezse eski sabit listeye duser (sabit liste SILINMEDI = geri uyum emnetyeti). Beyan ile IP muafiyeti ayri kararlardir (ip_exempt): /ws/agent beyan edildi (session katmaniyla tutarsizlik kapandi), crew/worker uclari da beyan edildi ama muafiyetleri kapali — gozlem verisi toplanmadan WAF/ban kapsami daraltilmaz. RouteBlueprint altina parametresiz machine-api alias'i eklendi.
    2. Anahtar basina kimlik (MachineApiKeyStore). external-api ayarina geri uyumlu keys bolumu: sha256:<hex> saklama, sabit zamanli hash_equals karsilastirmasi (diziden !== cikarildi), kapsam (scopes), iptal (revoked_at), sure (expires_at), anahtar basina hiz siniri ve govde siniri. Duz metin api_key dali acik kalir.
    3. ?token= log-only. Anahtar once basliktan okunur; sorgu/govde parametresi geri uyum icin okunmaya devam eder ama her kullanimda denetim kaydi + uyari dusurur (allow_query_token, varsayilan true — reddetme YAPILMAZ).
    4. Hata sozlesmesi. Yanitlara makine-okunur code alani eklendi (UNAUTHORIZED, FORBIDDEN_SCOPE, IP_NOT_ALLOWED, ...). Mevcut message metinleri degismedi; eski istemciler aynen calisir.
    5. Hata mesajinda istemci IP'si yansitilmiyor. Once Bu IP adresinden erisim izniniz yok: <ip> seklinde ic ag topolojisi sizdiriyordu.
    6. Yeni korumalar log-only. Govde/icerik tipi siniri, anahtar basina hiz siniri ve denetim kaydi enforce=false ile baslar; hicbir istek dusurulmez, yalniz kayit duser. Denetim kaydi (machine-api kanali) anahtar degerini degil key_id'yi yazar.
  • Ajan ucunun 401 yanitinda anahtari geri yansitilmasi kaldirildi. Go ajaninin /api/agent/* ucu, kimlik dogrulanamayan isteklerde gonderdigi anahtari istemciye geri yansitiyordu; bu deger ajan loguna duz metin dusuyordu. Alan kaldirildi; 401 yanitindaki hata metni aynen korundu ve ajan tarafi Go testleriyle dogrulandi (alan yapisal olarak okunmuyor, yalniz maskeleniyordu).

  • Crew gorev ucunde artik gercek bir govde boyut siniri var. Yorumda "govde JSON olmali, boyut sinirli" deniyordu ama kodda boyut siniri yoktu; beyan edilen govde boyutu 1 MB'yi asiyorsa istek reddediliyor ve govde hic okunmuyor.