# SQL korumaları

> AST denetleyicisinin her gönderimde uyguladığı kurallar — WHERE zorunluluğu, LIMIT enjeksiyonu, transaction'lar ve yasaklı komutlar.

- Dokümantasyon: Referans
- Güncelleme: 2026-10-18
- Kaynak: https://queryproxy.com/tr/docs/sql-korumalari/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Her gönderim, onay kuyruğuna girmeden **önce** parse edilip denetlenir. Kural
ihlal eden talep, açık bir mesajla gönderim anında reddedilir — DBA'e bile
ulaşmaz, veritabanına hiç ulaşmaz.

## Kurallar

### UPDATE / DELETE için WHERE zorunlu

```sql
DELETE FROM users;                    -- ✗ reddedilir
DELETE FROM users WHERE id = 42;      -- ✓ kabul (yine de onay gerekir)
```

Bu kural transaction içinde de geçerlidir. Statik olarak her zaman doğru olan
bir `WHERE` eksik sayılır:

```sql
DELETE FROM users WHERE 1 = 1;        -- ✗ reddedilir
DELETE FROM users WHERE true;         -- ✗ reddedilir
DELETE FROM users WHERE id = 1 OR 1 = 1;  -- ✗ reddedilir
```

Denetim; sabit değerleri (sayılar, düz string'ler, `TRUE`, `FALSE`, `NULL`),
karşılaştırmaları, `IS [NOT] NULL|TRUE|FALSE`, `AND`, `OR`, `NOT` ve parantezleri
değerlendirir. Değerlendiremediği bir operand (bir kolon, bir fonksiyon) hiçbir
zaman doğru sayılmaz: tespit statiktir ve en iyi çabayla (best-effort) çalışır.
Değerlendirilemeyecek kadar derin iç içe geçmiş ya da çok büyük bir `WHERE` de
reddedilir (`… with a WHERE clause too complex to verify is not allowed`).
Bu kural geçersiz kılınamaz. Tespit statik olduğu için yaygın hataları durdurur;
bir yazmanın yalnızca birkaç satırı etkilediğini kanıtlamaz.

Kural; `EXPLAIN ANALYZE` ve MariaDB `ANALYZE` içindeki ifade, CTE gövdeleri,
T-SQL batch'leri ve dinamik SQL için de geçerlidir. PostgreSQL `DO` blokları
istisnadır: bu kural için denetlenmezler, yazma olarak sınıflandırılırlar ve her
zaman insan onayından geçerler; incelemeci gövdenin tamamını görür. Bir `DO`
gövdesindeki dinamik SQL (PL/pgSQL `EXECUTE`) ise bu pazarlığın öbür yüzüdür:
çalıştırdığı ifade çalışma anında oluşturulur ve okunamaz, bu yüzden doğrudan
reddedilir (bkz.
[Anahtar kelimeye değil, tehlikeli kısma bakılan kurallar](#anahtar-kelimeye-değil-tehlikeli-kısma-bakılan-kurallar)).

### SELECT'e LIMIT eklenir

- `LIMIT` yok mu? Varsayılan (`1000`) otomatik eklenir — kendi satırında, ki
  sondaki bir satır yorumu onu yutamasın.
- `LIMIT` üst sınırın (`10000`) üzerinde mi? Aşağı çekilir.
- `LIMIT ALL` (PostgreSQL) sınırsız sayılır ve sınırlanır.
- Offset korunur: `LIMIT 10, 20000` → `LIMIT 10, 10000`,
  `LIMIT 99999 OFFSET 4` → `LIMIT 10000 OFFSET 4`.
- `FETCH FIRST n ROWS ONLY` ve T-SQL `TOP n` de aynı şekilde sınırlanır.
- SQL Server hedeflerine `LIMIT` eklenmez (geçerli T-SQL değildir); guard bunun
  yerine `TOP` üzerinde çalışır. Mevcut bir `TOP n` / `TOP (n)` üst sınıra
  (`10000`) çekilir; `TOP` içermeyen bir üst düzey SELECT ise varsayılan değer
  olan `TOP (1000)`'i, varsa `DISTINCT` / `ALL`'dan sonra alır.
  `TOP … PERCENT`, `TOP (@v)`, ifadeler ve ondalıklar **reddedilir**. `WITH`,
  `OFFSET`, `UNION`, `EXCEPT` ve `INTERSECT` için `TOP` eklenmez; bunları işçinin
  satır tavanı (üst sınır) sınırlar ve sonuç "kırpıldı" olarak işaretlenir.
- Guard'ın düz bir satır sayısı olarak anlayamadığı bir `LIMIT` (aritmetik —
  `LIMIT 100*100000` —, ifadeler, yer tutucular) sınırlanmak yerine
  **reddedilir**: doğrulanamayan bir limit uygulanamaz.
- Alt sorgu LIMIT'lerine dokunulmaz; yalnızca dış sorgu denetlenir. Kilitleme
  ifadeleri gözetilir — eklenen LIMIT, `FOR UPDATE`'ten *önce* yerleşir.

Bunun üstüne işçi, sonuçları akıtırken SQL'den bağımsız **mutlak bir satır
tavanı** (üst sınır) uygular — yani cümle düzeyindeki guard'ı atlayan bir sorgu
bile (dialect tuhaflıkları, `SHOW`, `EXPLAIN`) tavandan fazlasını döndüremez;
saklanan sonuç "kırpıldı" olarak işaretlenir.

İki sayı da yapılandırılabilir — bkz. [Yapılandırma](/tr/docs/yapilandirma/).
Hazırlanan SQL (eklenen LIMIT ile) gönderimden önce geliştiriciye, incelemede
DBA'e gösterilir; onayladığınız şey birebir çalışan şeydir.

### Çoklu ifadeler açık transaction ister

```sql
UPDATE a SET x = 1 WHERE id = 1;
UPDATE b SET y = 2 WHERE id = 2;      -- ✗ reddedilir: iki çıplak ifade
```

```sql
BEGIN;
UPDATE a SET x = 1 WHERE id = 1;
UPDATE b SET y = 2 WHERE id = 2;
COMMIT;                               -- ✓ kabul, atomik çalışır
```

İşçi ifadeleri gerçek bir transaction içinde çalıştırır: herhangi biri
başarısız olursa tamamı geri alınır. Gönderimde `ROLLBACK` reddedilir —
başarısızlıkta geri alma otomatiktir, elle yazılacak bir şey değildir. İç içe
transaction desteklenmez.

### Her zaman yasak

Bu komutlar sunucu düzeyinde kod çalıştırma ya da dosya erişimi verir ve bir
sorgu portalı üzerinden gündelik bir karşılıkları yoktur. Rolden bağımsız
olarak, koşulsuz reddedilirler:

- `DROP DATABASE` / `DROP SCHEMA`
- `GRANT`, `REVOKE`, `DENY`
- `CREATE USER` / `ALTER USER` / `DROP USER` (ve `ROLE` / `LOGIN` türevleri)
- `CREATE FUNCTION … SONAME` — yüklenebilir UDF'ler (ve `CREATE AGGREGATE FUNCTION`
  karşılığı)
- `SHUTDOWN`
- Sunucu tarafı dosya / program IO: `SELECT … INTO OUTFILE` / `INTO DUMPFILE`,
  `LOAD DATA`, PostgreSQL `COPY` (`COPY … TO PROGRAM` dahil), `LOAD_FILE()`
  fonksiyonu ve PostgreSQL'in dosya fonksiyonları `pg_read_file()`,
  `pg_read_binary_file()`, `pg_ls_dir()`, `lo_import()` ve `lo_export()` — şema
  nitelikli çağrılar (`pg_catalog.pg_read_file(…)`) dahil. Yalnızca çağrı biçimi
  yakalanır; aynı ada sahip bir kolon ya da takma ad engellenmez.
- Kendilerine string olarak verilen SQL'i çalıştıran ve guard'ın okuyamadığı
  PostgreSQL fonksiyonları: `query_to_xml`, `query_to_xmlschema`,
  `query_to_xml_and_xmlschema`, `xpath_table`, `ts_stat`, `ts_rewrite`,
  `crosstab*` ve `connectby`.

Bu denetimler ifadenin **yorumdan arındırılmış** biçimi üzerinde çalışır; yani
`DROP/**/DATABASE` ya da baştaki bir `/* … */` yorumu gibi hileler yasak bir
ifadeyi denetimden geçiremez.

`DROP TABLE`, `TRUNCATE` ve `ALTER` doğrudan yasak **değildir** — onay
gerektiren yazma olarak çalışırlar, ancak DDL içeren bir talep gönderim anında
işaretlenir: onay kuyruğunda ve talep detay sayfasında **DDL** rozeti görünür,
Slack ve Teams'e giden gönderim bildirimleri de DDL ibaresi taşır (sonraki
onaylandı/reddedildi bildirimleri taşımaz). DDL içermeyen talepte işaret yoktur.

Veritabanı yönetimi altyapı araçlarınızın işidir, bir sorgu portalının değil.

### Anahtar kelimeye değil, tehlikeli kısma bakılan kurallar

Üç ifade yalnızca içlerindekine bakılarak reddedilir; çünkü sözdizimleri,
guard'ın elinden almaması gereken sıradan DBA işini de kapsar.

**`CREATE EXTENSION` — extension adına göre.** Untrusted prosedürel diller
tanımları gereği veritabanı superuser'ı olarak çalışır ve reddedilir:
`plpythonu`, `plpython2u`, `plpython3u`, `plperlu`, `pltclu`, `plsh`, `plr`,
`plluau`, `plrubyu`, `plphpu`, `pljavau` — ve ayrıca PostgreSQL'in untrusted dil
için kullandığı `pl…u` adlandırma konvansiyonuna uyan her ad; yani henüz kimsenin
listelemediği bir bağlama (`plpython4u`) görüldüğü yerde reddedilir. Geri kalan
her şey normal biçimde kurulur: `pg_stat_statements`, `pgcrypto`, `uuid-ossp`,
`postgis`, `plv8`, `plpgsql`. Adı okunamayan bir `CREATE EXTENSION` reddedilir.

**`DO` blokları ve rutin gövdeleri.** Bir PostgreSQL `DO` bloğu,
`CREATE FUNCTION` ya da `CREATE PROCEDURE` başka bir dilde bir gövde taşır; bu
yüzden guard gövdeyi MySQL'in değil PostgreSQL'in kendi sözcüksel kurallarıyla
(yorumlar, `'…'`, `E'…'`, `U&'…'`, tırnaklı tanımlayıcılar ve iç içe
dollar-quoted string'ler) okur ve üst düzey SQL'e uygulanan yasaklı fonksiyon ve
korunan ayar kurallarını aynen uygular. Böylece
`DO $$ BEGIN DROP DATABASE prod; END $$` ve bir ters eğik çizgi ya da yorumun
ardına saklanmış bir `pg_read_file(…)` çağrısı geçemez. Bunun üstüne:

- Yalnızca `plpgsql` ve `sql` dilleri kabul edilir. `LANGUAGE` maddesi
  olmayan bir `DO` bloğu PostgreSQL varsayılanı olan `plpgsql` ile çalışır;
  `CREATE FUNCTION` / `PROCEDURE` ise SQL-standard bir `RETURN` gövdesi yoksa
  açık bir `LANGUAGE plpgsql` ya da `sql` gerektirir. Başka her dil, trusted olsun
  olmasın, reddedilir; guard'ın düz bir ad olarak okuyamadığı dil de öyle.
  Tırnaklı bir ad büyük/küçük harfini korur, dolayısıyla `LANGUAGE "PLPGSQL"`
  reddedilir. `CREATE LANGUAGE` ve `ALTER LANGUAGE … RENAME` de reddedilir.
- Dinamik SQL (`EXECUTE`) kullanan bir gövde, trigger fonksiyonları dahil,
  çalıştırdığı ifade çalışma anında oluşturulduğu için reddedilir. Trigger
  `EXECUTE FUNCTION|PROCEDURE` maddeleri bundan etkilenmez.
- `BEGIN ATOMIC` gövdeleri ve gövdesiz `CREATE FUNCTION` / `CREATE PROCEDURE`
  reddedilir.
- Bir rutin (`CREATE/ALTER FUNCTION|PROCEDURE … SET`), bir veritabanı
  (`ALTER DATABASE … SET`) ya da bir rol (`ALTER ROLE|USER … SET`) üzerindeki
  `SET` maddesi aşağıdaki korunan ayar kuralından geçer. Ayar çözümlenemezse
  ifade reddedilir.

**`SET` — değişken adına göre.** Reddedilenler; bir dosya yolu belirleyen, kodu
sunucu sürecine yükleyen ya da bir korumayı kapatan değişkenlerdir.

- *MySQL ve MariaDB:* `general_log`, `general_log_file`, `slow_query_log`,
  `slow_query_log_file`, `log_error`, `init_file`, `init_connect`, `plugin_dir`,
  `plugin_load`, `plugin_load_add`, `secure_file_priv`, `local_infile`. Yalnızca
  kalıcı scope'lar değerlendirilir — `GLOBAL`, `PERSIST` ve `PERSIST_ONLY`; hem
  anahtar kelimeli (`SET GLOBAL x = …`) hem de değişken yazımında
  (`SET @@GLOBAL.x = …`). Bir `SESSION` yazması, `sql_mode` ve salt okunur
  anahtarı hariç (aşağıya bakın), korunmaz; `@name` bir kullanıcı
  değişkenidir, asla bir sunucu ayarı değil. `SET STATEMENT … FOR <statement>`
  (MariaDB) iki kez değerlendirilir: atamaları yukarıdaki kuralla,
  `FOR`'dan sonraki ifade ise tek başına gönderilmiş gibi.
- *PostgreSQL:* `session_preload_libraries`, `local_preload_libraries`,
  `dynamic_library_path`, `default_transaction_read_only`,
  `transaction_read_only`, `session_replication_role`,
  `allow_system_table_mods`, `lo_compat_privileges`, `zero_damaged_pages`,
  `ignore_checksum_failure`, `role`, `session_authorization`. Bunlar **her**
  scope'ta reddedilir — `SET x`, `SET LOCAL x`, `SET SESSION x`,
  `SET x TO …` — çünkü kötüye kullanımları tam da oturum düzeyindeki
  değişikliktir. Hedef veritabanı bilinmediğinde de aynısı geçerlidir.
- *Her driver:* `set_config('<name>', …)` listedeki her ad için, üçüncü
  argümanın her iki değerinde de reddedilir.
- *Her driver, her scope:* `sql_mode`. `NO_BACKSLASH_ESCAPES` ve `ANSI_QUOTES`
  ters eğik çizginin ve çift tırnağın ne anlama geldiğini değiştirir; guard bunu
  izleyemez, bu yüzden `SET sql_mode`, `SET SESSION sql_mode`, `SET @@sql_mode`
  ve `set_config('sql_mode', …)` reddedilir. Okumak geçer. Çalıştırma anındaki
  karşılık gelen denetim için bkz.
  [sonraki bölüm](#guardın-sunucudan-farklı-okuduğu-metin).

Eşleme tam ad üzerinden ve büyük/küçük harf duyarsızdır; joker yoktur, bu yüzden
`max_connections`, `wait_timeout`, `time_zone` ve PostgreSQL'de `work_mem` ile
`search_path` geçer.

Bu iki liste kurulumunuz için `QUERYPROXY_EXTRA_UNTRUSTED_LANGUAGES` ve
`QUERYPROXY_EXTRA_DANGEROUS_VARIABLES` ile **genişletilebilir**. Fonksiyon,
prosedür ve SQLite pragma listelerinin kendi `QUERYPROXY_EXTRA_*` ayarları var.
Bkz. [Yapılandırma](/tr/docs/yapilandirma/). Yalnızca ekleme yapabilirler:
yerleşik girdiler tabandır, yapılandırma onları kısaltamaz.

### Kimlik, katalog ve salt okunur korumaları

Kimliğini değiştirebilen, sunucunun kendi tablolarını yeniden yazabilen ya da
salt okunur durumunu kaldırabilen bir oturum buradaki diğer tüm kuralları
geri alabilir. Bunlar guard'ın SQL okuduğu her konumda reddedilir: üst düzeyde,
`DO` ve rutin gövdelerinde, `PREPARE` ve `EXECUTE IMMEDIATE` metninde, CTE
gövdelerinde, `EXPLAIN ANALYZE`, MariaDB `ANALYZE` ve `SET STATEMENT … FOR`
içinde. Bunun kapsamadığı ifadeler [Bilinen sınırlar](#bilinen-sınırlar)
bölümünde listelenir. Aşağıdaki son madde istisnadır: gönderimde değil, talep
çalışırken denetlenir.

- **Rol ve kullanıcı geçişleri.** `SET [SESSION|LOCAL] ROLE`, `RESET ROLE`,
  `SET … SESSION AUTHORIZATION`, `RESET SESSION AUTHORIZATION` (`RESET "role"`
  gibi tırnaklı adlar dahil), MySQL ve MariaDB `SET ROLE` /
  `SET DEFAULT ROLE`, ayrıca `set_config('role', …)` /
  `set_config('session_authorization', …)`. `role` ve `session_authorization`
  korunan ayarların yerleşik listesindedir.
- **`ALTER SYSTEM`**, PostgreSQL'de neyi ayarlarsa ayarlasın.
- **Sistem kataloğuna yazma.** PostgreSQL'de `pg_catalog`, `pg_toast` ve
  `pg_…` tablolarına `INSERT`, `UPDATE`, `DELETE` ve `MERGE`; MySQL ve
  MariaDB'de `mysql` şemasına aynı ifadeler ve `REPLACE`. Nitelikli ve tırnaklı
  yazımlar kapsanır. Kural yazma hedefine bakar; bu yüzden
  `UPDATE t SET … FROM pg_proc` ve `INSERT INTO t SELECT … FROM pg_proc`
  geçer. MySQL ve MariaDB'de bir `mysql.*` tablosunu yalnızca okuyan çok
  tablolu `UPDATE` / `DELETE` de reddedilir; çünkü guard hangi tabloya
  yazıldığını ayırt edemez. Kataloğu okumak (`SELECT * FROM pg_proc`,
  `SELECT rolname FROM pg_roles`) etkilenmez. MySQL ve MariaDB'de `USE mysql`
  de reddedilir (`PREPARE`, `EXECUTE IMMEDIATE` ve `SET STATEMENT … FOR` içinde
  de); şemasız bir ad katalogda çözülmesin diye.
- **`READ WRITE`**: `SET TRANSACTION`, `SET SESSION CHARACTERISTICS`, `BEGIN`
  ve `START TRANSACTION` içinde. `READ ONLY` geçer.
- **MySQL / MariaDB salt okunur anahtarı.** `transaction_read_only` ya da
  `tx_read_only` yazmak her scope'ta ve her yazımda reddedilir (`SESSION`,
  `GLOBAL`, `@@`, `@@session.`, backtick, liste içinde, `SET STATEMENT`).
  `transaction_isolation` ve `SET TRANSACTION READ ONLY` geçer. Bazı
  ifadeler bunun için denetlenmez; bkz. [Bilinen sınırlar](#bilinen-sınırlar).
- **PostgreSQL `ALTER DATABASE|ROLE|USER … RESET`** ile korunan bir ayarın ya da
  `ALL`'un sıfırlanması; ayrıca `CREATE [OR REPLACE] RULE … DO [ALSO|INSTEAD]
  <eylem>` kuralının eylemi, tek başına gönderilen bir ifadeyle aynı kurallarla
  değerlendirilir. `DO` eylemini guard'ın bulamadığı kural reddedilir.
- **SQL Server kimlik taklidi.** `EXECUTE AS`, `EXEC AS`, `SETUSER` ve
  `REVERT`; `EXEC('…')` ve `sp_executesql` içinde de, ayrıca nesne
  tanımlarındaki `WITH EXECUTE AS`. `execute_as` adlı bir kolon ve string
  literal içindeki aynı sözcükler etkilenmez.
- **Varsayılan veritabanı `mysql` olan bir bağlantıda MySQL / MariaDB
  yazması.** Şemasız bir tablo adı oturumun varsayılan veritabanına göre
  çözülür; varsayılan `mysql` ise `UPDATE user SET …` hesap tablosunu
  düzenlerdi. Executor, yazmanın ilk ifadesinden önce bağlantıda `DATABASE()`
  değerini okur ve sonuç `mysql` ise talebi hiçbir şey çalıştırmadan
  başarısız kılar. Bağlantıyı uygulama veritabanına yönlendirin. Tek ifadeli
  okuma ve varsayılan veritabanı olmayan bağlantı denetlenmez.

### Guard'ın sunucudan farklı okuduğu metin

Bazı metinleri guard'ın lexer'ı ile veritabanı farklı okur. Tahmin yürütmek
yerine guard bunları ya reddeder ya da her iki okuma altında inceler:

- **Geçerli UTF-8 olmayan SQL** (`SQL must be valid UTF-8.`) ya da lexer'ın
  içinde hiçbir ifade bulamadığı SQL (`QueryProxy could not read this SQL: …`).
- String sabitlerinin, tırnaklı tanımlayıcıların ve yorumların **dışında** kalan
  bir **byte-order mark ya da Unicode boşluk** (U+FEFF, U+0085, U+00A0, U+1680,
  U+180E, U+2000–U+200B, U+2028, U+2029, U+202F, U+205F, U+3000) — her driver'da
  ve dinamik SQL içinde. Lexer böyle bir karakteri komşu sözcüğe yapıştırırken
  sunucu onu ayraç sayabilir.
- **MariaDB çalıştırılabilir yorumları** (`/*M!100000 … */`). `mariadb`
  driver'ında gövde SQL olarak okunur ve 3 seviyeden derin iç içe geçme
  reddedilir. `mysql` driver'ında `/*M!` düz bir yorum olarak kalır; bu yüzden
  bir MariaDB sunucusunu `mariadb` driver'ıyla bağlayın. Hedef veritabanı
  bilinmediğinde `/*M!` doğrudan reddedilir.
- `mysql` ve `mariadb` driver'larında **sürüm koşullu yorumlar**
  (`/*!NNNNN … */`). Böyle bir yorum iki kez denetlenir: bir kez sunucu gövdeyi
  çalıştırıyormuş, bir kez de atlıyormuş gibi; okumalardan biri reddedilirse ya
  da iki okuma ifade sayısı, transaction durumu, tür veya hazırlanan metin
  bakımından farklıysa ifade reddedilir. Şunlar da reddedilir (önceki sürümlere
  göre bir değişiklik): tek talepte farklı sürümler
  (`SELECT /*!40101 1 */, /*!50503 2 */` bir örnektir), 5 haneli olmayan bir
  sürüm (MariaDB'de 5 ya da 6), başka bir yorumun içine yerleşmiş ya da başka bir
  yorum barındıran sürümlü yorum ve gövdesinde bir string ya da satır yorumu
  içinde `*/` geçen yorum.
- **MySQL ve MariaDB'de `sql_mode`.** Ona yazmayı reddetmenin (yukarıda) yanı
  sıra executor, SQL'i çalıştırmadan önce `@@SESSION.sql_mode` değerini okur ve
  `NO_BACKSLASH_ESCAPES` ya da `ANSI_QUOTES` içeriyorsa hiçbir şey çalıştırmadan
  talebi başarısız kılar. QueryProxy açtığı her bağlantıda sunucunun
  varsayılanının yerine geçen katı bir `sql_mode` ayarlar; bu yüzden denetim
  bugün yalnızca bu ayar değişirse ya da aradaki bir şey modu değiştirirse
  devreye girer; bu bayraklardan birine sahip kalan bir bağlantı, bayrak
  kaldırılana dek reddedilir. Diğer driver'larda böyle bir denetim yoktur.
- **PostgreSQL ters eğik çizgileri ve `#`.** Ters eğik çizgiye yalnızca
  yorumlarda, dollar-quoted gövdelerde, `E'…'` string'lerinde ve düzgün biçimli
  `U&` string'lerinde izin verilir; başka her yerde (`'a\'` gibi düz bir string
  dahil) reddedilir, çünkü PostgreSQL ile MySQL bunun ne anlama geldiği
  konusunda anlaşamaz. `#`, sabitlerin, tırnaklı tanımlayıcıların ve yorumların
  dışında reddedilir. Hedef veritabanı bilinmediğinde de aynı kurallar geçerlidir;
  MySQL ve MariaDB etkilenmez.

### Yasak listesi ne değildir

Bu liste **yapısal olarak eksiktir ve öyle kalacaktır.** Bugün, birkaç lehçe
genelinde bilinen tehlikeli sözdizimini sayar; adını koymadığı bir yazım —
bir üretici eklentisi, bir fonksiyon takma adı, sunucunun gelecekteki bir
özelliği — listeden geçer. Yukarıdaki her girdi, birinin daha önceki bir kuralı
aşmanın yolunu bulması yüzünden vardır ve bu süreç bitmez.

Dolayısıyla onu derinlemesine savunmanın bir katmanı sayın, asla yetkilendirme
sınırı değil. **Bir ifadeyi gerçekte yetkilendiren DBA onayıdır**: hazırlanan
SQL'i görüp çalışıp çalışmaması gerektiğine karar verebilen bir insan. Yasak
listesi yalnızca en bariz kötüye kullanım taleplerini o kişiye ulaşmadan eler.
(Yeni bir boşluk yine de geçerli bir güvenlik bildirimidir; bkz.
[güvenlik politikası](https://github.com/QueryProxy/QueryProxy/blob/main/SECURITY.md).)

Bu, `DO` gövdesi taraması için iki kat geçerlidir. Düz metin olarak yazılmış
SQL'i okur; bloğun çalışma anında bir araya getirdiği SQL'i okuyamaz; bir `DO`
ya da rutin gövdesindeki dinamik SQL'in (`EXECUTE`) taranmak yerine
reddedilmesinin nedeni budur. Erişimi dışında kalan, bir bloğun yapabildiği
geri kalan her şeydir: `plpgsql` ile yazılmış bir `DO` bloğu yine de sunucu
tarafında serbest koddur ve gövdesinde düz yazılmış `WHERE`'siz bir `DELETE`
geçer; çünkü bloklar bu kuraldan muaftır ve karar onayda verilir. Taramanın
kazandırdığı şey, doğrudan superuser kod çalıştırma yollarının ve düz yazılmış
yasak ifadenin elenmesidir; verilmemesi gereken bir onayın altındaki son ağdır,
onaylanmış bir bloğun ne yapabileceğine konan bir sınır değil.

## Kapsam

Guard'ın her yapıyla dialect bazında ne yaptığı. **Reddedilir**, talebin
reddedildiği anlamına gelir. **Yazma olarak sınıflandırılır**, reddedilmediği ama
onay gerektiren bir yazma olarak çalıştığı anlamına gelir (bir `DELETE` /
`UPDATE` yine `WHERE` ister). **Okuma** düz bir okumadır. **Kapsanmaz**, guard'ın
bugün bu konuda hiçbir şey yapmadığı anlamına gelir. Davranış, guard'ın birim
testlerine karşı doğrulanır. PostgreSQL için karar ve sunucudaki etkisi ayrıca
canlı bir sunucuya karşı da denetlenir (`live-pgsql` test grubu); MySQL, MariaDB
ve SQL Server canlı sunuculara karşı çalıştırılmamıştır.

### PostgreSQL

| Yapı | Durum |
|---|---|
| `EXPLAIN ANALYZE DELETE FROM t` / `WHERE`'siz `UPDATE` | reddedilir |
| `EXPLAIN (ANALYZE, BUFFERS) UPDATE t SET a=1 WHERE id=1` (ayrıca `ANALYSE`, `ANALYZE true/on/1`) | yazma olarak sınıflandırılır |
| `EXPLAIN ANALYZE SELECT 1` | okuma |
| `EXPLAIN DELETE FROM t WHERE id=1`, `EXPLAIN (ANALYZE false) DELETE FROM t` | okuma |
| `EXPLAIN ANALYZE DROP DATABASE prod` | reddedilir |
| `EXPLAIN (ANALYZE` (kapatılmamış), `ANALYZE maybe`, boş seçenek, tırnaklı ya da bilinmeyen seçenek | reddedilir |
| 3 seviyeden derin iç içe `EXPLAIN ANALYZE` | reddedilir |
| `SELECT * INTO t2 FROM t` (ayrıca `SELECT*INTO`, parantez içinde, bir CTE içinde) | yazma (DDL) olarak sınıflandırılır, `LIMIT` eklenmez |
| `SELECT * INTO TEMP t2 FROM t` | yazma (DDL) olarak sınıflandırılır |
| `nextval`, `setval`, `pg_advisory_lock*`, `pg_advisory_xact_lock*`, `pg_try_advisory_*`, `pg_advisory_unlock*` | yazma olarak sınıflandırılır |
| `pg_sleep`, `pg_sleep_for`, `pg_sleep_until` | yazma olarak sınıflandırılır |
| `pg_terminate_backend`, `pg_cancel_backend`, `pg_reload_conf`, `pg_rotate_logfile`, `pg_promote`, `pg_switch_wal` | reddedilir |
| `dblink*` (önek: `dblink` ile başlayan bir kullanıcı fonksiyonu da reddedilir) | reddedilir |
| `pg_read_file`, `pg_read_binary_file`, `pg_ls_dir`, `lo_import`, `lo_export` (tırnaklı ve `U&` yazımlar dahil) | reddedilir |
| `set_config('literal', …)` | yazma olarak sınıflandırılır |
| `set_config(<not a literal>, …)` (sabit olmayan argüman) | reddedilir |
| `set_config('session_preload_libraries', …)` ve listedeki diğer PostgreSQL ayarları | reddedilir |
| Listedeki bir PostgreSQL ayarının `SET` / `SET LOCAL` / `SET SESSION` ile yazılması | her scope'ta reddedilir |
| `SET sql_mode`, `set_config('sql_mode', …)` | reddedilir |
| `SET work_mem`, `set_config('search_path', …)` ve listede olmayan diğer ayarlar | yazma olarak sınıflandırılır |
| `ALTER SYSTEM …` | reddedilir |
| `SET [SESSION\|LOCAL] ROLE`, `RESET ROLE`, `RESET "role"`, `SET SESSION AUTHORIZATION`, `RESET SESSION AUTHORIZATION`, `set_config('role', …)` | reddedilir |
| `pg_catalog`, `pg_toast` ya da `pg_…` tablosuna `INSERT` / `UPDATE` / `DELETE` / `MERGE` (CTE, `DO` gövdesi, `PREPARE` içinde de) | reddedilir |
| `SELECT * FROM pg_proc`, `SELECT rolname FROM pg_roles` | okuma |
| `UPDATE t SET … FROM pg_proc WHERE …`, `INSERT INTO t SELECT … FROM pg_proc` | yazma olarak sınıflandırılır; yazma hedefi belirleyicidir |
| `SET TRANSACTION READ WRITE`, `SET SESSION CHARACTERISTICS … READ WRITE`, `BEGIN READ WRITE`, `START TRANSACTION READ WRITE` | reddedilir |
| `SET TRANSACTION READ ONLY` | geçer |
| `ALTER DATABASE\|ROLE\|USER … RESET <listedeki ayar>` ya da `RESET ALL` | reddedilir |
| `CREATE RULE … DO [ALSO\|INSTEAD] <eylem>` | eylem olağan kurallarla değerlendirilir; `DO`'su bulunamayan kural reddedilir |
| `DELETE FROM t WHERE 1 = 1`, `WHERE true`, `WHERE id = 1 OR 1 = 1` | reddedilir (her zaman doğru `WHERE`) |
| `WHERE`'siz `DELETE FROM t` içeren `DO` bloğu | yazma olarak sınıflandırılır; `WHERE` kuralından muaftır, karar onayda verilir |
| `PREPARE n AS <statement>` | yazma olarak sınıflandırılır; ifade kendi kurallarıyla değerlendirilir |
| `WITH x AS (DELETE FROM t) SELECT …` (`WHERE` yok) | reddedilir |
| `WITH x AS (UPDATE t SET a=1 WHERE id=1 RETURNING …) SELECT …` | yazma olarak sınıflandırılır |
| `WITH … DELETE FROM t` (`WITH`'ten sonraki ifade) | `WHERE` kuralı uygulanır |
| 3 seviyeden derin iç içe ya da okunamayan `WITH` | reddedilir |
| Bir CTE içindeki `INSERT` / `MERGE` gövdesi | yazma olarak sınıflandırılır; gövdeye ayrı bir `WHERE` denetimi yapılmaz (**kapsanmaz**) |
| `EXECUTE` (dinamik SQL) içeren `DO` bloğu, `CREATE FUNCTION` / `CREATE PROCEDURE`, trigger fonksiyonları dahil | reddedilir |
| `plpgsql` ya da `sql` dışında bir dilde `DO` / `CREATE FUNCTION` / `CREATE PROCEDURE`; `BEGIN ATOMIC` gövdesi; gövdesiz `CREATE FUNCTION`; `CREATE LANGUAGE` | reddedilir |
| Yasaklı bir fonksiyonu çağıran ya da korunan bir ayarı yazan `DO` / rutin gövdesi (`PERFORM pg_read_file(…)`) | reddedilir; gövde PostgreSQL'in sözcüksel kurallarıyla okunur |
| Listedeki bir ayarın `CREATE/ALTER FUNCTION … SET`, `ALTER DATABASE … SET`, `ALTER ROLE … SET` ile yazılması | reddedilir |
| `query_to_xml`, `query_to_xmlschema`, `query_to_xml_and_xmlschema`, `xpath_table`, `ts_stat`, `ts_rewrite`, `crosstab*`, `connectby` | reddedilir |
| Bir `DO` / rutin gövdesinde yukarıdakiler dışındaki düz `plpgsql` metni | yazıldığı gibi taranır (bunun ötesi **kapsanmaz**) |
| Sabit, tırnaklı tanımlayıcı ya da yorum dışında `#` | reddedilir |
| Yorum, dollar-quoted gövde, `E'…'` ya da düzgün biçimli `U&` string dışında ters eğik çizgi | reddedilir |
| `SELECT … FOR UPDATE` | okuma (satırları kilitler) |
| `DENY` | reddedilir (`;` ile ayrılmış ya da tek başına biçimleri) |
| Veritabanı tarafında salt okunur transaction | **kapsanmaz** (v0.3.0 için planlandı) |
| Veritabanı tarafında statement timeout | **kapsanmaz** (v0.3.0 için planlandı) |
| TLS: bağlantı `sslmode=prefer` kullanır | **kapsanmaz** (ayar v0.3.0 için planlandı) |

### MySQL / MariaDB

| Yapı | Durum |
|---|---|
| `EXPLAIN ANALYZE UPDATE t SET a=1 WHERE id=1` | yazma olarak sınıflandırılır |
| `EXPLAIN ANALYZE DELETE FROM t` (`WHERE` yok) | reddedilir |
| `ANALYZE`'sız `EXPLAIN` | okuma |
| MariaDB `ANALYZE UPDATE t SET a=1 WHERE id=1` (ayrıca `ANALYZE FORMAT=JSON …`) | yazma olarak sınıflandırılır |
| MariaDB `ANALYZE DELETE FROM t` (`WHERE` yok) | reddedilir |
| MariaDB `ANALYZE SELECT …` | içindeki `SELECT` olarak değerlendirilir |
| MariaDB `ANALYZE DROP TABLE t`, `ANALYZE TRUNCATE …`, `ANALYZE SET …`, `(ANALYZE …)` | reddedilir |
| `ANALYZE TABLE t` (ve `NO_WRITE_TO_BINLOG` / `LOCAL` biçimleri) | yazma olarak sınıflandırılır, değişmedi |
| `/*M!100000 DELETE FROM t */` (`mariadb` driver'ı) | gövde denetlenir; `mysql` driver'ında düz yorum |
| Sürümlü `/*!NNNNN … */` yorumları | gövde her iki okuma altında denetlenir; karışık sürümler ve tuhaf hane sayıları reddedilir (bkz. yukarısı) |
| `SET SESSION sql_mode = …`, `SET GLOBAL sql_mode = …`, `SET @@sql_mode = …`, `SET @@SESSION.sql_mode = …` | her scope'ta reddedilir |
| `SELECT @@sql_mode`, `SHOW VARIABLES LIKE 'sql_mode'` | okuma |
| `SET [SESSION\|GLOBAL] transaction_read_only = …`, `tx_read_only`, `SET @@session.transaction_read_only = …`, `SET STATEMENT transaction_read_only=0 FOR …` | her scope'ta reddedilir |
| `SET TRANSACTION READ WRITE`, `START TRANSACTION READ WRITE`, `BEGIN READ WRITE` | reddedilir |
| `SET ROLE …`, `SET DEFAULT ROLE …` | reddedilir |
| `USE mysql` (`PREPARE`, `EXECUTE IMMEDIATE`, `SET STATEMENT … FOR` içinde de) | reddedilir |
| `mysql.…` tablosuna `INSERT` / `UPDATE` / `DELETE` / `REPLACE` (yalnızca okuyan çok tablolu `UPDATE` / `DELETE` de reddedilir) | reddedilir |
| Varsayılan veritabanı `mysql` olan bağlantıda yazma | SQL çalışmadan talep başarısız olur; tek ifadeli okumalar geçer |
| Çalıştırma anında `NO_BACKSLASH_ESCAPES` ya da `ANSI_QUOTES` içeren oturum `sql_mode`'u | SQL çalışmadan talep başarısız olur |
| MariaDB `SET STATEMENT … FOR <statement>` | `FOR`'dan sonraki ifade kendi kurallarıyla değerlendirilir (`WHERE`, yasak listesi, …); ifade yoksa reddedilir |
| `SET GLOBAL general_log := 1` (`:=` öncesinde boşluk) | reddedilir |
| `SELECT … INTO @var` | okuma |
| `SELECT … INTO OUTFILE` / `INTO DUMPFILE` | reddedilir |
| `get_lock`, `release_lock` | yazma olarak sınıflandırılır |
| `sleep`, `benchmark` | yazma olarak sınıflandırılır |
| `load_file` | reddedilir |
| `PREPARE s FROM '<DML without WHERE>'` (`WHERE`'siz DML) | reddedilir |
| `PREPARE s FROM '<DML with WHERE>'` (`WHERE`'li DML) | yazma olarak sınıflandırılır |
| `PREPARE s FROM @q` (değişken) | reddedilir |
| `WHERE`'siz `WITH x AS (DELETE …)` | reddedilir |
| `WHERE`'siz `WITH … DELETE FROM t` | reddedilir |
| `SELECT 1 DENY …` | okuma (`DENY` bir MySQL ifadesi değildir; `;` ile ayrılmış ve tek başına biçimleri reddedilir) |
| Veritabanı tarafında salt okunur transaction | **kapsanmaz** (v0.3.0 için planlandı) |
| Veritabanı tarafında statement timeout | **kapsanmaz** (v0.3.0 için planlandı) |

### SQL Server

| Yapı | Durum |
|---|---|
| `xp_*`, `sp_configure`, `sp_oa*`, `sp_addlinkedserver`, `sp_addlinkedsrvlogin`, `sp_serveroption`, `sp_addextendedproc`, `sp_execute_external_script` (ifade olarak ya da `EXEC`'ten sonra çağrılan) | reddedilir |
| `OPENROWSET`, `OPENDATASOURCE`, `OPENQUERY` | reddedilir |
| `BULK INSERT` | reddedilir |
| `sp_prepare`, `sp_prepexec`, `sp_prepexecrpc`, `sp_execute`, `sp_cursoropen`, `sp_cursorprepare`, `sp_cursorprepexec`, `sp_msforeachtable`, `sp_msforeachdb`, `sp_msforeach_worker`, `sp_sqlexec`, `sp_send_dbmail`, `sp_add_jobstep`, `sp_update_jobstep` | argümanlar ne olursa olsun reddedilir |
| `WHERE`'siz `EXEC('DELETE FROM t')` / `EXEC(N'…')` | reddedilir |
| `EXEC('DELETE FROM t WHERE id = 1')` | yazma olarak sınıflandırılır |
| `EXEC(@sql)`, `EXEC('a' + 'b')`, `EXEC(…) AT srv`, `EXEC … AS USER` | reddedilir |
| `sp_executesql N'…'` (`EXEC` ile ya da onsuz, ya da `@stmt =` ile) | SQL metni olağan kurallarla değerlendirilir; değişken reddedilir |
| `EXEC('')` | zararsız, geçer |
| `EXEC('EXEC xp_cmdshell ''dir''')` (iç içe sabit) | reddedilir |
| `SELECT 'xp_cmdshell'` (ad bir string sabitinin içinde) | okuma |
| `EXEC dbo.my_proc 1` | yazma olarak sınıflandırılır |
| `EXEC "quoted_name"`, `WHERE exec = 1` | reddedilir (guard katı tarafta yanılır) |
| Dinamik SQL içinde `BEGIN` / `COMMIT` / `ROLLBACK` | reddedilir |
| 3 seviyeden derin iç içe dinamik SQL | reddedilir |
| `SELECT … INTO t2 FROM t` | yazma (DDL) olarak sınıflandırılır, `TOP` eklenmez |
| `SELECT * FROM t` | okuma; `TOP (1000)` alır |
| `SELECT TOP 99999 …` / `TOP (99999)` | okuma, `TOP 10000`'e çekilir |
| `SELECT TOP 10 PERCENT …`, `TOP (@v)`, `TOP (5 + 5)`, `TOP (1.5)` | reddedilir |
| `SELECT * FROM #t WHERE id = 1` (geçici tablolar) | okuma |
| `SELECT 1 DELETE FROM t` (`;`'siz batch) | reddedilir |
| `SELECT 1 DROP DATABASE x` | reddedilir |
| `DENY` | reddedilir |
| `SHUTDOWN` | reddedilir |
| `THROW`, `CHECKPOINT`, `RECONFIGURE`, `KILL` | yazma olarak sınıflandırılır |
| `EXECUTE AS …`, `EXEC AS …`, `SETUSER`, `REVERT`, `EXEC('SETUSER …')`, `sp_executesql N'EXECUTE AS …'` | reddedilir |
| Tanımda `WITH EXECUTE AS OWNER\|CALLER\|SELF\|'user'` | reddedilir |
| `execute_as` adlı kolon ya da string literal içinde `EXECUTE AS` | okuma |
| Veritabanı tarafında salt okunur transaction | **kapsanmaz** (v0.3.0 için planlandı) |
| Veritabanı tarafında statement timeout | **kapsanmaz** (v0.3.0 için planlandı) |

### SQLite

| Yapı | Durum |
|---|---|
| `ATTACH`, `DETACH` | reddedilir |
| `VACUUM … INTO` | reddedilir |
| `VACUUM` (tek başına) | yazma olarak sınıflandırılır |
| `load_extension(` | reddedilir |
| `PRAGMA table_info(t)` ve izin verilen diğer salt okunur pragma'lar (`table_xinfo`, `index_list`, `index_info`, `index_xinfo`, `foreign_key_list`, `database_list`, `table_list`, `collation_list`, `function_list`, `compile_options`) | okuma |
| `PRAGMA x = …`, bir ayar için `PRAGMA x(v)`, listede olmayan her pragma | reddedilir |
| `QUERYPROXY_EXTRA_SQLITE_PRAGMAS` ile eklenen bir pragma | yalnızca çıplak biçimde izinli |
| `EXPLAIN VACUUM INTO '/tmp/x'` | reddedilir |
| `QUERYPROXY_SQLITE_ALLOWED_DIR` dışındaki dosyalar | ayar doluyken reddedilir. Ayar boşken uygulama dizini dışındaki her yol kabul edilir ve bağlantı biçimi uyarı gösterir |
| Uygulama dizini içindeki dosyalar (QueryProxy'nin varsayılan SQLite veritabanı oradadır) | ayardan bağımsız olarak her zaman reddedilir |
| Salt okunur transaction ve statement timeout | **kapsanmaz** (v0.3.0 için planlandı) |

### Tüm dialect'ler

| Yapı | Durum |
|---|---|
| Bir string sabitinin, yorumun ya da dollar-quoted gövdenin içindeki listelenmiş fonksiyon ya da prosedür adı (`SELECT 'pg_sleep(1)'`, `SELECT 1 -- dblink(`) | etkilenmez |
| `DESC analyze` (adı `analyze` olan bir tablo) | reddedilir (aşırı engelleme) |
| Sunucuda tanımlı fonksiyonların ve prosedürlerin içindeki kod | **kapsanmaz** |
| `EXECUTE IMMEDIATE '<DML without WHERE>'` (`WHERE`'siz DML) | reddedilir |
| `EXECUTE IMMEDIATE '<DML with WHERE>'` (`WHERE`'li DML) | yazma olarak sınıflandırılır |
| `EXECUTE IMMEDIATE <variable, expression or concatenation>` (değişken, ifade ya da birleştirme) | reddedilir |
| Bilinmeyen bir anahtar kelimeyle başlayan ifade | yazma olarak sınıflandırılır |

## Bilinen sınırlar

Guard her şeyi kapsamaz. Aşağıdaki durumlar bilinir ve olduğu gibi
bırakılmıştır. Guard, onayın ya da veritabanı yetkilerinin yerini tutmaz.

- **PostgreSQL katalog adları.** Yalnız `pg_catalog`, `pg_toast` ve şemasız
  `pg_…` adları katalog tablosu sayılır. `pg_temp` ya da başka bir şemadaki
  (`public.pg_x`) tablo kullanıcı tablosu olarak ele alınır.
- **SQLite ve SQL Server.** Katalog tablolarına (`sqlite_master`, `sys.…`)
  yazma, katalog kuralının kapsamında değildir.
- **Katalog DDL'i.** Katalog kuralı `INSERT`, `UPDATE`, `DELETE`, `MERGE` ve
  `REPLACE` ifadelerini değerlendirir. Katalog tablosu üzerinde `TRUNCATE`,
  `DROP` ve `ALTER` bu kuralın dışındadır.
- **MySQL ve MariaDB saklı kod.** `CREATE PROCEDURE`, `CREATE TRIGGER` ya da
  `CREATE EVENT … DO` gövdelerindeki `SET` ifadeleri denetlenmez.
- **MySQL ve MariaDB `SET PASSWORD` listeleri.** Yukarıdaki `SET` kurallarının
  hiçbiri (korunan değişkenler, `sql_mode`, salt okunur anahtarı) bir
  `SET PASSWORD` ifadesindeki diğer atamalara uygulanmaz. İfade `SET` olarak
  denetlenmez.

Bu sınırlar yüzünden guard tek başına bir güvenlik sınırı değildir.
QueryProxy'nin bağlandığı veritabanı kullanıcısına yalnız gereken yetkileri
verin (en az yetki ilkesi).

## Lehçeler ve temkinli geri düşüş

Parser önce MySQL lehçesini bilir. Tam parse edemediği ifadeler (PostgreSQL
`::jsonb` cast'leri, sürücüye özgü operatörler) yine **anahtar kelime bazında**
korunur: parse edilemeyen `WHERE`'siz bir `UPDATE` yine reddedilir, parse
edilemeyen bir `SELECT`'e yine LIMIT eklenir — sınıflandırılamayan her şey ise
**yazma** sayılır, yani tam onay incelemesinden geçer. QueryProxy tereddütte
her zaman katı taraftadır.
