Eine Abfrage misst Ähnlichkeit und prüft Zugriff — gleichzeitig
Es gibt keinen Zwischenschritt, in dem gesperrter Inhalt im
Arbeitsspeicher der Anwendung liegt und erst danach weggefiltert wird. Was der
Nutzer nicht sehen darf, kommt nicht aus der Datenbank heraus.
-- Vektorvergleich und Rechteprüfung in einer einzigen Abfrage
SELECT c.id, d.title, c.content,
(c.embedding <=> $query::vector) AS distance
FROM document_chunks c
JOIN documents d ON d.id = c.document_id
WHERE kg_visible($user_id, c.department_id, c.min_required_role, c.document_id)
ORDER BY c.embedding <=> $query::vector
LIMIT 5;
Die hervorgehobene Zeile ist der Unterschied zwischen einem
Firmen-Chatbot und einem Datenschutzvorfall.
Zwei Ebenen, absichtlich doppelt
Dieselbe Prüfung liegt ein zweites Mal als Zeilenrecht
(Row Level Security) in PostgreSQL. Fällt die obere Zeile durch einen
Programmierfehler weg, gibt die Datenbank trotzdem keine gesperrte Zeile heraus.
ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;
CREATE POLICY chunks_read ON document_chunks FOR SELECT
USING (kg_visible(kg_current_user_id(), department_id,
min_required_role, document_id));
Der Dienst verbindet sich mit einer eigenen Datenbankrolle, die
weder Tabelleneigentümerin ist noch Zeilenrechte umgehen darf. Was sie nicht darf,
kann auch ein Fehler in ihr nicht anrichten.
149Tests, alle bestanden
97 davon laufen gegen eine echte PostgreSQL-Datenbank: sieben Angriffsfragen mal
fünf Nutzer, rohe Datenbankabfragen ohne jeden Filter der Anwendung, der komplette
Freigabeweg samt Ablauf der Befristung. Dazu 52 Tests ohne Datenbank.
1Befehl zum Starten
docker compose up — Datenbank, Dienst und Oberfläche. Ohne Schlüssel
für ein Sprachmodell antwortet das System mit den freigegebenen Textstellen; die
Rechteprüfung ist davon unberührt.
0Framework dazwischen
Kein LangChain, kein Abfrage-Baukasten. Die beiden Zeilen, auf die es ankommt —
Sicherheitsfilter und Vektorvergleich — stehen im Klartext und sind prüfbar.