Blog/Retrieval és keresés

Hozzáférés-kezelés RAG-hoz: jogosultságtudatos visszakeresés dokumentumszivárgás nélkül

Hogyan szivárogtatnak dokumentumot a RAG-asszisztensek, és hogyan állítsd meg: ACL minden chunkon, előszűrés, sorszintű biztonság pgvectorban, jogszinkron.

··9 perc olvasás

  • RAG
  • Access control
  • Security
  • Multi-tenancy
  • Vector search
Egy felhasználói kérdés jogosultsági szűrőn halad át, így csak az általa olvasható chunkok jutnak el a nyelvi modellhez.

A lényeg röviden

  • A jogosultságokat a visszakeresési lekérdezésben érvényesítsd, mielőtt bármilyen szöveg a modellhez jut; a rendszerprompt nem hozzáférés-kezelés.
  • Minden chunk örökli a forrásdokumentuma ACL-jét, lehetőleg csoportazonosítókként, mellé bérlőazonosítót és az utolsó ACL-szinkron időpontját.
  • A szűrt vektoros keresésnek van egy recall-csapdája: a pgvector a szűrőt a HNSW-bejárás után alkalmazza, így egy 10%-os szűrő iteratív bejárás nélkül kb. 4 találatot hagyhat.
  • A jogosultságváltozások csak akkor hatnak, amikor elérik az indexet, ezért az ACL-eket a tartalomtól külön szinkronizáld, a magas kockázatú forrásokat pedig lekérdezéskor ellenőrizd.
  • A gyorsítótárak, összefoglalók, memória és embeddingek származtatott adatok, és ugyanazok a jogosultságok kellenek nekik, mint a forrásdokumentumaiknak.

Cikk meghallgatása

0:000:00

Egy belső RAG-asszisztens legrövidebb útja, ha mindent egyetlen szolgáltatásfiókkal indexelsz, és hagyod, hogy a modell válaszoljon. Ez egyben a legrövidebb út ahhoz is, hogy egy gyakornok rákérdezzen „az idei fizetési sávokra”, és forrásmegjelöléssel ellátott választ kapjon egy HR-táblázatból. A forrásrendszerben voltak jogosultságok; az index eltüntette őket.

Az OWASP ezt LLM08:2025 néven, a vektorok és embeddingek gyengeségeiként tartja számon: gyenge hozzáférés-kezelés az embeddingeken, szivárgás a bérlők között közös vektoradatbázisokban, és embedding-inverzió. Ez a cikk a gyakorlati oldal: hol érvényesítsd a jogosultságokat, mit kell minden chunknak hordoznia, hogyan csináld pgvectorban, Qdrantban, Pinecone-ban és Azure AI Searchben, hogyan tartsd szinkronban a jogokat, és hogyan teszteld, hogy semmi sem szivárog.

Hogyan szivárogtat dokumentumot egy RAG-rendszer?

Az általam átnézett rendszerekben a szivárgás ritkán ered ügyes támadásból. Ott keletkezik, ahol a jogosultságokat soha nem kapcsolták hozzá az adatokhoz:

  • ACL nélküli index. A tartalmat rendszergazdai vagy szolgáltatásfiókkal gyűjtötték be, és a chunkok semmilyen információt nem hordoznak arról, ki olvashatja őket.
  • Nyitva hibázó szűrők. Egy hiányzó csoport-claim üres szűrőt eredményez, az üres szűrő pedig mindenre illeszkedik.
  • Elavult jogosultságok. Valakit múlt héten eltávolítottak egy SharePoint-webhelyről; az index még mindig tartalmazza.
  • Származtatott adatok. Egy szemantikus gyorsítótár, egy beszélgetési memória vagy több dokumentumot összevonó összefoglaló egy második felhasználónak olyan anyagból válaszol, amelyet csak az első láthatott.
  • Túljogosított ágensek. Egy visszakereső eszköz a felhasználó identitása helyett a szolgáltatásfiókkal fut, így az ágens olyat is elér, amit a felhasználó nem.

Az utolsó pont az általános szabály a Az MI-ágensek identitások cikkből: a visszakeresés a felhasználó nevében, a felhasználó jogaival működjön, ne az őt futtató rendszer jogaival.

Hol történjen az engedélyezés?

A visszakeresési lekérdezésben, mielőtt bármi a modellhez érne. Ha egy chunk egyszer bekerült a promptba, semmilyen utasítás nem tartja megbízhatóan távol a választól, a prompt injection pedig közvetlenül rá is kérdezhet. A folyamat minden technológiai környezetben ugyanaz: hitelesítsd a felhasználót, oldd fel a csoportjait, add hozzá ezeket szűrőként a kereséshez, és csak a megmaradt chunkokat add tovább rerankelésre és generálásra.

Jogosultságtudatos visszakeresésA felhasználói tokent bérlőre és csoportlistára oldjuk fel. A keresési lekérdezés ezekre a csoportokra szűr, így a jogosulatlan chunkok soha nem lesznek jelöltek. A rerankelés és a nyelvi modell csak engedélyezett chunkokat lát.Felhasználói tokenOIDC, EntraFeloldásbérlő, csoportokSzűrt keresésACL a lekérdezésbenRerankcsak engedélyezettLLMforrást idéz
Ha a csoportfeloldás sikertelen, a kérés is sikertelen; soha nem esik vissza szűretlen keresésre.

Külön indexek, előszűrés vagy utószűrés?

Négy minta létezik, és a legtöbb valós rendszer kettőt kombinál: szigorú elszigetelést a bérlők között, és dokumentumszintű szűrést minden bérlőn belül.

MintaMűködésErősségGyengeség
Bérlőnkénti index vagy névtérA lekérdezés egy bérlő partícióját célozzaHiányzó szűrő nem léphet át bérlőt; kivezetés törlésselBérlőn belül nincs dokumentumszintű jog
Metaadat-előszűrésEngedélyezett csoportok chunkonként, szűrő a lekérdezésbenJogosulatlan chunk soha nem lesz jelöltAz ACL-eket be kell szinkronizálni az indexbe
Utószűrés engedélyezési szolgáltatássalJelöltek lekérése, majd mindegyik ellenőrzése OpenFGA-val vagy hasonlóvalMindig aktuális jogokTúllekérés, üres oldalak, késleltetés
Sorszintű biztonság az adatbázisbanA Postgres-szabályok a munkamenet szerepköre szerint szűrik a sorokatAlapból tilt, az alkalmazás alatt érvényesülCsak Postgreshez; recall-csapda ANN-indexekkel

Az OpenFGA leírja az utószűrés változatait: minden jelöltnél ellenőrizd a can_view relációt, vagy hívd meg egyszer a list-objects műveletet, hogy megkapd az összes dokumentumot, amelyet a felhasználó olvashat, és metszd a jelöltekkel. Kis halmazokra dokumentumonkénti ellenőrzést, nagyobbakra list-objects-et javasol. Minden utószűrés ára, hogy a szükségesnél többet kell lekérned, mert egyes jelöltek kiesnek.

Mit kell rendelkeznie minden chunknak?

A chunkok öröklik a forrásdokumentumuk jogosultságait. Ahol lehet, egyéni felhasználók helyett csoportokat tárolj; a Microsoft ugyanezt tanácsolja az ACL-lel védett tartalmakhoz Azure AI Searchben, a Pinecone pedig antipatternnek nevezi a hosszú felhasználóazonosító-listákra szűrést, ahol egy $in operátor legfeljebb 10 000 értéket fogad. Egy minimális adatcsomag:

{
  "chunk_id": "sp-hr-0412#7",
  "tenant_id": "acme",
  "doc_id": "sp-hr-0412",
  "source": "sharepoint://hr/salary-bands-2026.xlsx",
  "allowed_groups": ["grp-hr", "grp-exec"],
  "classification": "confidential",
  "acl_version": "2026-10-16T22:05:11Z",
  "text": "..."
}

Az acl_version mező teszi auditálhatóvá a szinkront: látod, milyen régiek az egyes chunkok jogosultságai, és megtalálod azokat, amelyekből kimaradt egy frissítés. Bármihez, ami több dokumentumból származik, például egy összefoglalóhoz, a csoportjaik metszetét használd, ne az unióját.

Hogyan csináld pgvectorban?

A Postgres sorszintű biztonsága az alkalmazás alá helyezi a szűrőt. Ha egy táblán bekapcsolod, minden normál hozzáférést egy szabálynak kell engedélyeznie, szabály nélkül pedig alapból tiltás érvényes. A szuperfelhasználók, a BYPASSRLS jogú szerepkörök és általában a tábla tulajdonosa megkerülik, ezért az alkalmazásnak külön szerepkörrel kell csatlakoznia, a FORCE ROW LEVEL SECURITY pedig a tulajdonosra is kiterjeszti a szabályt.

ALTER TABLE chunks ENABLE ROW LEVEL SECURITY;
ALTER TABLE chunks FORCE ROW LEVEL SECURITY;

-- The app sets the caller's tenant and groups per transaction.
CREATE POLICY chunk_read ON chunks FOR SELECT TO rag_app
  USING (
    tenant_id = current_setting('app.tenant_id')
    AND allowed_groups && string_to_array(current_setting('app.groups'), ',')
  );

BEGIN;
SET LOCAL app.tenant_id = 'acme';
SET LOCAL app.groups = 'grp-sales,grp-all';
SET LOCAL hnsw.iterative_scan = relaxed_order;   -- pgvector 0.8.0+
SELECT chunk_id, text FROM chunks
ORDER BY embedding <=> $1 LIMIT 20;
COMMIT;

Az utolsó beállítás fontosabb, mint amilyennek látszik. A pgvector a szűrőket a közelítő index bejárása után alkalmazza, és az alapértelmezett 40-es hnsw.ef_search mellett egy, a sorok 10%-ára illeszkedő szűrő kb. 4 találatot hagy. A jogosultsági szűrő pontosan ilyen szűrő, a tünet pedig nem szivárgás, hanem egy asszisztens, amely kevés joggal rendelkező felhasználóknak „nem talál” dokumentumokat. A pgvector 0.8.0-ban bevezetett iteratív indexbejárás addig folytatja, amíg elég sor nem illeszkedik, legfeljebb hnsw.max_scan_tuples sorig (alapból 20 000). Bérlőknél segít egy B-fa index a tenant_id oszlopon vagy a bérlő szerinti listapartícionálás is. A pgvector és vektoradatbázisok összehasonlításom megmutatja, mikor elég ez.

Mit kínál a Qdrant, a Pinecone és az Azure AI Search?

  • A Qdrant egy bérlőmezős gyűjteményt javasol a bérlőnkénti gyűjtemények helyett. Egy is_tenant true értékű kulcsszavas payload-index (v1.11-től) együtt tárolja az egyes bérlők vektorait, minden lekérdezés must szűrőt visz a bérlőre, v1.16-tól pedig a többszintű bérlőkezelés a nagy bérlőket saját shardba emeli. A dokumentumszintű csoportok további szűrőként a payloadba kerülnek.
  • A Pinecone bérlőnként egy névteret javasol egy serverless indexben. A névterek külön tárolódnak, amit a Pinecone fizikai elszigetelésnek nevez, a kivezetés pedig a névtér törlése. A lekérdezéseket a névtér mérete szerint számlázzák, így egy közös 100 GB-os névtér szűrése százszor többe kerül, mint egy 1 GB-os bérlői névtér lekérdezése.
  • Az Azure AI Search támogatja a biztonsági szűrőket, vagyis egyszerű karakterlánc-összehasonlítást egy, az alkalmazás által kitöltött identitásmezőn, előzetes verzióban pedig natív dokumentumszintű ACL-eket és RBAC-hatóköröket, SharePoint ACL-eket és Purview bizalmassági címkéket, amelyeket a felhasználó Entra tokenje érvényesít az x-ms-query-source-authorization fejlécben. A Microsoft egyértelművé teszi, hogy a biztonsági szűrő karakterlánc-egyezés, nem hitelesítés, tehát az alkalmazásnak előbb ellenőriznie kell az identitást.

Hogyan tartsd szinkronban a jogosultságokat?

Az index másolat, a másolatok pedig elavulnak. A Microsoft dokumentációja ezt kerek perec kimondja: a forrásrendszer jogosultságváltozásai csak akkor jelennek meg a keresési eredményekben, ha a metaadatok szinkronizálódtak az indexbe, SharePointnál pedig a szülő webhelyről, tárból vagy mappából örökölt változásokhoz kifejezett frissítés kell. Minden más technológiai környezet ugyanígy működik, akár dokumentálják, akár nem. Három intézkedés tartja kicsiben az ablakot:

  1. Az ACL-eket a tartalomtól külön szinkronizáld. Az allowed_groups frissítése metaadat-írás, nem újraembeddelés. Sokkal gyakrabban futtasd, mint a tartalomindexelést, és riassz, ha az acl_version lemarad.
  2. A csoportokat lekérdezéskor oldd fel. A csoportokat a chunkokon tárold, a felhasználó aktuális csoporttagságát pedig kérésenként az identitásszolgáltatótól kérd le, így egy csoportból való eltávolítás azonnal hat.
  3. A magas kockázatú forrásokat élőben ellenőrizd újra. HR-, jogi vagy igazgatósági anyagoknál a végső jelöltekre adj hozzá utószűrést a forrásrendszer vagy egy engedélyezési szolgáltatás felé, és csak ezeknél a gyűjteményeknél fogadd el a többletkésleltetést.

Mi a helyzet a gyorsítótárakkal, összefoglalókkal és embeddingekkel?

Minden, amit dokumentumokból számolunk, örökli azok érzékenységét. Egy szemantikus gyorsítótár kulcsának tartalmaznia kell a felhasználó jogosultsághalmazát, különben az egyik felhasználó válasza egy másikhoz kerül; a hatókör kezelését a szemantikus gyorsítótárazás cikkben írtam le. A beszélgetési memória egyetlen felhasználóé. A több dokumentumból generált összefoglalók, entitásleírások és chunkkontextusok a csoportjaik metszetét kapják.

Az embeddingek sem névtelenek. Morris és társai 2023-ban megmutatták, hogy egy iteratív inverziós módszer a 32 tokenes bemenetek 92%-át pontosan visszaállította az embeddingjeikből, klinikai jegyzetekből pedig teljes neveket nyert vissza. A vektortároló kapja meg ugyanazt a hálózati elszigetelést, titkosítást és hozzáférés-naplózást, mint a forrásdokumentumok, és vond be a törlési kérelmekbe is.

Hogyan teszteld, hogy semmi sem szivárog?

A visszakeresési réteget teszteld, ne a válaszokat. A modell megtagadhatja egy kapott chunk idézését, ezzel elfedve a hibát. Helyezz el egyedi jelölőszöveggel ellátott kanári-dokumentumokat, mindegyiket pontosan egy csoport számára láthatóan, és a visszakeresett chunkazonosítókra írj ellenőrzést:

import pytest

PERSONAS = {"sales": ["grp-sales"], "hr": ["grp-hr"], "none": []}
CANARIES = {"canary-hr": "grp-hr", "canary-sales": "grp-sales"}
QUERIES = ["salary bands", "canary report", "confidential", "*"]

@pytest.mark.parametrize("persona,groups", PERSONAS.items())
@pytest.mark.parametrize("query", QUERIES)
def test_no_canary_leaks(persona, groups, query):
    hits = retrieve(query, tenant="acme", groups=groups, k=50)
    for doc_id, owner_group in CANARIES.items():
        if owner_group not in groups:
            assert all(h.doc_id != doc_id for h in hits), (persona, query, doc_id)

def test_missing_groups_fail_closed():
    with pytest.raises(PermissionError):
        retrieve("anything", tenant="acme", groups=None, k=10)

Adj hozzá még két ellenőrzést: a visszavonási késleltetést, amikor egy tesztfelhasználót eltávolítasz egy csoportból, és méred, mennyi idő alatt tűnik el a kanári, valamint a bérlői elszigetelést, amikor az A bérlő érvényes tokenjével a B bérlő névterére kérdezel. Futtasd a tesztkészletet CI-ban és a szinkronfeladat minden módosítása után. A hozzáférés-kezelés a RAG-mérgezés elleni védekezés mellé tartozik: mindkettő a tudásbázist védi, az egyik az olvasás, a másik az írás ellen.

Források

  1. OWASP GenAI Security Project: LLM08:2025 Vector and Embedding Weaknesses
  2. PostgreSQL documentation: Row Security Policies
  3. pgvector README: filtering and iterative index scans
  4. Qdrant: Multitenancy and partitions guide
  5. Pinecone: Implement multitenancy using namespaces
  6. Microsoft Learn: Document-level access control in Azure AI Search
  7. OpenFGA: RAG authorization use case
  8. Morris et al.: Text Embeddings Reveal (Almost) As Much As Text (EMNLP 2023)

Gyakori kérdések

Mi a jogosultságtudatos RAG?

Olyan RAG-rendszer, amely csak azokat a chunkokat keresi vissza, amelyeket a kérdező felhasználó a forrásrendszerben olvashat. A felhasználó identitását csoportokra oldjuk fel, és ezek a csoportok szűrik a keresést, mielőtt bármilyen szöveg a modellhez kerülne.

A vektoros keresés előtt vagy után szűrjek?

Előtte, amikor csak a tároló támogatja. A lekérdezésbe épített előszűrés a jogosulatlan chunkokat eleve kizárja a jelöltek közül. Az engedélyezési szolgáltatással végzett utószűrés is működik, de többet kell lekérned, és üres találati oldalakat kockáztatsz.

Biztonságosabb a bérlőnkénti index, mint a metaadat-szűrő?

Erősebb elszigetelést ad: egy lekérdezés akkor sem éri el egy másik bérlő adatait, ha hiányzik egy szűrő, a kivezetés pedig a névtér törlése. A Pinecone bérlőnként egy névteret javasol. Egy bérlőn belül azonban továbbra is dokumentumszintű szűrők kellenek.

Kiszivárogtathatják az embeddingek az eredeti szöveget?

Igen, bizonyos mértékig. Morris és társai 2023-ban kimutatták, hogy egy iteratív inverziós módszer a 32 tokenes bemenetek 92%-át pontosan visszaállította az embeddingjeikből. A vektortárolót ugyanúgy védd, mint a forrásdokumentumokat.

Hogyan teszteljem, hogy a jogosultságok tartanak?

Helyezz el csak egy csoport által látható kanári-dokumentumokat, futtass rögzített kérdéskészletet minden csoport felhasználójaként, és ellenőrizd, hogy egyetlen kanári-chunk sem jelenik meg olyan találati halmazban, ahová nem tartozik. Futtasd CI-ban és a jogosultságszinkron minden módosítása után.

Pont erre van szükséged?

Írj a projektedről vagy a pozícióról – szívesen hallok felőled.