Bir yan ürünümün backend tarafında, kullanıcıların doğal dille karmaşık finansal raporlar alabilmesi için bir AI agent kurgulamıştım. Her şey harika gidiyordu; agent veritabanına bağlanıyor, gerekli tabloları sorguluyor ve sonucu özetliyordu. Ta ki bir gece, agent’ın “yanlışlıkla” devasa bir JOIN sorgusuyla PostgreSQL üzerindeki tüm CPU kaynaklarını sömürüp veritabanını kilitlediğini görene kadar. O gün anladım ki, bir agent’a “tool” (araç) vermek, eline dolu bir silah verip “sadece hedefe ateş et” demekten farksız.
Agent mimarilerinde güvenliği sadece prompt engineering ile sağlayamazsınız. “Lütfen veritabanını silme” demek bir güvenlik önlemi değil, sadece bir temennidir. Gerçek dünyada, üretim (production) ortamında koşan bir agent’ın yeteneklerini, sistem seviyesinde ve uygulama mimarisinde katı kurallarla kısıtlamanız gerekir. Bu yazıda, kendi projelerimde uyguladığım ve “canı yanan” bir mühendis olarak geliştirdiğim 3 adımlı güvenli tool tasarımını anlatacağım.
1. Adım: Tool Tanımlarında Şema Sıkılaştırma (Schema Hardening)
LLM’lere (Large Language Models) bir tool sunduğunuzda, model bu tool’un ne işe yaradığını ve hangi parametreleri aldığını JSON Schema üzerinden anlar. Eğer şemanız gevşekse, model yaratıcılığını konuşturur ve beklemediğiniz parametreler uydurur. Kendi geliştirdiğim bir üretim ERP’sinde, stok sorgulama tool’una limit parametresi koymadığım için agent bir keresinde tüm stok geçmişini tek seferde çekmeye çalıştı.
Şema sıkılaştırma, agent’ın hareket alanını daha tanım aşamasında daraltmaktır. Sadece veri tipini (string, integer) belirtmek yetmez; minimum-maksimum değerleri, izin verilen enum listelerini ve zorunlu alanları (required fields) demir yumrukla belirlemelisiniz. Pydantic kullanarak bu şemaları oluşturmak, hem kodun okunabilirliğini artırıyor hem de modelin hata yapma payını azaltıyor.
from pydantic import BaseModel, Field, validator
from typing import Optional, List
class StockQuerySchema(BaseModel):
warehouse_id: int = Field(..., description="Sorgulanacak depo ID'si. Sadece 1, 2 veya 5 olabilir.")
sku_pattern: str = Field(..., min_length=3, max_length=20, description="Ürün kodu deseni.")
limit: int = Field(default=10, ge=1, le=100, description="Dönecek maksimum kayıt sayısı.")
@validator('warehouse_id')
def validate_warehouse(cls, v):
allowed = [1, 2, 5]
if v not in allowed:
raise ValueError(f"Depo ID {v} yetki dışı!")
return v
Yukarıdaki örnekte ge=1 (greater than or equal) ve le=100 (less than or equal) kısıtlamaları hayat kurtarır. Agent, kullanıcıdan gelen “tüm stokları getir” komutunu aldığında bile, bu şema sayesinde 100 kayıttan fazlasını isteyemeyeceğini anlar. Ayrıca, warehouse_id gibi kritik alanlarda validator kullanarak, modelin hayal gücüyle uydurabileceği ID’lerin önüne daha uygulama katmanında geçiyorum.
2. Adım: Runtime İzolasyonu ve Sandboxing
Diyelim ki agent’a bir “Python Interpreter” veya “Shell” yetkisi verdiniz. Bu, agent’ın sisteminizde herhangi bir komutu çalıştırabileceği anlamına gelir. Sıkıştırılmadığında bu yetki, agent’ın os.listdir('/') gibi tek bir satırla root dizinini gezmesine kadar varabilir; yani serbest bir kabuk, pratikte tüm işletim sistemine açılan bir kapıdır. Agent’ın tool’u çalıştırdığı ortam, ana uygulamanızdan ve işletim sisteminizden tamamen izole edilmelidir.
Benim tercihim, her bir tool execution (araç çalıştırma) işlemini kısıtlı bir Docker container’ı içinde veya en azından systemd-run ile kaynakları sınırlandırılmış bir sandbox’ta yapmaktır. Eğer agent kod yazıp çalıştıracaksa, bu kodun internet erişimi olmamalı, dosya sistemine erişimi sadece /tmp ile sınırlı kalmalı ve CPU/Memory limitleri (cgroups) katı bir şekilde uygulanmalıdır.
# Bir tool execution sürecini cgroups ile kısıtlama örneği
systemd-run --scope -p MemoryLimit=256M -p CPUQuota=50% --user python3 tool_executor.py
Bu yaklaşım, agent’ın sonsuz döngüye girmesi veya sistemi kilitlemeye çalışması durumunda ana uygulamanızın ayakta kalmasını sağlar. Kendi yan ürünümde, tool çalıştırma sürelerini kısa bir tavanla sınırladım. Bu süre içinde cevap gelmiyorsa, SIGKILL gönderip işlemi sonlandırıyorum. Bu sayede, agent’ın yanlışlıkla başlattığı ağır SQL sorguları veya karmaşık hesaplamalar tüm VPS’i (Virtual Private Server) çökertemiyor.
PostgreSQL performans tuning yazımda bahsettiğim gibi, veritabanı seviyesinde de statement_timeout ayarını mutlaka yapmalısınız. Agent’ın kullandığı veritabanı kullanıcısının yetkileri sadece SELECT ile sınırlı olmalı ve hatta mümkünse sadece belirli View’ları görebilmelidir. “Olur o kadar” diyerek root veya superuser yetkisi vermek, ileride başınıza gelecek felaketin davetiyesidir.
3. Adım: İzin Matrisi ve RBAC (Role-Based Access Control)
Her agent her tool’u kullanmamalıdır. Bir “Müşteri Destek Agent’ı” stok durumunu görebilmeli ama “Fiyat Güncelleme” tool’una erişememelidir. Yazılım dünyasında yıllardır kullandığımız RBAC prensiplerini agent mimarisine de entegre etmemiz gerekiyor. Ben bunu genellikle “Tool Registry” adını verdiğim bir merkezi yapıyla çözüyorum.
Bu yapıda, her tool’un bir “gerekli yetki” (required scope) tanımı var. Agent bir tool’u çağırmaya yeltendiğinde, mevcut oturumdaki (JWT içindeki) yetkilerle tool’un gereksinimleri karşılaştırılıyor. Eğer kullanıcı “admin” değilse ama agent “kullanıcı sil” tool’unu çağırmaya çalışıyorsa, sistem LLM’e gitmeden bu isteği reddediyor.
| Tool Adı | Gerekli Scope | Risk Seviyesi | Manuel Onay (HITL) |
|---|---|---|---|
get_stock_list |
inventory.read |
Düşük | Hayır |
update_price |
inventory.write |
Yüksek | Evet |
delete_order |
orders.admin |
Kritik | Evet |
send_email |
marketing.send |
Orta | Hayır |
Yukarıdaki tabloda gördüğünüz “Manuel Onay (HITL - Human-in-the-loop)” sütunu kritik. Yüksek riskli ve kritik işlemlerde, agent tool’u çağırmak istediğinde işlem hemen gerçekleşmez. Sistem, agent’ın talebini bir “bekleyen istek” olarak kaydeder ve kullanıcıya “Agent şu fiyatı güncellemek istiyor, onaylıyor musunuz?” diye sorar. Bu mekanizma, agent’ın kendi başına verebileceği zararların büyük çoğunluğunu daha gerçekleşmeden engelliyor.
Gözlemleme ve Rate Limiting: “Prompt Injection” Sonrası Felaket Senaryosu
Diyelim ki her şeyi yaptınız ama bir şekilde agent manipüle edildi. Bir saldırgan, agent’ı binlerce kez anlamsız tool call yapmaya zorlayarak API maliyetlerinizi (OpenRouter, Gemini Flash veya Groq faturalarını) şişirebilir. Bu yüzden, tool seviyesinde Rate Limiting uygulamak şart.
Ben projelerimde her bir kullanıcı ve agent oturumu için “dakikalık tool çağrı limiti” koyuyorum. Örneğin, bir kullanıcı dakikada en fazla 5 kez veritabanı sorgulatan bir tool’u tetikleyebiliyor. Bu limiti aşarsa, sistem agent’a “Çok fazla istek yaptın, biraz bekle” mesajı dönüyor. Redis tabanlı bir Fixed Window veya Sliding Window algoritması bu iş için biçilmiş kaftan.
# Redis ile basit bir rate limit kontrolü
async def check_tool_limit(user_id: str, tool_name: str):
key = f"limit:{user_id}:{tool_name}"
current = await redis.get(key)
if current and int(current) >= 5:
raise RateLimitExceeded("Dakikalık limit aşıldı.")
await redis.incr(key)
await redis.expire(key, 60)
Bu sadece maliyet güvenliği değil, aynı zamanda sistemin genel sağlığı için de önemli. Bir test sırasında, agent’ın recursive (özyinelemeli) bir şekilde kendi kendini çağıran bir mantık hatasına düştüğünü gördüm. Eğer bu rate limit olmasaydı, o günkü API faturası ciddi şekilde şişerdi.
Üretim ERP’sindeki Deneyimim: Stok Hareketlerini Yapay Zekaya Emanet Etmek
Bir üretim ERP’si üzerinde çalışırken, depo sorumlularının “Şu hammadde bittiğinde beni uyar ve eksik kadar sipariş taslağı oluştur” diyebilmesini istedik. Burada agent’ın hem stokları okuması hem de satın alma modülüne veri yazması gerekiyordu. 20 yıllık saha tecrübem bana şunu fısıldadı: “AI’ya doğrudan yazma yetkisi verme.”
Çözüm olarak “Shadow Tooling” adını verdiğim bir yöntem kullandım. Agent, create_purchase_order tool’unu çağırdığında, veritabanına gerçek bir sipariş kaydı atmak yerine, draft_orders adında geçici bir tabloya veri yazdı. Bu tabloya yazılan veriler, bir insan tarafından onaylanmadığı sürece finansal bir geçerliliğe sahip değildi.
Bu süreçte öğrendiğim en büyük ders, agent’ın hata mesajlarını nasıl okuduğuydu. Eğer tool bir hata döndürüyorsa (örneğin: “Yetersiz yetki”), bu hatayı agent’a çok açık bir şekilde vermelisiniz ki model neden başarısız olduğunu anlayıp döngüye girmesin. Ama dikkat; hata mesajında sistemin iç yapısına dair sırlar (veritabanı sütun isimleri, stack trace vb.) yer almamalı. Sadece “İşlem reddedildi: Yetki yetersiz” yeterlidir.
Güvenli Agent Tasarımı İçin Kontrol Listesi
Kendi sistemlerinizde bir agent ayağa kaldırırken şu listeyi yanınızda bulundurun. Bunlar “olur o kadar” denilip geçilecek maddeler değil, sistemin ayakta kalma garantisidir:
- Tool şemalarını Pydantic ile zorunlu kılın:
descriptionalanlarını LLM için,validatoralanlarını sistem güvenliği için doldurun. - Stateless execution sağlayın: Her tool çağrısı birbirinden bağımsız olmalı, bir önceki çağrıdan kalan “çöp” veriler bir sonrakini etkilememeli.
- Audit Log tutun: Agent hangi tool’u, hangi parametrelerle, ne zaman çağırdı ve sonuç ne oldu? Bu loglar, bir sorun çıktığında “root cause” analizi yapmak için tek kaynağınız olacak.
- Maliyet tavanı belirleyin: Sadece tool bazlı değil, genel oturum bazlı token ve maliyet limitleri koyun.
- Timeout’ları asla unutmayın: Hiçbir tool çağrısı sonsuza kadar sürmemeli. L4 (Network) ve L7 (Application) seviyesinde timeout’ları set edin.
Daha önce yazdığım Yazılım mimarisinde idempotency yazısında belirttiğim gibi, agent’ın aynı tool’u yanlışlıkla iki kez çağırması durumunda sistemin sapıtmaması gerekir. Özellikle ödeme sistemleri veya stok hareketleri gibi kritik alanlarda, her tool call bir request_id ile takip edilmeli ve mükerrer işlemler engellenmelidir.
AI agent’lar dünyası henüz çok yeni ve her gün yeni bir saldırı veya hata türüyle karşılaşıyoruz. Ancak temel mühendislik prensiplerini —izolasyon, kısıtlı yetki ve gözlemlenebilirlik— bu yeni dünyaya uyarladığımızda, korkmadan üretim ortamına çıkabiliriz. Unutmayın, en iyi agent, ne yapacağını bildiği kadar neyi yapmaya yetkisinin olmadığını da bilen agent’tır.
Sonraki adım: Agent’ların kendi aralarındaki iletişimde (multi-agent sistemler) güvenliği nasıl sağlarız? Onu da bir başka yazıda, yine sahada yaşadığım bir felaket senaryosu üzerinden anlatırım.