Tüm yazılar
·6 dk okuma·ShamashAi Ekibi

ShamashAi Agent Token Güvenliği: SHA-256 Hash ve Plaintext Yönetimi

ShamashAi agent token'larını SHA-256 hash ile saklar, plaintext'i bir kez gösterir ve geri alınamaz hale getirir. API credential breach senaryolarında çoklu kapı riskini ortadan kaldıran mimari.

Cuma akşamı 19:47: DB yedeği sızdırılınca API token'lar nasıl patlak veriyor?

Cuma akşamı 19:47'de IT ekibi hafta sonu için dağılmak üzereyken, monitoring dashboard'unda olağandışı bir SQL Server bağlantısı belirir. İki dakika sonra 180 GB'lık database yedeği \backup-share\archived klasöründen silinir. Pazartesi sabahı forensic incelemede anlaşılır ki yetkisiz bir kullanıcı, eski bir domain admin hesabıyla backup share'e erişmiş, shamash_prod.bak dosyasını indirmiş ve silinmiş. Sorun şu: eğer bu yedekte API token'lar plaintext olarak saklanıyorsa, saldırgan artık tüm agent'ların kimlik bilgilerine sahip demektir. 45 farklı site'daki 200+ cihaz, her biri kendi token'ıyla ShamashAi'a log gönderiyordu. Plaintext token ele geçtiyse saldırgan şimdi sahte log gönderebilir, gerçek olayları maskeleyebilir veya agent'ları disable edebilir. Bu senaryo, credential storage mimarisinin neden hash-only olması gerektiğini gösterir.

ShamashAi, agent authentication token'larını yalnızca SHA-256 hash olarak SQL Server'da saklar. Plaintext token bir kez oluşturulurken kullanıcıya gösterilir, ardından backend'de kalıcı olarak silinir ve geri alınamaz hale gelir. Breach durumunda saldırgan DB'de yalnızca hash göreceği için token'ı reverse edemez ve agent kimliğine bürünemez. Bu yazıda ShamashAi'nin agent token mimarisini, oluşturma-saklama akışını, dbo.audit_log kayıtlarını ve revocation mekanizmasını inceleyeceğiz.


ShamashAi agent token formatı ve scope

ShamashAi agent token'ları shamashai_agent_<base64> formatında üretilir. Örnek:

shamashai_agent_aG9zdDpsb2NhbGhvc3Q6cG9ydDo4MDgw

Base64 segment, token'ın oluşturulduğu timestamp, project ID ve rastgele entropy'yi encode eder. Token per-project scope taşır: bir proje altında oluşturulan token, yalnızca o projenin resource'larına (device, site, log ingestion endpoint) erişebilir. Cross-project okuma veya yazma yetkisi yoktur.

Token oluşturulurken POST /auth/agent-tokens endpoint'i şu payload'ı bekler:

{ "project_id": "prj_89abcd", "label": "DC01-WinAgent", "expires_at": "2027-10-28T00:00:00Z" }

Backend, token'ı üretir ve bir kez plaintext olarak döner:

{ "token": "shamashai_agent_aG9zdDpsb2NhbGhvc3Q6cG9ydDo4MDgw", "token_id": "tok_xyz123", "warning": "Bu token bir daha gösterilmeyecek. Şimdi kopyalayın." }

Aynı anda backend, token'ın SHA-256 hash'ini hesaplar ve dbo.agent_tokens tablosuna yazar:

sql CREATE TABLE dbo.agent_tokens ( id UNIQUEIDENTIFIER PRIMARY KEY, project_id NVARCHAR(50) NOT NULL, label NVARCHAR(200), token_hash NVARCHAR(64) NOT NULL, -- SHA-256 hex created_at DATETIME2 DEFAULT GETUTCDATE(), expires_at DATETIME2, last_used_at DATETIME2, revoked_at DATETIME2, INDEX ix_project_id (project_id), INDEX ix_token_hash (token_hash) );

token_hash kolonu, plaintext token'ın SHA-256'sını hex string olarak (64 karakter) saklar. Plaintext token hiçbir zaman bu tabloya yazılmaz.


"Bir kez gösterilir, sonra geri alınamaz" akışı

Token oluşturulduğunda frontend (Next.js) kullanıcıya modal popup ile plaintext token'ı gösterir:

┌────────────────────────────────────────────────────────┐ │ Agent Token Oluşturuldu │ ├────────────────────────────────────────────────────────┤ │ shamashai_agent_aG9zdDpsb2NhbGhvc3Q6cG9ydDo4MDgw │ │ [Kopyala] │ │ │ │ ⚠️ Bu token bir daha gösterilmeyecek. │ │ Şimdi kopyalayın ve güvenli bir yere kaydedin. │ └────────────────────────────────────────────────────────┘

Kullanıcı modal'ı kapattıktan sonra plaintext token frontend state'inden de silinir. Backend response log'larında plaintext token hiçbir zaman loglanmaz; yalnızca token_id ve project_id audit'e yazılır:

{ "event_type": "AGENT_TOKEN_CREATED", "timestamp": "2026-10-28T09:17:00Z", "actor_user_id": "usr_abc", "resource_id": "tok_xyz123", "project_id": "prj_89abcd", "metadata": { "label": "DC01-WinAgent", "expires_at": "2027-10-28T00:00:00Z" } }

Eğer kullanıcı token'ı kaybederse, "Unuttum / kaybettim" seçeneği yoktur. Tek çözüm:

1. Eski token'ı revoke et (POST /auth/agent-tokens/{id}/revoke). 2. Yeni token oluştur. 3. Agent config'i güncelle.

Bu akış, plaintext token'ın backend'de veya log'larda kalmamasını garanti eder.


Agent tarafında: appsettings.json'a yapıştırma

ShamashAi .NET 8 agent, appsettings.json dosyasında token'ı okur:

{ "ShamashAi": { "ApiEndpoint": "https://shamashai.afnteknoloji.com/api", "AgentToken": "shamashai_agent_aG9zdDpsb2NhbGhvc3Q6cG9ydDo4MDgw", "ProjectId": "prj_89abcd", "SiteId": "site_istanbul_hq" }, "Serilog": { "MinimumLevel": "Information" } }

Agent başlatıldığında POST /events/ingest endpoint'ine heartbeat gönderir:

http POST /events/ingest HTTP/1.1 Host: shamashai.afnteknoloji.com Authorization: Bearer shamashai_agent_aG9zdDpsb2NhbGhvc3Q6cG9ydDo4MDgw Content-Type: application/json

{ "event_type": "AGENT_HEARTBEAT", "timestamp": "2026-10-28T09:20:00Z", "device_id": "dev_dc01", "site_id": "site_istanbul_hq" }

Backend, gelen Authorization: Bearer <plaintext_token> header'ını okur, SHA-256 hash'ini hesaplar ve dbo.agent_tokens.token_hash ile karşılaştırır:

csharp // Fastify equivalent logic (C# pseudo-code for clarity) string incomingToken = request.Headers["Authorization"].Replace("Bearer ", ""); string computedHash = ComputeSHA256(incomingToken);

var tokenRecord = await db.AgentTokens .Where(t => t.TokenHash == computedHash && t.RevokedAt == null && t.ExpiresAt > DateTime.UtcNow) .FirstOrDefaultAsync();

if (tokenRecord == null) return Unauthorized("Invalid or revoked token");

// Update last_used_at tokenRecord.LastUsedAt = DateTime.UtcNow; await db.SaveChangesAsync();

Node.js Fastify backend'de gerçek implementasyon:

javascript const crypto = require('crypto');

function computeSHA256(plaintext) { return crypto.createHash('sha256').update(plaintext, 'utf8').digest('hex'); }

fastify.addHook('preHandler', async (request, reply) => { const authHeader = request.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return reply.code(401).send({ error: 'Missing or invalid Authorization header' }); }

const incomingToken = authHeader.replace('Bearer ', ''); const computedHash = computeSHA256(incomingToken);

const tokenRecord = await db.query( `SELECT id, project_id, expires_at, revoked_at FROM dbo.agent_tokens WHERE token_hash = @hash AND revoked_at IS NULL`, { hash: computedHash } );

if (tokenRecord.length === 0 || new Date(tokenRecord[0].expires_at) < new Date()) { return reply.code(401).send({ error: 'Invalid or expired token' }); }

// Attach project context request.projectId = tokenRecord[0].project_id;

// Update last_used_at asynchronously db.query( UPDATE dbo.agent_tokens SET last_used_at = GETUTCDATE() WHERE id = @id, { id: tokenRecord[0].id } ).catch(err => fastify.log.error('Failed to update last_used_at', err)); });

Bu kod, plaintext token'ı bellekte hash'leyip DB'deki hash ile karşılaştırır. DB'de plaintext hiçbir zaman saklanmadığı için breach durumunda saldırgan token'ı kullanamaz.


last_used_at tracking: pasif token tespiti

dbo.agent_tokens.last_used_at kolonu, token'ın en son ne zaman kullanıldığını kaydeder. Security ekibi bu kolon sayesinde pasif token'ları tespit edebilir:

sql SELECT id, label, project_id, created_at, last_used_at, DATEDIFF(DAY, last_used_at, GETUTCDATE()) AS days_since_last_use FROM dbo.agent_tokens WHERE revoked_at IS NULL AND DATEDIFF(DAY, last_used_at, GETUTCDATE()) > 90 ORDER BY last_used_at ASC;

Bu sorgu, 90 gün boyunca kullanılmamış token'ları listeler. Örnek output:

id | label | days_since_last_use tok_old123 | OldServer-Agent | 120 tok_test456 | TestEnv-Agent | 95

IT Manager bu token'ları revoke edebilir:

http POST /auth/agent-tokens/tok_old123/revoke HTTP/1.1 Authorization: Bearer <admin_user_session>

Revoke işlemi revoked_at kolonunu doldurur ve token artık authenticate olamaz.


Revocation ve audit log

Token revoke edildiğinde dbo.audit_log şu kaydı yazar:

{ "event_type": "AGENT_TOKEN_REVOKED", "timestamp": "2026-10-28T10:05:00Z", "actor_user_id": "usr_abc", "resource_id": "tok_old123", "project_id": "prj_89abcd", "metadata": { "label": "OldServer-Agent", "reason": "Decommissioned server" } }

Revoke edilen token'la gelen sonraki istekler 401 döner. Agent log'unda şu hata görülür:

[ERROR] 2026-10-28 10:07:00 - Authentication failed: Invalid or revoked token [INFO] 2026-10-28 10:07:00 - Retrying in 60 seconds...

IT ekibi, alert aldığında yeni token oluşturup agent config'ini günceller.


Compliance: ISO 27001:2022 Annex A.9.4.3 (Password management)

ISO 27001:2022 Annex A.9.4.3 "Password management system" kontrol maddesi, credential'ların hash veya encrypt edilerek saklanmasını gerektirir. ShamashAi agent token mimarisi bu kontrolü şu şekilde karşılar:

  • A.9.4.3.a: Token'lar SHA-256 ile hash'lenir (irreversible).
  • A.9.4.3.b: Plaintext token yalnızca kullanıcıya bir kez gösterilir, backend'de saklanmaz.
  • A.9.4.3.c: last_used_at ve revocation mekanizması ile lifecycle yönetimi sağlanır.
Compliance raporu oluşturulurken GET /compliance/evidence endpoint'i şu kanıtları sunar:

{ "control": "A.9.4.3", "evidence": [ { "type": "database_schema", "table": "dbo.agent_tokens", "column": "token_hash", "storage_method": "SHA-256 hex" }, { "type": "audit_log_sample", "event_type": "AGENT_TOKEN_CREATED", "plaintext_stored": false } ] }


Breach senaryosu: hash'lenmiş token ne kadar güvenli?

DB yedeği sızdırıldığında saldırgan dbo.agent_tokens.token_hash kolonunu görür. SHA-256 hash'i reverse edemez mi?

Hayır, çünkü:

1. Preimage resistance: SHA-256, hash'ten plaintext'e geri dönüş hesaplama açısından imkansızdır (2^256 complexity). 2. Yüksek entropy: Token base64 segment'i 128-bit rastgele entropy içerir; rainbow table saldırısı için impractical. 3. Format prefix: shamashai_agent_ prefix, token'ı application-specific yapar; generic password dictionary'ler işe yaramaz.

Saldırgan yine de brute-force deneyebilir mi? Teorik olarak evet, ama:

  • ShamashAi API rate limiting uygular: aynı IP'den dakikada 10'dan fazla failed auth denemesi 429 Too Many Requests döner.
  • dbo.audit_log'da AUTH_FAIL_AGENT event'leri birikir ve BRUTE_FORCE_DETECTED alert'i tetiklenir.

Limitler ve dürüst notlar

  • Token rotation yok: ShamashAi şu anda otomatik token rotation (örneğin her 90 günde bir yenileme) sunmuyor. Manuel revoke + yeni token oluşturma gerekiyor.
  • Granular scope eksik: Token'lar project-level scope taşır; device veya site bazında kısıtlama henüz yok. Yani bir token, proje içindeki tüm cihazlar için geçerli.
  • Token export yok: Oluşturulmuş token'ların plaintext'ini sonradan export etme imkanı yok. Bu özellik güvenlik gereği kasıtlı olarak engellendi.
  • Offline agent desteği sınırlı: Agent internet bağlantısı kesildiğinde token doğrulama yapamaz; offline buffer mekanizması var ama authentication local cache'e dayanır (ilk başarılı auth sonrası).
  • Multi-project agent yok: Bir agent, yalnızca tek bir project token'ıyla çalışır. Aynı cihazda iki farklı projeye log göndermek için iki ayrı agent instance gerekir.

Sonuç: hash-only storage neden fark yaratır

ShamashAi'nin agent token'ları SHA-256 hash olarak saklaması, breach durumunda lateral movement riskini ortadan kaldırır. Cuma akşamı 19:47'deki senaryo gerçekleşse bile, saldırgan DB'den yalnızca hash alır ve agent kimliğine bürünemez. last_used_at tracking ile pasif token'lar tespit edilir, revocation ile lifecycle kontrol altında tutulur. Plaintext'in bir kez gösterilip geri alınamaz olması, security-by-design prensibinin somut uygulamasıdır.

Pilot programı 30 gün ücretsiz: shamashai.com.tr/iletisim

Paylaş

Bu konuyu projenize uygulayalım

Pilot programı kapsamında ürünü gerçek altyapınızda 30 gün ücretsiz deneyin.