Bir gayrimenkul satış ekibinde CRM ekranını açan herkes aynı şeyi görmemeli. Çağrı merkezindeki yeni başlayan bir temsilci, lead'i aramak için telefon numarasına ihtiyaç duyar ama o numarayı listede toplu halde görmesine gerek yoktur. Pazarlama analisti kampanya performansına bakarken kimin telefonu olduğunu hiç bilmemelidir. Bir entegrasyon ortağının destek ekibi hata ayıklarken gerçek e-posta adreslerini görmemelidir. Ben Koray Yalçın olarak lead yönetimi ve CRM kurulumlarında bu ayrımı en sık atlanan güvenlik katmanı olarak görüyorum: herkes "yetki" konuşur, ama yetkili olan kişinin bile neyi, ne kadarını ve ne zaman göreceği konuşulmaz.
Veri maskeleme, kişisel verinin yetkisiz ya da gereğinden fazla göz önüne çıkmasını engellemek için değerin bir kısmının gizlenerek gösterilmesidir. En bilinen örneği 0532***1234 biçiminde gösterilen telefon numarasıdır. Bu yazıda kavramı sözlük tanımından çıkarıp bir geliştiricinin gözünden ele alıyorum: maskelemeyi nerede yapacağız, kurallarını nasıl veri olarak tutacağız, "numarayı göster" düğmesinin arkasında ne olmalı, dışa aktarma ve loglar nasıl korunacak, test ortamına gerçek veri nasıl sızmayacak.
Maskeleme, şifreleme ve yetkilendirme aynı şey değil
Bu üç kavram sık karıştırılır, oysa farklı soruları yanıtlarlar.
- Yetkilendirme "bu kullanıcı bu kayda erişebilir mi?" sorusunu yanıtlar. Erişemiyorsa kayıt hiç gelmez.
- Şifreleme "veri diskte ya da ağda çalınırsa okunabilir mi?" sorusunu yanıtlar. Uygulama içinde yetkili kullanıcıya çözülmüş haliyle gelir.
- Maskeleme "erişimi olan kullanıcı alanın tamamını görmeli mi?" sorusunu yanıtlar. Kayda erişim vardır, ama belirli alanlar kısmen gizlenir.
Yani maskeleme, yetkilendirmenin alan düzeyindeki ince ayarıdır. Bir temsilci kendisine atanmış lead'e erişebilir (yetkilendirme), telefon sütunu veritabanında şifreli durabilir (şifreleme), ama liste ekranında numara maskeli görünür ve yalnız arama anında tam haliyle açılır (maskeleme). Üçü birbirinin yerine geçmez; birlikte çalışır.
Kural 1: Maskeleme sunucuda yapılır, arayüzde değil
En yaygın hata, tam veriyi API'den gönderip arayüzde CSS ya da JavaScript ile gizlemektir. Tarayıcının geliştirici araçlarını açan herkes ağ sekmesinde tam numarayı görür. Mobil uygulamada da aynı şey geçerlidir: yanıt gövdesi cihazda önbelleğe alınır.
Doğru yer, verinin sunucudan çıktığı son katmandır: serileştirme (serializer) ya da yanıt dönüştürücü. Böylece hangi uç noktadan gelirse gelsin aynı kural uygulanır. Kuralı her endpoint'e tek tek yazmak yerine tek bir fonksiyona bağlamak, yeni bir endpoint eklendiğinde maskelemenin unutulmasını engeller.
# Tek giriş noktası: her yanıt bu fonksiyondan geçer def serialize_lead(lead: dict, viewer: Viewer, context: str) -> dict: out = {} for field, value in lead.items(): rule = FIELD_RULES.get(field) if rule is None: out[field] = value # kişisel veri olmayan alan continue level = resolve_level(rule, viewer, context, lead) out[field] = apply_mask(value, rule.kind, level) return out Burada context parametresi önemlidir: aynı kullanıcı liste ekranında, kayıt detayında, dışa aktarmada ve raporda farklı görünürlük seviyesine sahip olabilir.
Kural 2: Kuralları kodda değil veride tut
Hangi alanın kimde nasıl görüneceği zamanla değişir. Yeni bir rol eklenir, bir alan kişisel veri olarak işaretlenir, hukuk ekibi e-postanın da maskelenmesini ister. Bunu her seferinde kod değişikliğiyle yapmak hem yavaştır hem de denetlenmesi zordur. Kuralları bir tabloda tutmak, kimin neyi ne zaman değiştirdiğini de kayıt altına almayı kolaylaştırır.
CREATE TABLE field_mask_rule ( id bigserial PRIMARY KEY, entity text NOT NULL, -- 'lead', 'contact', 'deal' field text NOT NULL, -- 'phone', 'email', 'national_id' mask_kind text NOT NULL, -- 'phone', 'email', 'full', 'none' role text NOT NULL, -- 'agent', 'analyst', 'manager', 'partner' context text NOT NULL, -- 'list', 'detail', 'export', 'report', 'log' level text NOT NULL CHECK (level IN ('hidden','masked','revealable','clear')), updated_by bigint NOT NULL, updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (entity, field, role, context) ); Dört seviye pratikte yeterli oluyor:
- hidden: Alan yanıtta hiç yer almaz.
- masked: Alan maskeli gelir, açılamaz.
- revealable: Alan maskeli gelir, kullanıcı gerekçeyle açabilir; açma kaydedilir.
- clear: Alan tam gelir.
Varsayılan değer her zaman en kısıtlayıcı olandır. Tabloda kuralı olmayan ama kişisel veri olarak işaretlenmiş bir alan hidden sayılmalıdır. Böylece yeni eklenen bir alan için kural yazmayı unutmak veri sızıntısına değil, eksik görünüme yol açar. Eksik görünüm şikayet getirir ve düzeltilir; sızıntı ise çoğu zaman fark edilmez.
Maskeleme biçimleri: ne kadarını göstermeli?
Maskelemenin amacı veriyi tamamen yok etmek değil, işi yapmaya yetecek kadarını bırakmaktır. Temsilci "bu, dün aradığım numara mı?" diye ayırt edebilmeli; analist aynı kişinin iki kez form doldurup doldurmadığını anlayabilmelidir.
import re def mask_phone(v: str) -> str: digits = re.sub(r"\D", "", v or "") if len(digits) < 7: return "***" return f"{digits[:4]}***{digits[-4:]}" if len(digits) >= 10 else f"***{digits[-3:]}" def mask_email(v: str) -> str: if not v or "@" not in v: return "***" local, domain = v.split("@", 1) head = local[:1] return f"{head}***@{domain}" def apply_mask(value, kind, level): if level == "clear": return value if level == "hidden": return None if kind == "phone": return mask_phone(value) if kind == "email": return mask_email(value) return "***" Birkaç pratik not:
- Telefonda ülke kodu ve operatör önekini bırakmak çoğu zaman yeterli bağlam verir; son dört hane ayırt etmeyi sağlar. Kısa numaralarda daha az hane gösterin, aksi halde maskeleme anlamsızlaşır.
- E-postada alan adını açık bırakmak, kurumsal lead'leri ayırt etmeye yarar. Ama küçük bir şirketin alan adı tek başına kişiyi belli edebilir; hassas projelerde alan adını da maskeleyin.
- Kimlik numarası, pasaport numarası gibi alanlar için kısmi gösterim yerine
fullmaskeyi verevealableseviyesini tercih edin.
"Numarayı göster" düğmesi: açma kaydı ve gerekçe
revealable seviyesindeki bir alanın açılması, ayrı bir uç noktaya yapılan bilinçli bir istektir. Liste yanıtı hiçbir zaman tam değeri taşımaz; kullanıcı düğmeye bastığında tek bir kaydın tek bir alanı için istek gider, sunucu yetkiyi yeniden kontrol eder, açma olayını kaydeder ve değeri döner.
CREATE TABLE pii_reveal_log ( id bigserial PRIMARY KEY, viewer_id bigint NOT NULL, entity text NOT NULL, entity_id bigint NOT NULL, field text NOT NULL, reason text NOT NULL, -- 'call', 'whatsapp', 'appointment', 'other' note text, ip inet, revealed_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX ON pii_reveal_log (viewer_id, revealed_at DESC); Gerekçe alanı ilk başta bürokrasi gibi görünür, ama iki işe yarar. Birincisi, kullanıcıyı bir an durdurur; "neden bu numaraya ihtiyacım var?" sorusunu sordurur. İkincisi, denetimde anlamlı bir iz bırakır. Arama gerekçesiyle açılan bir numaranın ardından gerçekten bir arama kaydı (call log) oluşmuş mu, bunu karşılaştırmak mümkün hale gelir.
Daha da iyisi, birçok durumda numarayı hiç açmadan işi yaptırmaktır. Temsilci "Ara" düğmesine bastığında santral ya da tıkla-ara entegrasyonu numarayı sunucu tarafında çevirir; temsilci numarayı hiç görmez. WhatsApp mesajı da aynı şekilde şablon üzerinden sunucudan gönderilebilir. Benim kurduğum lead yönetimi akışlarında hedef, temsilcinin günlük işinin büyük kısmını tam numarayı görmeden tamamlayabilmesidir. Açma düğmesi istisna için kalmalıdır.
Anormal açma davranışını yakalamak
Açma kaydı tutulduktan sonra basit kurallarla ciddi riskleri yakalamak mümkündür:
-- Son bir saatte olağan dışı sayıda açma yapan kullanıcılar SELECT viewer_id, count(*) AS reveals FROM pii_reveal_log WHERE revealed_at > now() - interval '1 hour' GROUP BY viewer_id HAVING count(*) > 40; Eşik değer ekibin gerçek iş yüküne göre belirlenmelidir; buradaki 40 yalnızca örnek bir değerdir. Eşiği aşan kullanıcı için açma uç noktası geçici olarak yavaşlatılabilir, yöneticiye bildirim gidebilir. İşten ayrılmadan önceki günlerde toplu numara açma, bu tablonun en sık ortaya çıkardığı örüntülerden biridir.
Dışa aktarma: en büyük sızıntı kapısı
Ekranda maskeleme ne kadar iyi olursa olsun, "Excel'e aktar" düğmesi tüm emeği boşa çıkarabilir. Dışa aktarma ayrı bir context olarak ele alınmalı ve kuralları ayrıca tanımlanmalıdır. Benim önerim:
- Dışa aktarmada
revealableseviyesi yoktur; yamaskedyaclear. Toplu açma, tek tek açmanın kaydını anlamsız kılar. -
cleardışa aktarma yalnız belirli rollere açıktır ve her dışa aktarma işi kimin, hangi filtreyle, kaç satır aldığıyla birlikte kaydedilir. - Dosya kalıcı bir bağlantıyla değil, kısa süreli imzalı bir bağlantıyla indirilir.
- Analiz amaçlı dışa aktarmalarda telefon ve e-posta yerine sabit bir takma kimlik (pseudonym) verilir. Analist aynı kişiyi tekrar eden satırlarda tanıyabilir ama kim olduğunu bilmez.
import hmac, hashlib def pseudonym(value: str, secret: bytes, scope: str) -> str: norm = re.sub(r"\D", "", value) if scope == "phone" else value.strip().lower() mac = hmac.new(secret, f"{scope}:{norm}".encode(), hashlib.sha256) return mac.hexdigest()[:16] Sırrı (secret) dışa aktarma işine özel ya da döneme özel tutmak, iki farklı dosyanın birleştirilerek kişilerin eşleştirilmesini zorlaştırır.
Loglar ve hata izleme araçları
Uygulama logları, hata izleme servisleri ve kuyruk mesajları kişisel verinin en sessiz biriktiği yerlerdir. Bir hata yığını (stack trace) tüm istek gövdesini içerebilir; bir webhook hatası gelen lead'in telefonunu loglara yazabilir. Burada context = 'log' için kural genellikle masked ya da hidden olmalıdır ve log kütüphanesine bir filtre eklenmelidir:
import logging SENSITIVE_KEYS = {"phone", "email", "national_id", "passport_no"} class PiiFilter(logging.Filter): def filter(self, record): if isinstance(record.args, dict): record.args = {k: ("***" if k in SENSITIVE_KEYS else v) for k, v in record.args.items()} return True Anahtar adına göre filtrelemek tek başına yetmez; serbest metin içinde geçen telefon ve e-posta kalıplarını düzenli ifadeyle temizleyen ikinci bir katman da eklemek gerekir. Hata izleme servislerinin çoğu gönderim öncesi veri temizleme kancası sunar; bu kancayı aynı kural kümesine bağlamak tutarlılığı korur.
Test ve geliştirme ortamı: gerçek veri olmadan gerçekçi veri
Üretim veritabanının bir kopyasını test ortamına almak hızlıdır ama riskli. Test ortamları genellikle daha zayıf korunur, daha çok kişi erişir ve üçüncü taraf araçlara bağlanır. Burada ekranda maskeleme yeterli değildir; verinin kendisi dönüştürülmelidir. Buna statik maskeleme ya da tokenizasyon denir.
İyi bir test verisi dönüşümünün üç özelliği olmalı:
- Tutarlılık: Aynı gerçek telefon her tabloda aynı sahte telefona dönüşmeli; böylece tekilleştirme ve eşleştirme mantığı testte de çalışır.
- Biçim koruma: Sahte telefon geçerli bir telefon biçiminde olmalı, sahte e-posta geçerli bir e-posta olmalı; doğrulama kuralları kırılmamalı.
- Geri döndürülemezlik: Test ortamına erişen biri sahte değerden gerçeğine ulaşamamalı.
def fake_phone(real: str, secret: bytes) -> str: h = int(pseudonym(real, secret, "phone"), 16) return "0599" + str(h % 10_000_000).zfill(7) # kullanılmayan bir önek seçin def fake_email(real: str, secret: bytes) -> str: return f"user{pseudonym(real, secret, 'email')[:10]}@example.test" Sahte numaralar için gerçek bir operatöre ait olmayan bir önek seçmek, testten yanlışlıkla gerçek bir kişiye SMS ya da arama gitmesini engeller. Aynı şekilde sahte e-postalar için example.test gibi teslim edilemeyen bir alan adı kullanılmalıdır. Dönüşüm sırrı test ortamında hiç bulunmamalıdır.
KVKK ve GDPR açısından nereye oturur?
Maskeleme, kişisel verinin işlenme amacıyla sınırlı ve ölçülü tutulması ilkesinin teknik karşılıklarından biridir. KVKK ve GDPR, kişisel verilere erişimin işin gerektirdiği ölçüyle sınırlanmasını ve uygun teknik tedbirlerin alınmasını bekler. Maskeleme tek başına uyumluluk sağlamaz; açık rıza, aydınlatma metni, saklama süresi ve silme süreçleri ayrıca kurulmalıdır. Ama bir veri sızıntısı soruşturmasında "liste ekranında numaralar maskeliydi, açmalar gerekçeyle kaydediliyordu, dışa aktarma rol bazlıydı" diyebilmek, alınan tedbirin somut kanıtıdır. Hukuki yorum için her zaman bir uzmana danışılmalıdır; burada anlattıklarım mühendislik pratiğidir.
Uygulamaya alırken kontrol listesi
- Kişisel veri alanlarını bir kayıt defterinde işaretleyin; işaretli ama kuralı olmayan alan varsayılan olarak gizli olsun.
- Maskelemeyi tek bir serileştirme katmanına bağlayın; arayüze tam veri göndermeyin.
- Liste, detay, dışa aktarma, rapor ve log için ayrı bağlam kuralları tanımlayın.
- Açma işlemini ayrı uç noktaya taşıyın; gerekçe ve kayıt zorunlu olsun.
- Tıkla-ara ve şablon mesaj ile numarayı göstermeden iletişim kurulmasını sağlayın.
- Açma kayıtlarında anormal hacmi izleyin.
- Test ortamına yalnız dönüştürülmüş veri aktarın.
- Kuralları her çeyrek rol değişiklikleriyle birlikte gözden geçirin.
SSS
Veri maskeleme ile anonimleştirme arasındaki fark nedir?
Maskeleme, yetkili bir kullanıcı için veriyi kısmen gizler; gerçek değer sistemde durur ve gerektiğinde açılabilir. Anonimleştirmede ise veri kişiyle bir daha ilişkilendirilemeyecek şekilde dönüştürülür. Test ortamı için yapılan geri döndürülemez dönüşüm anonimleştirmeye yakındır; ekran maskelemesi değildir.
Maskelemeyi veritabanı düzeyinde yapmak daha güvenli değil mi?
Bazı veritabanları dinamik maskeleme özelliği sunar ve ek bir savunma hattı olarak işe yarar. Ama bağlam (liste mi, dışa aktarma mı, log mu) bilgisi genellikle uygulama katmanındadır. Pratikte en sağlam yapı, uygulama katmanında bağlam duyarlı maskeleme ve veritabanında rol bazlı erişim kısıtlamasının birlikte kullanılmasıdır.
Temsilci numarayı görmeden nasıl arama yapar?
Tıkla-ara entegrasyonu ile CRM, numarayı sunucu tarafında santrale iletir; temsilcinin telefonu çalar ve görüşme bağlanır. Temsilci ekranda yalnız maskeli numarayı görür. WhatsApp ve SMS için de şablonlu gönderim aynı mantıkla çalışır.
Maskeli telefonla tekrar eden lead'ler nasıl tespit edilir?
Tekilleştirme mantığı sunucuda tam değer üzerinde çalışır; kullanıcıya yalnız sonuç gösterilir. Analiz tarafında ise sabit takma kimlik kullanılır; aynı kişi farklı satırlarda aynı kimlikle görünür.
Yönetici rolü her şeyi açık görmeli mi?
Hayır. Yönetici rolü genellikle rapor ve performans ekranlarında çalışır; orada tam numaraya ihtiyaç yoktur. Yöneticinin de revealable seviyesinde olması ve açmalarının kaydedilmesi, hem kötüye kullanımı önler hem de ekibe doğru örnek olur.
Küçük bir ekip için bu yapı fazla değil mi?
Dört kişilik bir ekipte bile liste ekranında maskeleme ve dışa aktarmanın kısıtlanması birkaç saatlik iştir ve en büyük riski kapatır. Açma kaydı, anormallik izleme ve test verisi dönüşümü ekip büyüdükçe eklenebilir. Önemli olan, varsayılanın en baştan kısıtlayıcı olmasıdır.
Koray Yalçın — lead yönetimi, CRM mimarisi, dijital pazarlama ve PropTech üzerine çalışıyor.
