# Veri maskeleme

> Kolon deseni ve içerik regex maskeleme kuralları, üç strateji ve sonuçların neden diske ulaşmadan önce maskelendiği.

- Dokümantasyon: Kılavuzlar
- Güncelleme: 2026-10-10
- Kaynak: https://queryproxy.com/tr/docs/veri-maskeleme/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Maskeleme kuralları, kişisel verileri geliştiricinin gördüğü sonuç verisinin —
ve QueryProxy'nin kendi sonuç depolamasının — dışında tutar. Kurallar DBA'ler
tarafından **Masking** altında yönetilir.

## Yazım anında maskeleme

Maskeleme **sonuç akışının içinde** çalışır: işçi sonuç kümesini cursor ile
gezerken her satır, sonuç dosyasına yazılmadan *önce* maskelenir. Bir sonucun
maskesiz hâli diskte hiç var olmaz; dolayısıyla depolanan sonuç verisi sonradan
maskesiz görüntülenemez ya da indirilemez. Bilinçli bir tasarım kararı olarak
"maskeyi kaldır" düğmesi yoktur.

## Hata mesajları

Başarısız bir çalıştırmanın da metni vardır ve bir veritabanı sürücüsü hatası,
onu bozan değerleri yankılayabilir (örneğin PostgreSQL'in `invalid input syntax
for type integer: "alice@example.com"`, MySQL'in `Duplicate entry
'bob@example.com'` mesajı ya da SQL'in kendisi). Bu yüzden hata mesajı,
saklanmadan ya da gösterilmeden önce sonuç verisiyle aynı **yazım anında
maskeleme** ilkesinden geçer:

1. **Temizleme.** Bağlantının kendi parolası, kullanıcı adı, host'u ve
   veritabanı adı `[redacted]` ile değiştirilir.
2. **Regex kuralları.** Bağlantının etkin her içerik kuralı, eşleştiği yeri
   kendi stratejisiyle yerinde maskeler. Kuralı olmayan bir bağlantıda yalnızca
   temizleme uygulanır.
3. **Kolon kuralları, fail-closed.** Mesaj, bir kolon kuralının kapsadığı bir
   kolonu anıyorsa içindeki her literal değer (tırnaklı değerler, `Duplicate
   entry '…'`, `Key (…)=(…)`) eşleşen kolon kuralları arasındaki en güçlü
   stratejiyle (`full`, sonra `hash`, sonra `partial`) maskelenir. Böyle bir
   mesajda tırnaklar dengeli değilse (MySQL'in `doesn't exist` mesajındaki gibi
   başıboş bir kesme işareti eşleşmeyi kaydırır) mesajın tamamı `*****` olur.
   PostgreSQL'in `Failing row contains (…)` mesajı kolon adı vermez; bağlantıda
   herhangi bir kural varsa satırın tamamı maskelenir.
4. **Aksi hâlde fail-closed.** Bir regex değerlendirilemiyorsa mesajın tamamı
   `*****` olur. Kuralların alınacağı bağlantı kalmadıysa ya da maskeleme
   kendisi başarısız olduysa saklanan metin sabit `Job failed; details in
   application log.` mesajıdır.

Talep detayında, talep sahibinin bildiriminde (ilk 120 karakter) ve denetim
günlüğünde görünen budur. **Ham mesaj yalnızca uygulama günlüğüne yazılır.**
Kuyruk işinin kendi hata işleyicisi (`failed()`) de aynı yolu kullanır; yani
normal çalıştırma yolunun dışındaki bir hata artık ham metin saklamaz, talep
sahibine tam bir kez bildirim gider ve hata tam bir kez denetlenir.

> **Neyi kapsamaz.** Mesajdaki tırnaksız, çıplak bir değer (örneğin `value 42`)
> yalnızca regex kuralıyla yakalanır, kolon kuralıyla yakalanmaz. Hata, SQL'i
> yazıldığı gibi taşır: metin literal'leri tırnaklıdır, sayılar değildir (`WHERE
> national_id = 12345678901`). Hassas biçimler için içerik kurallarını etkin
> tutun. Bağlantı **testi** hatalarını ayrı bir yoldan bildirir; bu yolun
> dışındadır.

### Yükseltmeden sonra

Önceki sürümlerin sakladığı mesajlar siz maskeleyene kadar maskesiz kalır. Bir
kez çalıştırın:

```bash
php artisan queryproxy:mask-error-messages --dry-run   # yalnızca raporlar
php artisan queryproxy:mask-error-messages
```

Komut, var olan taleplerin hata mesajını ve uygulama içi "talep başarısız"
bildirimlerindeki kopyayı her bağlantının güncel kurallarıyla yeniden maskeler.
Artık var olmayan bir talep ya da bağlantı sabit mesajı alır. Zaman damgaları ve
okundu durumu korunur ve çalıştırma tek bir `error_messages.masked` denetim
kaydı olarak yazılır. Aynı kurallarla yeniden çalıştırmak hiçbir şeyi
değiştirmez; kural değişikliğinden sonra güncel kuralları uygular ve yalnızca
maske ekler, hiçbir maskeyi kaldırmaz. Yeni mesajlar yazıldıkları anda
yürürlükte olan kurallarla maskelenir; bir kuralı değiştirmek saklanan mesajları
kendiliğinden yeniden maskelemez. Bağlantı kuralları okunamayan bir kayıt
atlanır, değiştirilmez ve raporlanır; komut hata koduyla çıkar.

> [!IMPORTANT]
> **Denetim günlüğü değiştirilemez; komut ona dokunamaz.** Önceki sürümlerin
> yazdığı denetim kayıtları (`request.execution_failed`,
> `execution.late_completion`) maskelenmemiş mesaj metnini korur ve bir denetçi
> bunları hâlâ okuyup dışa aktarabilir. Orada yalnızca yükseltmeden sonraki
> hatalar maskelenir.

## Kural türleri

| Eşleme türü | Neye bakar | Örnek desen |
| :-- | :-- | :-- |
| **Kolon** | Kolonun adı (büyük/küçük harf duyarsız joker; `\|` alternatifleri ayırır) | `*email*`, `*password*\|*secret*\|*token*\|*api_key*` |
| **Regex** | Hücrenin içeriği (ayraçlı PCRE) | `/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/` |

Aynı değer için kolon kuralı içerik kuralından önce gelir. İki tür de **her
skaler değerde** çalışır, yalnızca string'lerde değil: PDO `BIGINT` ve `DECIMAL`
kolonları için native tamsayılar döndürür; bu yüzden sayı olarak saklanan bir
kart numarası ya da kimlik numarası, metin olarak saklananla aynı şekilde içerik
kurallarıyla eşleşir. Hiçbir kuralın eşleşmediği değer özgün tipiyle olduğu gibi
döndürülür.

Her kural ya **takım genelinde** geçerlidir ya da tek bir bağlantıya
sabitlenir; silmeden açılıp kapatılabilir.

## Stratejiler

| Strateji | Sonuç | Örnek |
| :-- | :-- | :-- |
| `full` | Değerin tamamı değiştirilir | `*****` |
| `partial` | İlk ve son karakter korunur, arada en fazla 8 yıldız | `a***@***.com`, `5********2` |
| `hash` | Deterministik SHA-256 öneki | `sha256:9f86d081884c` |

`partial` e-postaları okunur tutar (ilk harf + TLD); üç karakter ya da daha
kısa değerler `***` olur. `hash` ise analistlerin
değeri görmeden kolon üzerinde join/sayım yapabilmesini sağlar.

## Varsayılan kural seti

Tek tıkla (**Add default rules**) makul varsayılanlar kurulur: e-posta, telefon
ve kart kolonları (`partial`), secret/token/parola/`api_key` kolonları (`full`),
ayrıca şunlar için içerik regex'leri:

- e-posta adresleri (`partial`),
- kredi kartı numaraları (`partial`); gerçek issuer identification aralıklarına
  daraltıldı, böylece artık 13 haneli epoch zaman damgalarında, snowflake
  id'lerde ya da sipariş numaralarında tetiklenmez,
- sabit önekli üretici API anahtarları ve token'ları — `sk-`, `xox…`, `ghp_` ve
  benzerleri, `glpat-`, `AKIA`/`ASIA`, `AIza` (`full`),
- JSON Web Token'lar (`full`),
- PEM formatındaki private key blokları (`full`),
- yüksek entropili sırlar: küçük harf, büyük harf ve rakam karışımı 32 ya da daha
  fazla karakter (`full`).

Sırlara yönelik içerik kuralları şu yüzden vardır: kolon kuralı yalnızca
**çıktı** kolon adını görür ve o adı sorguyu yazan seçer — `SELECT api_key AS k`,
en katı `*api_key*` desenini bile atlatıyordu. Değer üzerinden eşleme, takma adla
gizlenmiş sırrı yine de yakalar.

Varsayılanlar idempotenttir — iki kez eklemek yeni bir şey oluşturmaz.

> **Yeni takımlar varsayılan kurallarla başlar; mevcut takımlar bunları
> otomatik almaz.** v0.2.1'den itibaren oluşturulan her takımda — **Teams &
> Users** altında, demo tohumuyla ya da başka herhangi bir yolla — varsayılan
> kurallar oluşturulduğu anda kurulur. Yükseltmeden önce var olan takımlara
> **geriye dönük kurulum yapılmaz** ve hiçbir takım yükseltmede yeni eklenen
> varsayılanları kendiliğinden almaz: böyle her takım için **Masking**'i açın ve
> **Add default rules**'u bir kez çalıştırın. Zaten kuralları olan bir takımda
> çalıştırmak güvenlidir — yalnızca eksik varsayılanlar eklenir, mevcut hiçbir
> şey değiştirilmez ya da kaldırılmaz. Bu, bir takımın daha önce kurduğu eski,
> daha geniş kart deseninin kural listesinde kalması demektir; artık
> istemiyorsanız onu kapatın.
>
> **Etkin** hiçbir maskeleme kuralı olmayan bir takım için **Query Studio** ve
> **Masking** sayfalarında bir uyarı banner'ı görünür. Hiç kural yoksa
> *"This team has no masking rules — query results are returned unmasked."*
> yazar ve Masking sayfasında doğrudan **Add default rules**'a bağlanır. Kurallar
> var ama hepsi kapalıysa *"This team has no enabled masking rules — query
> results are returned unmasked."* yazar ve onları etkinleştirmenizi ister.
> Query Studio'da banner kullanıcıya bir DBA'e sormasını söyler. Denetim takım
> geneline bakar: tek etkin kuralı bir bağlantıya bağlı olan bir takımda banner
> görünmez, ama diğer bağlantılarındaki sorgular maskesiz döner — en az bir
> takım geneli kuralı etkin tutun. Varsayılanlar ya hep ya hiç kurulur, bu
> yüzden bir takım oluşturulurken yaşanan bir hata onu asla yarım bir kural
> setiyle bırakmaz.

## Kuralları test etmek

Masking sayfasının altındaki **playground**, etkin kurallarınızın önizlemesini
verir: bir kolon adı ve örnek değer girin, sonucun tam olarak nasıl görüneceğini
görün. Önizleme takımın etkin kurallarının tamamını uygular; bağlantıya göre
filtrelenmez. Geçersiz regex desenleri kayıt anında reddedilir; felaket geri izlemeye
(ReDoS) yol açabilecek desenler de reddedilir — bozuk ya da kontrolsüz bir desen
üretime girip sessizce hiçbir şeyi eşleştirmeden duramaz.

Regex maskeleme ayrıca **kapalı yönde başarısız olur** (fail-closed): bir desen
çalışma anında bir değere karşı değerlendirilemezse, o değer **ham verilmez,
maskelenir** — maskeleme, en çok korumaya ihtiyaç duyduğu patolojik veride açık
kalmaz.
