2026 márciusában négy órán keresztül egyetlen tenant rendszerpromptja időnként átszivárgott egy másik tenant válaszába. Nem teljes ömlés, hanem szilánkok — egy mondat itt, egy bekezdés ott. A bug a Redis prompt cache hash-kulcs konstrukciójában volt, és olyan ritkán ütött, hogy két napig nem reprodukáltuk megbízhatóan. Ez egy poszt arról, mi okozta, hogyan találtuk meg, és melyik bug-osztály az, amit még mindig nem tudunk teljesen kizárni.
A hibás hash-kulcs
A cache-kulcs eredeti formája sha256(systemPrompt + userMessage + modelId) volt. Tenant-prefix nem szerepelt — a feltételezés az volt, hogy ha két különböző tenant véletlenül ugyanazt a system promptot küldi (pl. a default „Te vagy egy segítőkész asszisztens”), akkor a cache-hit OK, mert a válasz mindkettőjüknek érvényes lenne. Ez ártatlannak tűnt — egészen addig, amíg egy tenant elkezdett dinamikusan injektált, ÜGYFÉL-SZINTŰ adatot tenni a system promptba (egy contact-information snippet, ami a támogatási hangnemet konfigurálta). Ettől a system prompt elvileg per-end-user változott, de a cache nem tudta. Ami ennél is rosszabb: a userMessage egy tisztított, normalizált verzió volt — két különböző felhasználói üzenet ugyanazt a normál formát adhatta, és a cache-kulcs összeolvasztotta őket.
A végeredmény: A tenant rendszerpromptja, beágyazott contact-szöveggel, B tenant válaszának kontextusává vált. A modell mindkétkor jó választ generált a kérdésre, de a hangnem (és néhány esetben a kontaktszemély neve) átszivárgott.
Hogyan találtuk meg
A bug egy ügyfélpanaszból indult: „Miért szólított minket Zoltánnak? Zoltán nem dolgozik nálunk.” A logokat nézve a request OK volt, a válasz OK volt, a system prompt korrekt — semmi sem stimmelt a panasszal. A Redis cache-en futottunk egy MONITOR-t (production-on, két percig, kontrolláltan), és láttuk: két szomszédos kulcs ugyanazt a hash-t adta, az egyikbe „Zoltán” volt írva, a másikba nem. Az OpenTelemetry trace-en a cache.hit=true és cache.key=<hash> together adta meg a smoking gunt.
A háromrétegű javítás
- Tenant-szkópolt kulcs-prefix. Minden cache-kulcs most
tenant:<tenantId>:prefixet kap. Ez az alapja minden további rétegnek. Egyszerű, idempotens, visszafelé kompatibilis (a régi kulcsok kiöregszenek TTL-en). - Per-key TTL osztály. Nem minden cache-bejegyzés egyenlő. A statikus rendszerpromptok 24 órán keresztül cache-elhetők, a dinamikus, ügyféladatot tartalmazók maximum 60 másodpercig. A
TtlClassifierszolgáltatás bemenete a system prompt; kimenete egy TTL-érték. Heurisztikák: ha a prompt tartalmaz placeholder pattern-t ({{customer_name}}típus), max 60s. Ha nem, 24h. - Audit-on-hit. Minden cache-hit egy
eng_cache_auditsort generál (tenant_id, cache_key, hit_at, response_size). Ez nem analytics — ez biztonsági trace. Ha valaha újra szivárgásgyanúnk lesz, ezen a táblán percek alatt megtaláljuk a cross-tenant ütközéseket.
Amit még mindig nem oldunk meg teljesen
A bug-osztály neve „semantic key collision”: két szintaktikailag különböző prompt, ami ugyanazt a választ kéri. Ezeket a normalizáció néha (de nem mindig) ugyanahhoz a kulcshoz hozza. A teljes védelem ellenük az, hogy minden tenant teljesen izolált cache-rétegben él, akkor is, ha a prompt szóról szóra megegyezik. Ez most a sztenderdünk — de a cache-hit ráta ennek köszönhetően 71%-ról 58%-ra esett. A teljesítményvesztés a biztonság ára, és ezt vállaljuk. Aki azt mondja, hogy nem kell trade-off, az még nem futtatott multi-tenant cache-t.
Poszt-mortem megjegyzés a detection time-ról
Az első szivárgási esemény és az első ügyfélpanasz között 92 perc telt el. A panasz és az első reprodukciós kísérlet között 18 óra, mert a ticketet kezelő mérnök hallucinációnak vette, nem szivárgásnak. Az incidens után átírtuk a support-engineering eszkalációs scriptet: minden olyan riport, ami egy ismeretlen nevet emleget egy válaszban, security review-t indít, nem model-quality review-t. A kettő könnyen összekeverhető, és az összekeverés ára magas. Az új script azóta három alkalommal tüzelt: kétszer helyesen non-issue-t azonosított (model-hallucinált plauzibilis magyar nevek), egyszer pedig valódi bugot fogott — egy tenant-prefix, ami egy Redis cluster rebalance közben kihullott. A tanulság, amit a csapat fennen visszhangoz: a security szempontú gyanú olcsóbb hibázni, mint a model-quality szempontú; az időveszteség minimális, a megelőzött kár potenciálisan tetemes.