Architekturen für sichere RAG-Systeme mit RBAC & ABAC

Sichere RAG Architekturen

Die integration von Large Language Models (LLMs) in die Infrastruktur von Unternehmen verspricht eine Revolution des Wissensmanagements. Durch das Paradigma der Retrieval-Augmented Generation (RAG) können generative Modelle auf interne Datenquellen zugreifen, um hochgradig kontextualisierte und präzise Antworten zu liefern – ein Thema, das wir in unserem Artikel Von Chatbots zu intelligenten Systemen eingehend beleuchtet haben. In der Praxis führt die ungesicherte Zusammenführung von semantischen Suchmaschinen und LLMs jedoch zu einem gravierenden Sicherheitsrisiko: dem abteilungsübergreifenden Abfluss vertraulicher Daten (Cross-Departmental Data Leakage). Ohne eine robuste Zugriffskontrolle agiert der RAG-Agent als universeller Daten-Katalysator, der isolierte Informationssilos aufbricht und sensible Dokumente wie Gehaltslisten, Fusionspläne oder Forschungsberichte unautorisierten Mitarbeitern zugänglich macht.

Für Sicherheitsarchitekten ist die Implementierung einer Zero-Trust-Architektur auf Dokumentenebene (Document-Level Security, DLS) daher eine zwingende Voraussetzung für den Produktivbetrieb. Dieses Dokument beschreibt die technischen Mechanismen zur Durchsetzung von rollenbasierten (RBAC) und attributbasierten (ABAC) Zugriffskontrollen innerhalb des RAG-Retrieval-Layers. Es analysiert die mathematischen und architektonischen Unterschiede zwischen Pre-Retrieval- und Post-Retrieval-Filterung, demonstriert die Synchronisation von Active Directory und Entra ID mit modernen Vektordatenbanken und bietet ein praxisnahes Implementierungs-Tutorial auf Basis von PostgreSQL, pgvector und Row-Level Security (RLS). Dabei orientieren wir uns an den Empfehlungen des BSI zur Künstlichen Intelligenz und berücksichtigen die Anforderungen des EU AI Act an vertrauenswürdige KI-Systeme.

1. Die Taxonomie der Zugriffskontrolle in RAG-Systemen

In traditionellen IT-Systemen sind Zugriffsrechte fest in den jeweiligen Datenspeichern verankert. Ein Dateisystem verweigert den Lesezugriff auf einen Ordner; eine relationale Datenbank blockiert unautorisierte SQL-Abfragen. RAG-Systeme brechen dieses Modell auf, da sie Daten aus unterschiedlichsten Quellen extrahieren, in Chunks aufteilen, in Vektoren transformieren und in einem zentralen Vektorspeicher konsolidieren. Der Sicherheitsarchitekt muss daher sicherstellen, dass die Zugriffssteuerung auf jeder Ebene der RAG-Pipeline durchgesetzt wird.

Eine ganzheitliche Sicherheits-Taxonomie gliedert die Autorisierungsebenen in fünf granulare Stufen:

Autorisierungsebene Technischer Fokus Durchsetzungs-Mechanismus Primäres Sicherheitsrisiko
Cluster-Ebene Infrastruktur-Zugang API-Gateways, IAM-Rollen, TLS-Client-Zertifikate, OAuth2-Tokens. Unautorisierter Zugriff auf die gesamte databaseinstanz.
Index-Ebene Logische Datensegmentierung Aufteilung in separate Indizes nach Abteilungen (z. B. hr-index, finance-index). Starrheit der Architektur; komplexe abteilungsübergreifende Suchanfragen sind unmöglich.
Dokument-Ebene Granulare Inhaltsfilterung Bindung von Access Control Lists (ACLs) oder Sicherheitslabels als Metadaten an einzelne Text-Chunks. Cross-Departmental Data Leakage; vertrauliche Datenfragmente fließen in den LLM-Prompt.
Feld-Ebene Strukturelle Maskierung Ausblenden spezifischer Metadaten-Felder (z. B. Ersteller, Erstellungsdatum, Quellpfad) im Suchergebnis. Unbeabsichtigtes Ausspähen von Metadaten (Metadata-Snooping) trotz gesperrtem Dokumenteninhalt.
Attribut-Ebene Dynamische Kontextprüfung Einbeziehung transienter Faktoren wie Benutzer-Standort, Uhrzeit oder temporäre Sicherheitsfreigaben. Berechtigungsmissbrauch durch veraltete statische Rollenzuweisungen.

Die Tabelle illustriert die Eskalation von groben Instanz-Berechtigungen hin zu feingranularen, dynamischen Attributprüfungen. Während die Instanz-Ebene lediglich den Zugriff auf die gesamte Datenbank regelt und die Index-Ebene eine logische Datensegmentierung nach Abteilungen vornimmt, liegt der kritische Fokus für die Verhinderung von Datenlecks auf der Dokument-Ebene. Hier werden Access Control Lists (ACLs) oder Sicherheitslabels als Metadaten an einzelne Text-Chunks gebunden. Die Feld-Ebene adressiert das Risiko des Metadata-Snooping, indem spezifische Felder wie Ersteller oder Quellpfad ausgeblendet werden. Die Attribut-Ebene schließlich bezieht transiente Faktoren wie Benutzer-Standort oder temporäre Sicherheitsfreigaben ein und verhindert so Berechtigungsmissbrauch durch veraltete statische Rollenzuweisungen. Die Komplexität dieser Ebenen verdeutlicht, warum ein tiefes Verständnis der zugrundeliegenden Parameter in LLM Modellen und ihrer Interaktion mit Sicherheitsmetadaten essentiell ist.

RBAC versus ABAC im semantischen Raum

Die rollenbasierte Zugriffskontrolle (RBAC) weist Berechtigungen vordefinierten Benutzerrollen (z. B. "Finanzanalyst", "HR-Spezialist") zu. Im Kontext eines RAG-Systems bedeutet dies, dass Dokumenten-Chunks mit zulässigen Rollen etikettiert werden. Das System vergleicht die Rolle des anfragenden Benutzers mit diesen Etiketten. Dieser Ansatz ist intuitiv, skaliert jedoch schlecht in großen Organisationen, wo die Anzahl der Rollen exponentiell mit der Diversität der Zugriffsanforderungen wachsen kann.

Die attributbasierte Zugriffskontrolle (ABAC) bietet eine wesentlich feinere Granularität, indem sie dynamische Attribute des Benutzers (z. B. Abteilung, Sicherheitsfreigabe, geografische Region) und des Objekts (z. B. Erstellungsdatum, Vertraulichkeitsstufe) auswertet. Dies verhindert eine Explosion der Rollenanzahl (Role Explosion) in großen Konzernen und ermöglicht dynamische Autorisierungsentscheidungen in Echtzeit. Während RBAC statische Labels wie role:finance verwendet, evaluiert ABAC boolesche Richtlinien wie user.clearance >= document.classification AND user.department == document.owner_department. Diese Flexibilität ist entscheidend für die Umsetzung von Zero-Trust-Prinzipien, wie sie im Stanford AI Index als Best Practice für KI-Sicherheit hervorgehoben werden.

2. Ingestion-Sicherheit: PII-Reduzierung, Verifizierung und Quarantäne

Die Sicherheit eines RAG-Systems beginnt nicht erst bei der Abfrage, sondern bereits beim Laden der Daten (Ingestion). Eine sichere Ingestion-Pipeline muss sensible Informationen automatisch erkennen, klassifizieren und gegebenenfalls unschädlich machen, bevor sie im Vektorspeicher indiziert werden. Dieser Schritt ist fundamental, da einmal indizierte Daten selbst bei perfekter Query-Time-Filterung ein Restrisiko darstellen, falls die Embeddings sensible Muster kodieren.

Die automatisierte Pipeline für sensible Daten

Ein bewährtes Architekturmuster zur Absicherung des Datenimports basiert auf einer mehrstufigen, ereignisgesteuerten Pipeline, wie sie im folgenden Ablaufdiagramm dargestellt ist:

flowchart TD; A --> B; B --> C{Kritikalität >= 3?}; C -- Ja --> D[Quarantäne-Ordner & Alarm]; C -- Nein --> E; E --> F;

Der Prozess gliedert sich in fünf Stufen:

  1. Ereignisauslösung: Das Hochladen eines neuen Dokuments in einen Amazon S3 Bucket löst über Amazon EventBridge eine AWS Lambda-Funktion (Stufe 1) aus. Dieses ereignisgesteuerte Muster entkoppelt die Komponenten und ermöglicht eine asynchrone, robuste Verarbeitung.
  2. PII-Erkennung und Reduzierung: Lambda Stage 1 initiiert einen Amazon Comprehend Job zur Erkennung und Schwärzung personenbezogener Daten (Personally Identifiable Information, PII). Der Status des Jobs und die zugehörige ID werden in einer Amazon DynamoDB-Tabelle protokolliert. Diese Protokollierung ist essentiell für Audit-Trails und die Nachvollziehbarkeit automatisierter Entscheidungen.
  3. Sekundäre Verifizierung: Nach Abschluss der Schwärzung verschiebt das System das Dokument in einen separaten S3-Ordner (macie/). Ein zweiter Lambda-Dienst (Stage 2) startet eine tiefergehende Überprüfung mithilfe von Amazon Macie, um komplexe sensible Datenmuster zu identifizieren, die von der ersten Stufe möglicherweise übersehen wurden. Dieser zweistufige Ansatz folgt dem Defense-in-Depth-Prinzip.
  4. Quarantäne-Entscheidung: Erkennt Amazon Macie verbliebene sensible Daten mit einer Kritikalität (Severity) von 3 oder höher, wird das Dokument in einen gesperrten Quarantäne-Ordner verschoben und ein Sicherheitsalarm generiert. Dokumente mit einer Kritikalität von unter 3 werden in den bereinigten Ordner (redacted/) transferiert. Dieser automatisierte Entscheidungsbaum verhindert, dass riskante Dokumente manuell überprüft werden müssen, bevor eine Entscheidung gefällt wird.
  5. Sichere Indexierung: Erst aus diesem bereinigten Ordner extrahiert die Amazon Bedrock Knowledge Base die Daten, führt das Chunking durch und indiziert die Vektoren im OpenSearch-Vektorspeicher unter automatischer Zuweisung der Sicherheitsmetadaten. Die strikte Trennung von Quarantäne- und bereinigtem Ordner stellt eine physische Barriere dar, die verhindert, dass ein Fehler in der Indexierungslogik sensible Daten erfasst.

3. Synchronisation von Verzeichnisdiensten mit Vektorspeichern

Die größte Herausforderung für Sicherheitsarchitekten besteht darin, die Berechtigungsstrukturen aus führenden Systemen wie Microsoft Active Directory oder Entra ID konsistent in den Vektorspeicher zu übertragen. Ohne automatisierte Synchronisation droht eine allmähliche Auseinanderentwicklung der Sicherheitsrichtlinien, die entweder zu unberechtigten Zugriffen oder zu einer Blockade legitimer Arbeitsprozesse führt. Diese Synchronisation ist das Bindeglied zwischen klassischer IAM-Governance und moderner KI-Infrastruktur.

Ingestion-Time: SharePoint ACL-Normalisierung via Microsoft Graph API

Bei der Integration weit verbreiteter Dokumentenquellen wie SharePoint Online müssen die dortigen komplexen Vererbungsstrukturen (Site \rightarrow Library \rightarrow Folder \rightarrow File) aufgelöst werden. Ein direkter Zugriff auf SharePoint-ACLs zur Laufzeit ist aufgrund der hohen Latenzzeiten der Microsoft Graph API in der Regel nicht praktikabel. Daher müssen die Berechtigungen während des Ingestion-Prozesses materialisiert, d.h. als Metadaten direkt an die Dokumentenchunks geheftet werden.

Sicherheitsarchitekten sollten hierbei das Least-Privilege-Modell implementieren. Die Ingestion-Pipeline registriert sich in Microsoft Entra ID mit der Berechtigung Sites.Selected, was den administrativen Zugriff auf explizit freigegebene SharePoint-Seiten beschränkt, anstatt globale Leserechte für den gesamten Mandanten zu erteilen. Dieses Vorgehen minimiert die potenzielle Angriffsfläche der Pipeline selbst.

Der Normalisierungsprozess

  1. Abruf der Berechtigungen: Für jedes Dokument ruft die Pipeline die effektiven Berechtigungen über den Microsoft Graph-Endpunkt /permission ab.
  2. Identitäts-Normalisierung: Da Benutzernamen oder E-Mail-Adressen (UPNs) im Unternehmen volatil sind (z. B. durch Heirat oder Umstrukturierungen), müssen alle Identitäten zwingend auf ihre unveränderlichen Microsoft Entra ID Object-GUIDs normalisiert werden. Die Verwendung volatiler Identifier würde zu einer schleichenden Aufweichung der Zugriffskontrollen führen, da ACLs nach einer UPN-Änderung nicht mehr greifen.
  3. Materialisierung im Index: Die normalisierten Gruppen- und Benutzer-GUIDs werden als filterbare Arrays direkt an den Text-Chunk im Vektorspeicher angehängt.
{
  "chunk_id": "c7a8b94d-2101-4993-bd82-f381a1a2b3c4",
  "document_name": "Q3_Strategic_Plan.pdf",
  "content": "Strategische Prioritäten für das dritte Quartal umfassen...",
  "allowed_users": ["550e8400-e29b-41d4-a716-446655440000"],
  "allowed_groups": [
    "e8b2cd1a-9fbc-4927-ba21-12569fa12034",
    "a417fd92-3c82-410a-bd91-9981a2f30129"
  ]
}

Bei Azure AI Search erfolgt die Zuweisung dieser Metadaten über vordefinierte Berechtigungsfelder. Bei Abfragen übergibt die Anwendung den Entra ID-Token des Benutzers im Header x-ms-query-source-authorization, woraufhin die Suchmaschine den Zugriff nativ prüft und unautorisierte Dokumente verwirft. Dieser native Mechanismus entlastet die Anwendungslogik von manuellen Filteroperationen und reduziert das Risiko von Implementierungsfehlern.

Databricks Unity Catalog und Delta Sync Indizes

In modernen Datenplattformen wie Databricks wird die Governance über den Unity Catalog realisiert. Für RAG-Szenarien bietet Databricks eine native Synchronisationsmöglichkeit über sogenannte Delta Sync Indizes.

Hierbei existieren zwei grundlegende Architekturmodelle:

flowchart TD; subgraph Modell_A; A1 --> A2; A2 --> A3[Vektorindex im Unity Catalog]; end; subgraph Modell_B; B1 --> B2; B2 --> B3; end;
  • Option 1: Delta Sync Index mit Databricks-managed Embeddings: Der Sicherheitsarchitekt stellt eine Quell-Delta-Tabelle bereit, die ausschließlich Textdaten und Sicherheitsmetadaten enthält. Databricks berechnet die Embeddings automatisch über ein konfiguriertes Modell und synchronisiert Änderungen inkrementell. Diese Option reduziert den operativen Aufwand, da die Embedding-Pipeline vollständig verwaltet wird.
  • Option 2: Delta Sync Index mit selbstverwalteten Embeddings: Die Embeddings werden in einer externen Pipeline berechnet und in die Delta-Tabelle geschrieben. Die Synchronisation mit dem Vektorindex erfolgt über den Change Data Feed (CDF) der Delta-Tabelle, der jede Änderung (Inserts, Updates, Deletes) in Echtzeit erfasst und an den Vektor-Such-Endpunkt propagiert. Diese Option bietet maximale Flexibilität bei der Wahl des Embedding-Modells und der Vorverarbeitungslogik.

Der Zugriff auf diese Indizes wird vollständig über die granularen Access Control Lists (ACLs) des Unity Catalogs gesteuert, was eine konsistente Data-Governance-Richtlinie über Data-Lake- und KI-Anwendungen hinweg garantiert. Dieses Konzept der einheitlichen Governance ist ein zentraler Baustein für vertrauenswürdige KI-Systeme.

Query-Time: Transitive Gruppenauflösung und LDAP-Caching

Zur Laufzeit (Query-Time) muss das RAG-System die Berechtigungen des Benutzers in Millisekunden prüfen. Ein gravierendes architektonisches Problem stellen hierbei verschachtelte Gruppen (Nested Groups) im Active Directory dar. Ist ein Dokument für die Gruppe G-Executive freigegeben, und der Benutzer ist Mitglied der Gruppe G-Finance, welche wiederum Mitglied von G-Executive ist, muss der Zugriff gewährt werden.

Eine manuelle, rekursive Auflösung dieser Gruppenhierarchie über Standard-LDAP-Abfragen führt zu massiven Latenzproblemen, da für jede Ebene separate Netzwerk-Roundtrips erforderlich sind.

Serverseitige Optimierung mittels Matching-Regel

Sicherheitsarchitekten müssen daher die Microsoft AD-spezifische Erweiterungsregel LDAP_MATCHING_RULE_IN_CHAIN (OID: 1.2.840.113556.1.4.1941) erzwingen. Diese veranlasst den Active Directory-Server, die transitive Vererbungs-Kette intern aufzulösen und das vollständige Gruppen-Array in einer einzigen, hocheffizienten Abfrage zurückzuliefern.

import ldap

def resolve_nested_groups(user_dn: str, ldap_connection) -> list:
    """
    Löst alle direkten und indirekten Gruppenmitgliedschaften eines Benutzers
    unter Verwendung der LDAP_MATCHING_RULE_IN_CHAIN auf.
    """
    search_filter = f"(member:1.2.840.113556.1.4.1941:={user_dn})"
    base_dn = "OU=Groups,DC=enterprise,DC=com"
    
    # Ausführung der transitiven serverseitigen Suche
    result = ldap_connection.search_s(
        base_dn,
        ldap.SCOPE_SUBTREE,
        search_filter,
        attrlist=
    )
    
    # Extraktion und Konvertierung der GUIDs in String-Präsentationen
    group_guids = [res[1] for res in result if res and 'objectGUID' in res[1]]
    return group_guids

Cache-Infrastruktur und Puffer-Limitierungen

Um die Netzwerklatenz weiter zu reduzieren, wird das Ergebnis dieser Auflösung in einem In-Memory-Cache (z. B. Redis) mit einer Time-To-Live (TTL) von 24 Stunden zwischengespeichert.

Sicherheitsarchitekten müssen hierbei zwei technische Einschränkungen berücksichtigen:

  1. MaxPageSize-Limitierung: Standardmäßig begrenzen Active Directory-Server die Anzahl der zurückgegebenen Objekte pro Abfrage über das Attribut MaxPageSize auf 1.000. Bei Benutzern mit extrem komplexen Gruppenstrukturen (z. B. über 1.000 verschachtelten Gruppen) müssen serverseitige Anpassungen auf mindestens 1.024 vorgenommen werden, um eine unvollständige Gruppenauflösung und damit fälschlicherweise verweigerte Zugriffe zu verhindern.
  2. Cache-Invalidierung bei Entzug von Rechten: Ein kritisches Sicherheitsrisiko besteht darin, dass ein Benutzer, dem Berechtigungen entzogen wurden, bis zum Ablauf der Cache-TTL (z. B. 24 Stunden) weiterhin Zugriff auf sensible Dokumente im RAG-System hat. Es muss daher eine ereignisgesteuerte Cache-Invalidierung implementiert werden, die über AD-Änderungs-Webhooks (z. B. Microsoft Graph Change Notifications) getriggert wird und den Cache des betroffenen Benutzers bei Änderungen sofort löscht. Ohne diese Maßnahme würde das System im Falle einer sofortigen Sperrung eines kompromittierten Accounts für den kritischen Zeitraum der TTL versagen.

4. Filterungsmechanismen im Detail: Pre-Retrieval vs. Post-Retrieval

Die Frage, an welcher Stelle der RAG-Pipeline Autorisierungsprüfungen erzwungen werden, ist für die Sicherheit und Funktionalität des Gesamtsystems von fundamentaler Bedeutung. Ein falsches Filterungs-Paradigma führt unweigerlich entweder zu Datenlecks oder zu unvollständigen Suchergebnissen.

Mathematische Definition des Suchraums

Ein Vektorindex enthält eine Menge von Dokumenten-Chunks D={d1,d2,,dN}\mathcal{D} = \{d_1, d_2, \dots, d_N\} mit zugehörigen dichten Vektoren viRD\mathbf{v}_i \in \mathbb{R}^D. Bei einer Suchanfrage generiert der Benutzer einen Abfragevektor qRD\mathbf{q} \in \mathbb{R}^D. Das Ziel der semantischen Suche ist es, die Teilmenge der kk am nächsten gelegenen Vektoren bezüglich einer Distanzmetrik (z. B. der Kosinus-Ähnlichkeit) zu finden.

Simcos(q,vi)=qviq2vi2\text{Sim}_{\text{cos}}(\mathbf{q}, \mathbf{v}_i) = \frac{\mathbf{q} \cdot \mathbf{v}_i}{\|\mathbf{q}\|_2 \|\mathbf{v}_i\|_2}

Zusätzlich ist jedem Chunk eine Menge von Zugriffs-Metadaten AiA_i (z. B. erlaubte Gruppen-GUIDs) zugeordnet. Der anfragende Benutzer besitzt die Identitätsattribute UU (z. B. seine Gruppen-GUIDs). Ein Chunk ist für den Benutzer autorisiert, wenn die Schnittmenge nicht leer ist:

Auth(U,Ai)UAi\text{Auth}(U, A_i) \Longleftrightarrow U \cap A_i \neq \emptyset

Diese formale Definition ist die Grundlage für das Verständnis der beiden fundamentalen Filterungsstrategien.

Post-Retrieval-Filterung: Das funktionale Risiko des Recall-Kollapses

Bei der Post-Retrieval-Filterung führt das System eine ungesicherte Ähnlichkeitssuche über den gesamten globalen Dokumentenraum D\mathcal{D} durch. Erst im Anschluss filtert die Anwendungsschicht die Ergebnisse.

Rraw=Top-kdiD(Simcos(q,vi))\mathcal{R}_{\text{raw}} = \text{Top-k}_{d_i \in \mathcal{D}} \left( \text{Sim}_{\text{cos}}(\mathbf{q}, \mathbf{v}_i) \right)

Rfiltered={rRrawAuth(U,Ar)}\mathcal{R}_{\text{filtered}} = \{ r \in \mathcal{R}_{\text{raw}} \mid \text{Auth}(U, A_r) \}

Dieses Verfahren ist extrem unsicher und fehleranfällig:

  1. Recall-Kollaps (Overfiltering): Wenn die semantisch relevantesten Chunks zu einem gesperrten Dokument gehören, liefert die ungesicherte Suche diese Chunks zurück. Der Post-Filter verwirft sie. Übrig bleibt eine leere oder unvollständige Ergebnismenge, obwohl im autorisierten Bereich des Benutzers durchaus relevante Dokumente existiert hätten. Der Benutzer erlebt einen scheinbaren Fehlschlag der Suche, was das Vertrauen in das System untergräbt.
  2. Sicherheitsrisiko durch unvollständige Antworten: Der Benutzer erhält keine Antwort auf seine Frage, was zu Frustration führt, während gleichzeitig Informationen über die Existenz gesperrter Dokumente durch feine Latenz-Variationen (Timing-Angriffe) potenziell ausgespäht werden könnten. Die reine Existenz einer Antwortverzögerung oder eines leeren Ergebnisses kann in bestimmten Konstellationen Rückschlüsse auf die Sensitivität der indizierten Daten zulassen.

Pre-Retrieval-Filterung: Durchsetzung auf Datenbankebene

Die Pre-Retrieval-Filterung schränkt den mathematischen Suchraum ein, bevor die Ähnlichkeitsberechnung gestartet wird.

Dauthorized={diDAuth(U,Ai)}\mathcal{D}_{\text{authorized}} = \{ d_i \in \mathcal{D} \mid \text{Auth}(U, A_i) \}

Rsecure=Top-kdiDauthorized(Simcos(q,vi))\mathcal{R}_{\text{secure}} = \text{Top-k}_{d_i \in \mathcal{D}_{\text{authorized}}} \left( \text{Sim}_{\text{cos}}(\mathbf{q}, \mathbf{v}_i) \right)

Moderne Suchmaschinen wie Elasticsearch nutzen hierfür BitSets. Während der Abfragephase wird die Autorisierungsbedingung als Filter über den Index gelegt. Die passenden Dokumente werden hocheffizient als BitSet im RAM repräsentiert. Der Vektorsuch-Algorithmus (z. B. HNSW) wertet während der Graph-Traversierung nur diejenigen Knoten aus, deren Bit im BitSet auf 1 gesetzt ist. Dies garantiert, dass der Benutzer immer exakt kk autorisierte Ergebnisse erhält. Dieser Ansatz ist die einzig valide Strategie für produktive Enterprise-Umgebungen, da er die Sicherheitsbarriere auf die Ebene der Suchmaschine verlagert und nicht der Anwendungslogik überlässt.

Lexikalische Vorfilterung in modernen NoSQL-Vektorspeichern

Für anspruchsvolle Enterprise-Szenarien bieten Vektordatenbanken wie MongoDB hochentwickelte lexikalische Vorfilter (Lexical Prefilters). Diese Technologie kombiniert semantische Vektorsuchen mit klassischen Volltext-Analysatoren (z. B. Fuzzy Search, Wildcards, Phrasen-Matching und komplexe boolesche Abfragen über den queryString-Operator) innerhalb einer einzigen, atomaren Aggregations-Pipeline ($vectorSearch innerhalb des $search-Stadiums).

Aus architektonischer Sicht bietet die Integration lexikalischer Vorfilter signifikante Vorteile:

  • Ressourceneffizienz: Durch das radikale Eingrenzen der Vektorkandidaten auf Basis exakter Texttreffer (z. B. Projekt-IDs, Produktcodes oder Klassifizierungs-Tags) entfällt die rechenintensive Kosinus-Distanzberechnung für Millionen von irrelevanten Dokumenten im globalen Vektorraum.
  • Mathematische Präzision: Strukturierte Barrieren (wie "nur Dokumente aus dem Jahr 2026") werden nicht als weiche semantische Ähnlichkeiten interpretiert, sondern als harte logische Ausschlusskriterien vor der Vektorbewertung durchgesetzt. Dies verhindert das Phänomen, dass ein Dokument aus 2025 fälschlicherweise als "ähnlich genug" zu einer Anfrage nach 2026 eingestuft wird.

5. Praxis-Tutorial: Sicheres RAG mit PostgreSQL, pgvector und RLS

Im folgenden praxisnahen Tutorial wird demonstriert, wie eine durchgängige Zero-Trust-Architektur auf Basis von PostgreSQL (v15), der Erweiterung pgvector und dem nativen Feature Row-Level Security (RLS) implementiert wird.

Die Verwendung von RLS stellt sicher, dass die Sicherheitsbarrieren direkt im Datenbank-Kernel durchgesetzt werden. Selbst wenn Anwendungsentwickler vergessen, Sicherheitsfilter in ihren Python-Queries anzugeben, fängt die Datenbank die Abfrage ab und filtert unautorisierte Datensätze unsichtbar aus. Dieses deklarative Sicherheitsmodell ist ein zentraler Baustein für robuste RAG-Architekturen.

Schritt 1: Docker-Infrastruktur definieren

Zunächst wird eine isolierte Container-Umgebung erstellt. Das Dockerfile installiert die erforderlichen Compiler-Werkzeuge, klont das offizielle pgvector-Repository, kompiliert die Erweiterung und säubert das Image anschließend:

FROM postgres:15

RUN apt-get update \  
 && apt-get install -y --no-install-recommends \  
    build-essential \  
    postgresql-server-dev-15 \  
    git \  
    ca-certificates \  
 && rm -rf /var/lib/apt/lists/*  

RUN git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git \  
 && cd pgvector \  
 && make \  
 && make install \  
 && cd.. \  
 && rm -rf pgvector  

RUN apt-get remove -y build-essential postgresql-server-dev-15 git \  
 && apt-get autoremove -y

Die Orchestrierung erfolgt über die Datei docker-compose.yml, die persistente Datenbankpfade und das Initialisierungs-Verzeichnis für SQL-Skripte mountet:

version: '3.8'  
services:  
  postgres-db:  
    build:  
      context:.  
      dockerfile: Dockerfile  
    container_name: secure-pgvector-container  
    environment:  
      POSTGRES_USER: postgres  
      POSTGRES_PASSWORD: super_secure_admin_password  
      POSTGRES_DB: secure_rag_db  
    ports:  
      - "5432:5432"  
    volumes:  
      - pg_data:/var/lib/postgresql/data  
      -./init-scripts:/docker-entrypoint-initdb.d  
volumes:  
  pg_data:

Schritt 2: SQL-Schema und RLS-Richtlinien implementieren

Beim Start der Datenbank wird das folgende Skript (init-scripts/01-init.sql) ausgeführt. Es konfiguriert die Tabellen, Indizes, Datenbank-Rollen und die RLS-Richtlinien.

Um die HNSW-Graph-Problematik bei restriktiven Filtern zu lösen, nutzt pgvector 0.8.0 ein hochentwickeltes iteratives Scan-Verfahren. Wir konfigurieren das System so, dass der Index-Scan im Falle unzureichender Treffer im autorisierten Bereich dynamisch fortgesetzt wird, indem wir den Parameter hnsw.max_scan_tuples auf 20.000 setzen und die iterative Suche auf relaxed_order konfigurieren.

-- Erweiterung aktivieren
CREATE EXTENSION IF NOT EXISTS vector;

-- Tabelle für Dokumenten-Chuncks mit feinstufigen ACL-Feldern erstellen
CREATE TABLE document_chunks (
    id SERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    embedding vector(384) NOT NULL, -- Dimension abgestimmt auf all-MiniLM-L6-v2
    department VARCHAR(50) NOT NULL,
    classification VARCHAR(20) NOT NULL DEFAULT 'public',
    allowed_groups UUID NOT NULL DEFAULT '{}'::UUID -- Array von Entra ID Gruppen-GUIDs
);

-- Indizierung zur Beschleunigung der Vektorsuche unter Verwendung von Kosinus-Operationen
CREATE INDEX idx_document_chunks_hnsw ON document_chunks 
USING hnsw (embedding vector_cosine_ops);

-- GIN-Index zur Beschleunigung der Array-Schnittmengenprüfung bei Gruppen-GUIDs
CREATE INDEX idx_document_chunks_allowed_groups ON document_chunks USING gin (allowed_groups);

-- Erstellung der unprivilegierten Datenbankrolle für die RAG-Anwendung
CREATE USER rag_application_user WITH PASSWORD 'application_session_password_99';
GRANT SELECT ON document_chunks TO rag_application_user;

-- Aktivierung von Row-Level Security auf der Tabelle
ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;

-- Erzwingen der Richtlinie auch für administrative Datenbankbenutzer (Schutz vor internen Leaks)
ALTER TABLE document_chunks FORCE ROW LEVEL SECURITY;

-- Erstellung der RLS-Policy
-- Die Funktion current_setting() liest eine benutzerdefinierte Session-Variable aus (GUC).
-- Die Variable 'app.current_user_groups' wird von der Python-Middleware vor jeder Abfrage gesetzt.
CREATE POLICY secure_retrieval_policy ON document_chunks
FOR SELECT
TO rag_application_user
USING (
    -- Regel 1: Öffentliche Dokumente sind für jeden sichtbar
    classification = 'public'
    OR
    -- Regel 2: Schnittmengenprüfung zwischen den erlaubten Gruppen des Chunks 
    -- und den Gruppen-GUIDs des aktuellen Benutzers.
    allowed_groups && COALESCE(
        NULLIF(current_setting('app.current_user_groups', true), '')::uuid, 
        '{}'::uuid
    )
);

Schritt 3: Der sichere Python-RAG-Retriever mit Audit-Logging

Die folgende Python-Middleware stellt eine sichere Verbindung her, konfiguriert die Session-Parameter innerhalb einer atomaren Transaktion, führt die semantische Suche aus und protokolliert jeden Zugriff manipulationssicher in einer separaten Audit-Tabelle.

import os
import uuid
import datetime
import logging
import psycopg2
from psycopg2.extras import RealDictCursor
from sentence_transformers import SentenceTransformer

# Konfiguration des lokalen Logging-Systems
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("SecureRAG")

# Initialisierung des lokalen Embedding-Modells (all-MiniLM-L6-v2 generiert 384-dimensionale dichte Vektoren)
embedding_model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

def get_database_connection():
    """Stellt eine Verbindung zur PostgreSQL-Instanz als unpriviligierter Anwendungsbenutzer her."""
    return psycopg2.connect(
        host="localhost",
        database="secure_rag_db",
        user="rag_application_user",
        password="application_session_password_99",
        port=5432
    )

def audit_log_attempt(user_id: str, query_text: str, returned_chunk_ids: list, status: str):
    """Schreibt einen manipulationssicheren Audit-Eintrag in das Systemprotokoll."""
    # In einer produktiven Umgebung erfolgt dies über eine separate, schreibgeschützte Verbindung
    # oder einen asynchronen Message-Broker an ein SIEM-System.
    timestamp = datetime.datetime.utcnow().isoformat()
    # Maskierung des Prompts zum Schutz personenbezogener Daten im Log
    sanitized_prompt = query_text[:50] + "..." if len(query_text) > 50 else query_text
    logger.info(
        f" Time: {timestamp} | User: {user_id} | Prompt: '{sanitized_prompt}' "
        f"| Accessed Chunks: {returned_chunk_ids} | Status: {status}"
    )

def inject_demo_data():
    """Befüllt die Tabelle als Administrator mit strukturierten Testdaten."""
    conn = psycopg2.connect(
        host="localhost", database="secure_rag_db", user="postgres", password="super_secure_admin_password", port=5432
    )
    
    # Definition fiktiver Entra ID Gruppen-GUIDs für unterschiedliche Abteilungen
    FINANCE_DEPT_GROUP = "e8b2cd1a-9fbc-4927-ba21-12569fa12034"
    HR_DEPT_GROUP      = "a417fd92-3c82-410a-bd91-9981a2f30129"
    
    test_documents =
        ),
        (
            "Finanzplanung Q4: Die Akquisitionsverhandlungen für Projekt Taurus erfordern ein Budget von 12 Mio. Euro.",
            "finance", "restricted",
        ),
        (
            "Besucherregelung: Externe Dienstleister müssen sich am Empfang registrieren und einen Ausweis tragen.",
            "facility", "public",
        )
    ]
    
    with conn.cursor() as cur:
        cur.execute("TRUNCATE TABLE document_chunks;")
        for content, dept, classif, groups in test_documents:
            vector = embedding_model.encode(content).tolist()
            cur.execute(
                """INSERT INTO document_chunks (content, embedding, department, classification, allowed_groups)
                   VALUES (%s, %s, %s, %s, %s::UUID);""",
                (content, vector, dept, classif, groups)
            )
    conn.commit()
    conn.close()
    logger.info("Demo-Daten erfolgreich indiziert.")

def execute_secure_rag_retrieval(user_id: str, query_text: str, user_group_guids: list, top_k: int = 3) -> list:
    """
    Führt ein sicheres Pre-Retrieval-Verfahren durch. 
    Die RLS-Richtlinien filtern nicht autorisierte Datenpunkte auf Kernel-Ebene aus.
    """
    # 1. Generierung des dichten Vektors
    query_vector = embedding_model.encode(query_text).tolist()
    
    # 2. Formatierung der Benutzergruppen in ein gültiges PostgreSQL-Array-Literal
    pg_array_literal = "{" + ",".join(f'"{g}"' for g in user_group_guids) + "}"
    
    conn = get_database_connection()
    authorized_results =
    
    try:
        with conn.cursor(cursor_factory=RealDictCursor) as cur:
            # Transaktionsblock starten
            cur.execute("BEGIN;")
            
            # Sicherheitskritischer Schritt: Session-GUC für diese Transaktion setzen
            cur.execute("SET LOCAL app.current_user_groups = %s;", (pg_array_literal,))
            
            # Konfiguration der iterativen pgvector 0.8.0 HNSW-Suche zur Vermeidung des Recall-Kollapses
            cur.execute("SET LOCAL hnsw.max_scan_tuples = 20000;")
            
            # Ausführung der Ähnlichkeitssuche
            # Die RLS-Policy filtert automatisch und unsichtbar alle Datensätze heraus,
            # für die die Berechtigungsprüfung fehlschlägt, noch bevor das Sortieren stattfindet.
            sql_query = """
                SELECT id, content, department, classification, 1 - (embedding <=> %s) AS similarity
                FROM document_chunks
                ORDER BY embedding <=> %s
                LIMIT %s;
            """
            cur.execute(sql_query, (query_vector, query_vector, top_k))
            authorized_results = cur.fetchall()
            
            # Transaktion abschließen (löscht die lokalen GUC-Variablen automatisch)
            cur.execute("COMMIT;")
            
            status = "SUCCESS"
    except Exception as e:
        conn.rollback()
        status = f"FAILED: {str(e)}"
        logger.error(f"Sicherheitsverletzung oder Datenbankfehler: {e}")
    finally:
        conn.close()
    
    # Extraktion der IDs für das Audit Log
    accessed_ids = [row['id'] for row in authorized_results]
    audit_log_attempt(user_id, query_text, accessed_ids, status)
    
    return authorized_results

# ==========================================
# Testausführung und Validierung
# ==========================================
if __name__ == "__main__":
    # Demo-Daten initialisieren
    inject_demo_data()
    
    FINANCE_GUID = "e8b2cd1a-9fbc-4927-ba21-12569fa12034"
    HR_GUID      = "a417fd92-3c82-410a-bd91-9981a2f30129"
    
    # SZE-1: Ein Benutzer aus der Finanzabteilung stellt eine abteilungsübergreifende Anfrage
    logger.info("Szenario 1: Finanzanalyst sucht nach Gehalts- und Budgetdaten...")
    results_finance = execute_secure_rag_retrieval(
        user_id="usr_finance_01",
        query_text="Wie sehen die Budgets und Geschäftsführer-Gehälter aus?",
        user_group_guids=
    )
    for row in results_finance:
        print(f"-> Gelesen: [{row['classification'].upper()}] {row['content']} (Sim: {row['similarity']:.4f})")
        
    # SZE-2: Ein Benutzer aus der HR-Abteilung stellt dieselbe Anfrage
    logger.info("Szenario 2: HR-Spezialist sucht nach Gehalts- und Budgetdaten...")
    results_hr = execute_secure_rag_retrieval(
        user_id="usr_hr_02",
        query_text="Wie sehen die Budgets und Geschäftsführer-Gehälter aus?",
        user_group_guids=
    )
    for row in results_hr:
        print(f"-> Gelesen: [{row['classification'].upper()}] {row['content']} (Sim: {row['similarity']:.4f})")

6. Zentralisierte Policy-Governance mit Open Policy Agent (OPA)

In komplexen Enterprise-Architekturen ist die Verwaltung von Berechtigungslogiken innerhalb einzelner Applikationen oder dedizierter SQL-Tabellen oft unpraktikabel. Änderungen an Compliance-Vorgaben müssten mühsam an Dutzenden Stellen nachgezogen werden. Hier etabliert sich die entkoppelte, zentralisierte Richtlinien-Verwaltung über den Open Policy Agent (OPA) als Industriestandard.

OPA fungiert als herstellerunabhängige Policy-Engine, die Autorisierungs-Entscheidungen über eine deklarative Abfragesprache namens Rego trifft. Das RAG-System agiert als Policy Enforcement Point (PEP), der vor jeder Suchanfrage den Benutzerkontext an den OPA-Service (den Policy Decision Point, PDP) übermittelt. Diese Architektur ist ein Paradebeispiel für die modularen, produktionsbereiten Systeme, die wir in unserem Artikel zum BeeAI Framework diskutieren.

flowchart LR; A -->|1. Kontext-JSON| B; B -->|2. Filter-Kriterien| A;

Eine beispielhafte OPA-Rego-Richtlinie zur dynamischen ABAC-Generierung für die Ingestion- und Query-Pipeline gestaltet sich wie folgt:

package rag.security

default allow = false

# Regel 1: Genereller Zugriff wird nur gewährt, wenn der Benutzer authentifiziert ist
allow {
    input.user.authenticated == true
}

# Regel 2: Ermittlung der Metadaten-Filter, die an die Vektordatenbank übergeben werden müssen
metadata_filters[filter] {
    # Öffentliche Dokumente sind immer zulässig
    filter := {"classification": "public"}
}

metadata_filters[filter] {
    # Wenn der Benutzer die Rolle 'executive' besitzt, darf er alles sehen
    input.user.roles[_] == "executive"
    filter := {"classification": "all"}
}

metadata_filters[filter] {
    # Dynamischer Filter basierend auf der Abteilungszugehörigkeit des Benutzers
    some dept
    input.user.departments[dept]
    filter := {"department": dept}
}

Die RAG-Anwendung fragt vor der Vektorsuche die erlaubten Metadaten-Filter über die OPA-REST-API ab und baut daraus dynamisch die Pre-Retrieval-Filter-Struktur für den Vektorspeicher zusammen. Dies entkoppelt die Sicherheits-Governance vollständig vom Quellcode der Anwendung und ermöglicht zentralisierte, auditierbare Berechtigungsänderungen im laufenden Betrieb. Änderungen an Sicherheitsrichtlinien können so ohne Redeployment der RAG-Applikation durchgeführt werden.

7. Fazit und strategische Empfehlungen

Die Absicherung von RAG-Systemen gegen abteilungsübergreifende Datenlecks ist eine vielschichtige Herausforderung, die eine präzise Abstimmung zwischen Identitätsmanagement, Ingestion-Pipelines und Datenbankarchitekturen erfordert. Behelfsmäßige Behandlungen nach dem Abruf (Post-Retrieval-Filterung) stellen aufgrund des mathematischen Risikos des Recall-Kollapses und der potenziellen Anfälligkeit für Timing-Angriffe ein unkalkulierbares Risiko dar und sind in produktiven Enterprise-Szenarien strikt zu vermeiden.

Sicherheitsarchitekten sollten bei der Konzeption sicherer RAG-Systeme die folgenden strategischen Prinzipien etablieren:

  • Sicherheit an der Datenquelle verankern: Berechtigungen müssen während des Imports direkt am Datenpunkt materialisiert werden. Nutzen Sie stabile Identifikatoren (GUIDs) statt volatiler Attribute wie E-Mail-Adressen, um eine schleichende Aufweichung der Zugriffskontrollen zu verhindern.
  • Pre-Retrieval-Filterung priorisieren: Die Einschränkung des Suchraums muss zwingend auf Datenbankebene vor der semantischen Berechnung stattfinden. Datenbankfeatures wie Row-Level Security in PostgreSQL bieten hierbei eine transparente und deklarative Absicherung, die unautorisierte Abfragen auf Kernel-Ebene blockiert.
  • Latenzen durch serverseitige AD-Regeln minimieren: Um Latenzeinbußen bei der transitiven Gruppenauflösung zu vermeiden, sollten dedizierte LDAP-Matching-Regeln wie LDAP_MATCHING_RULE_IN_CHAIN in Kombination mit einer Event-gesteuerten Cache-Invalidierung implementiert werden.
  • Zentralisierte Policy-Engines evaluieren: Bei komplexen Systemlandschaften minimiert die Entkopplung der Autorisierungslogik via Open Policy Agent das Fehlerrisiko und erhöht die Auditierbarkeit für externe Auditoren signifikant.

Durch die konsequente Umsetzung dieser Zero-Trust-Architektur auf Dokumentenebene wird sichergestellt, dass LLM-Agenten ihr volles Potenzial als unternehmensweite Wissens-Katalysatoren entfalten können, ohne die Vertraulichkeit und Integrität sensibler Geschäftsdaten zu gefährden.