Izolácia tenantov v agentovej pamäti, z konštrukcie
Väčšina agentových pamäťových knižníc filtruje podľa user_id, ktoré posielaš pri každom volaní. inspeximus 1.6.0 robí zo scope vlastnosť handle, takže žiadny zabudnutý parameter nevypustí tenanta. Potom sme si vlastný release red-teamovali, našli, že konsolidačný priebeh nebol scopovaný, a opravili to.
Toto je malá, čestná poznámka o zmene predvoleného postoja a o zachytení vlastnej chyby počas jej shipovania. Žiadny prevratný claim — len inžinierske rozhodnutie, ktoré vieme obhájiť, a číslo, ktoré sme odmerali.
Bežný spôsob, ako udržať pamäte jedného používateľa mimo recallu iného, je filtračný argument: pri každom čítaní pošleš user_id (alebo agent_id, run_id, group_id) a store vráti zhodné riadky. mem0, Zep aj Letta fungujú takto a je to rozumné. Jediná slabina je, že scopovanie žije vo volajúcom: zabudni argument na jednej ceste v kóde a tá cesta vidí všetkých. Nie je to chyba konkurencie — je to vlastnosť toho, že kontrolu dáš na miesto volania.
V inspeximus 1.6.0 je scope vlastnosťou handle. Naviažeš raz — store.for_tenant(id) vráti izolovaný pohľad nad jedným zdieľaným store, alebo Inspeximus(tenant=id) naviaže celý store — a potom niet argumentu, ktorý by sa dal zabudnúť. Nie je to nová myšlienka; je to complete mediation a fail-safe defaults, ktoré pomenovali Saltzer a Schroeder v roku 1975. Je to voľba predvoleného postoja, nie vynález.
Buďme jasní v tom, čo to nie je. Nie je to bezpečnostná hranica. Je to logická multi-tenancy v jednom procese: for_tenant() vráti pohľad, ktorý zdieľa ten istý podkladový zoznam záznamov, filtrovaný podľa tagu. Akýkoľvek kód držiaci neviazaný store, alebo chyba v knižnici, vidí všetko. Vzájomne nepriateľskí tenanti chcú oddelené stores, oddelené kľúče alebo databázou vynútenú row-level security — nie knižnicu v zdieľanom procese. To, čo získaš, je, že bežné cesty v kóde nevypustia tenanta vynechaním.
Kde izolácia naozaj uniká: zápisová cesta
Medzitenantový únik zriedka pochádza zo zjavného recall volania. Pochádza zo zápisovej cesty — zlučovanie entít, deduplikácia, konsolidácia, supersession, mazanie — kde sa kontrola "ktorý tenant?" ľahko vynechá, lebo kód pretvára záznamy, nie odpovedá na dotaz. Databázový svet sa to učí opakovane: CVE-2024-10976 bol PostgreSQL aplikujúci nesprávnu row-level-security politiku, keď sa dotaz znovu použil naprieč zmenami user-id. Filter bol v poriadku; jedna cesta bola nefiltrovaná.
Nie sme nad tým. Keď sme si vlastnú 1.6.0 pred napísaním tohto red-teamovali, audit našiel, že inspeximus konsolidačný priebeh — periodický krok, ktorý linkuje takmer-duplikáty a supersedeuje zastarané záznamy — skenoval celý store bez scope tenanta. Takže konsolidácia jedného tenanta mohla linknúť jeho záznam k záznamu iného tenanta alebo ho supersedeovať. Naša sonda tú cestu netestovala. Je to presne tá trieda chyby, ktorej má prístup na strane store zabrániť, sediaca v našom vlastnom release.
Opravili sme to: konsolidácia, klastrovanie, sken kontradikcií a kontrola konfliktu sa teraz scopujú na aktívneho tenanta. Potom sme sondu rozšírili, aby merala únik cez konsolidačný priebeh.
Čo sme odmerali
Päť tenantov zdieľa jeden fyzický store. Každý zapíše súkromné tajomstvo plus adverzariálnu sadu postavenú tak, aby presiakla cez hranicu — ten istý supersession kľúč s inou hodnotou, echo susedovej hodnoty, zdieľané id subjektu, PII riadky a takmer-duplicitný riadok, ktorý by nescopovaný dedup linkol naprieč tenantami. Potom každý tenant spustí každú medzitenantovú sondu.
- Medzitenantový únik pri čítaní (deteguje meno aj hodnotu tajomstva cudzieho tenanta): 0 z 20.
- Medzitenantová supersession keyed zápisom: 0.
- Konsolidačný priebeh linkujúci alebo supersedeujúci naprieč tenantami: 0 — s kontrolou potvrdzujúcou, že ten istý korpus produkuje 10 medzitenantových linkov, keď sa scope odstráni, takže tá nula je reálna prevencia, nie prázdny pass.
- Jeden tenant otravujúci recall iného zápisom falošnej hodnoty pod zdieľaným kľúčom: 0.
- Nadmerné mazanie cudzích riadkov, keď tenant spustí mazanie a PII sweep: 0, pričom jeho vlastné riadky sa správne zmazali.
Toto sú deterministické sondy, nie vzorkovaná pravdepodobnosť: ukazujú, že vynútené cesty nemajú defekt na tejto adverzariálnej sade, nie že únik je dokázateľne nulový. Kontrast, ktorý feature motivuje — ten istý korpus v jednom store recallovaný bez mäkkého scope argumentu uniká všetkých päť tajomstiev — je konfiguračný kontrast v rámci inspeximus, nie benchmark proti inému produktu. Sonda aj testy sú v repozitári; spusti ich a vyvráť nás.
Prior art, čestne
Nie sme prví a jeden konkurent je blízko. Zep Graphiti vynucuje svoj group_id namespace na úrovni úložiska naprieč čítaniami a zápismi — na tom istom mieste, kam inspeximus dáva kontrolu. Ostávajú dva rozdiely: Graphiti group_id je voliteľný a pri vynechaní padá na zdieľaný namespace (fail-open by default) a potrebuje grafovú databázu (Neo4j/FalkorDB). Úzky delta inspeximus je fail-closed by default, v jednom bezzávislostnom súbore, so scopovanou konsolidačnou cestou. Nedávny preprint MemTrust mapuje širšiu medzeru a charakterizuje viaceré pamäťové systémy ako spoliehajúce sa na "základné oddelenie namespaceov".
PII vrstva je podlaha, nie DLP
1.6.0 pridáva aj bezzávislostnú PII vrstvu: regex detektor taguje záznamy, recall(redact_pii=True) maskuje PII len vo vrátenom texte a forget_pii() prehrabe a tombstonuje PII riadky. Regex chytá štruktúrované formáty — email, SSN, kartu — a ľudské mená nedeteguje v podstate vôbec. Na náročnom 48-typovom benchmarku (PIIBench) aj plné Microsoft Presidio, regex plus trénovaný model, skóruje okolo F1 0.14. Takže je to rozumný default a pohodlie pri minimalizácii dát, nie ochrana a nie compliance; na detekciu použi reálne DLP.
Čestná hranica
Záruka končí pri návratovej hodnote recall(). Vo chvíli, keď je pamäť tenanta recallovaná do zdieľaného context-okna LLM alebo do cross-agent zdieľaného summary, scopovanie store už neplatí — neplatí nič, čo store ovláda. Read izolácia tiež nič nehovorí o tenantovi zapisujúcom, aby ovplyvnil recall iného, nad rámec key-level otravy, ktorú sme testovali. Tie švy — cross-context a cross-agent únik — sú tá zaujímavá nevyriešená časť a sú tam, kde záruka na úrovni store končí. Tvrdíme auditovateľnú, in-library izoláciu uložených záznamov a ich zápisových ciest, nie bezpečnostnú hranicu a nie dokázateľné zabúdanie.
inspeximus je pod MIT licenciou a bez závislostí: github.com/DanceNitra/inspeximus. Každé číslo tu sa regeneruje zo sondy a testov v repozitári.
FAQ
Nerobí mem0 alebo Zep multi-tenant izoláciu už teraz? Áno. Škálujú pamäť podľa user_id / agent_id / run_id / group_id. Rozdiel je, kde kontrola žije — vlastnosť handle (inspeximus) oproti argumentu, ktorý posielaš pri každom volaní — a že inspeximus scopuje konsolidačnú a mazaciu cestu, nielen recall.
Je to bezpečnostný feature? Nie. Je to logická, in-process izolácia, ktorá bráni úniku vynechaním na bežných cestách v kóde. Na reálnu hranicu dôvery medzi nepriateľskými tenantami použi oddelené stores alebo databázou vynútenú izoláciu.
Môžem stále bežať jeden store pre všetkých? Áno. Neviazaný store je admin pohľad a vidí všetko; store.for_tenant(id) rozdáva izolované pohľady, ktoré zdieľajú jeden fyzický store bez duplikácie.
Stačí PII detektor na GDPR? Nie. Je to regex podlaha pre dátovú minimalizáciu. Na detekciu použi reálne DLP; na auditovateľné mazanie uloženého záznamu použi forget_pii(), s vedomím, že nesiaha na externý index, cache ani na kontext modelu.