Permissions et confidentialité dans le RAG
Comment garantir qu'un utilisateur ne récupère jamais un passage d'un dossier auquel il n'a pas accès : ACL par chunk, filtre côté serveur, double barrière application + moteur, audit et tests.
L'essentiel
- La confidentialité est une propriété de l'architecture, pas un réglage : le filtre d'accès s'applique avant ou pendant la recherche vectorielle, jamais seulement après, et jamais dans un prompt.
- Chaque chunk porte ses métadonnées d'accès (
dossier_id, groupes autorisés) copiées depuis la source ; l'identité de l'utilisateur vient du SSO, jamais d'un paramètre client. - Double barrière : l'application filtre, et le moteur (JWT Qdrant ou RLS PostgreSQL) refuse de lui-même les points hors périmètre. Les quatre CVE Open WebUI de 2026 montrent pourquoi une seule couche ne suffit pas 2.
- Tout accès à un chunk est journalisé ; la révocation d'un droit déclenche une resynchronisation ; un test automatisé « l'avocat B ne voit jamais A » tourne à chaque mise à jour.
- Ce modèle est celui des acteurs établis (Azure AI Search, AWS, Glean, Pinecone) et de l'OWASP LLM08:2025. C'est aussi la décision ADR-003.
Le problème à résoudre
Le cas de référence est un cabinet de 5 à 10 personnes. Les associés voient tous les dossiers. L'avocat A travaille sur les dossiers A, B et C ; l'avocat B sur D et E ; le secrétariat n'accède qu'à l'administratif. Le secret professionnel et les conflits d'intérêts imposent que l'avocat B ne puisse jamais, même par une question habile, obtenir un passage, un nom, une date ou un montant issus des dossiers A, B ou C.
Un RAG naïf indexe tout dans une collection, cherche les passages les plus proches de la question, et laisse le modèle répondre. Dans ce schéma, le seul rempart est le prompt système (« ne révèle que… »), ce qui n'est pas un contrôle d'accès : le passage interdit est déjà dans le contexte du modèle, et une injection de prompt ou une simple reformulation le fait sortir. Un rempart applicatif seul (l'interface vérifie les droits avant d'appeler la base) est mieux, mais l'histoire récente montre qu'il se contourne.
Modèle de permissions cible
Le modèle combine RBAC (rôles) et ACL par dossier.
| Niveau | Objet | Exemple | Source de vérité |
|---|---|---|---|
| Rôle | ce qu'un utilisateur peut faire | associe, avocat, secretariat, admin-technique | groupes de l'IdP (Entra ID, Authentik, Keycloak) |
| ACL dossier | qui peut lire un dossier | dossier A : associés + avocat A | ACL du stockage source (SMB, SharePoint, Drive) ou table de la Box |
| Portée de recherche | les dossiers filtrables pour une requête | avocat A : A, B, C | calculée côté serveur à chaque requête |
L'unité d'isolation est le dossier, pas l'utilisateur. Pinecone déconseille explicitement de filtrer sur « large lists of individual user IDs » 11 ; à l'inverse, un cabinet compte quelques dizaines à quelques centaines de dossiers, et un utilisateur en voit rarement plus de quelques dizaines : la liste dossier_id IN (…) reste courte (estimation).
Le rôle admin-technique (la personne qui maintient la Box) est un cas à part : il doit pouvoir administrer sans lire le contenu. Ce point relève de la sécurité et du RGPD, mais l'architecture RAG doit le permettre : aucun compte de service ne doit interroger la base sans filtre en production.
Propagation des ACL depuis la source
Le pipeline d'ingestion lit, pour chaque document, ses métadonnées d'accès dans le système source et les copie sur chaque chunk. Azure AI Search formule la même exigence : quand un skillset découpe un document, « the permission metadata fields move from indexer field mappings to index projections », c'est-à-dire au niveau du fragment 8.
| Source | Ce qu'on peut lire | Limites |
|---|---|---|
| Partage SMB / NAS | arborescence (un dossier client = un répertoire), ACL NTFS via cifsacl 17 | le mapping SID → groupe exige un annuaire commun ; en pratique l'ACL Box est souvent dérivée du chemin |
| SharePoint / OneDrive | GET /drives/…/items/…/permissions via Graph, héritage (inheritedFrom) 16 | nécessite Files.Read.All ou Sites.Read.All avec consentement admin ; liens de partage complexes à aplatir |
| Google Drive | ressource permissions, rôles et types (user, group, domain, anyone) | le type anyone doit lever une alerte |
| Import manuel | l'utilisateur choisit le dossier cible dans la Box | l'ACL est celle du dossier Box |
Recommandation initiale Hypothèse : le dossier est la clé. La Box maintient une table dossier(id, nom, client, groupes_lecture) alimentée soit par convention de nommage du partage SMB, soit par les ACL Graph. Chaque chunk reçoit dossier_id et, par dénormalisation, acl_groups (liste des groupes IdP autorisés) pour éviter une jointure à chaque requête. Les changements d'ACL côté source ne sont visibles qu'après resynchronisation : « Permission changes in the source system … are only reflected in search results after that metadata is synchronized to the index » 8. Ce délai doit être mesuré et affiché.
Flux d'une requête avec filtre
Trois propriétés à retenir :
- La portée est calculée côté serveur à partir du jeton SSO, à chaque requête. Aucun paramètre de la requête HTTP (identifiant de collection, de knowledge base, de fichier) n'entre dans le filtre. C'est le pattern décrit par Azure (« At query time, get the identity of the caller. Pass in the identity of the caller as the filter string ») 8 et AWS (« Metadata filtering … enables access control … based on attributes such as user roles, departments ») 9.
- Le filtre est dans la requête de récupération, pas après. Azure justifie aussi cela par la performance : « Filtering inside the search pipeline is faster than loading larger result sets into your application and trimming there ».
- Le moteur impose lui-même le filtre, indépendamment du code applicatif (section suivante).
Double barrière : application + moteur
Barrière 1 : l'application
L'API de la Box (ou l'interface, si l'on utilise son RAG intégré) vérifie les droits et construit le filtre. C'est nécessaire mais insuffisant : Open WebUI applique ce contrôle « côté application » et a publié en 2026 quatre advisories où il était contourné, dont une où « Three of the five code paths perform vector store queries without any authorization check » et où « Access revocation is ineffective for RAG content » 1.
Barrière 2 : le moteur
Deux mécanismes candidats, un par base étudiée dans Q-004 :
Qdrant : JWT avec claim payload. Le serveur accepte des jetons JWT portant access (r, m, rw), une restriction par collections et un claim payload (par exemple "payload": {"dossier_id": [...]} dans la logique de la documentation) qu'il applique comme filtre obligatoire sur les points renvoyés 4. L'application génère un jeton par requête (ou par session) contenant la liste des dossiers autorisés ; même si son code est bugué, Qdrant ne renvoie pas un point d'un autre dossier. La documentation multitenancy recommande une collection unique partitionnée par payload, avec un index is_tenant: true sur le champ de partition 5.
PostgreSQL + pgvector : Row-Level Security. Une politique CREATE POLICY … USING (dossier_id IN (SELECT dossier_id FROM acl WHERE user_id = current_setting('app.user_id'))) filtre chaque ligne ; l'application se connecte avec un rôle non superuser et pose SET LOCAL app.user_id par transaction (pattern usuel, estimation). Trois avertissements officiels s'appliquent 6 :
- les superusers et rôles
BYPASSRLScontournent toujours RLS : le rôle applicatif ne doit être ni l'un ni l'autre, etFORCE ROW LEVEL SECURITYprotège aussi contre le propriétaire de la table ; - les fonctions non
LEAKPROOFpeuvent être évaluées avant la politique par l'optimiseur : l'opérateur de distance vectorielle et toute fonction custom sont à examiner (canal caché par erreur ou par ordonnancement : estimation à vérifier) ; - les sauvegardes doivent être lancées avec
row_security = offpour échouer plutôt qu'omettre silencieusement des lignes.
Interaction avec HNSW : le filtre RLS est un filtre WHERE, donc soumis au post-filtrage documenté par pgvector (« If a condition matches 10% of rows, with HNSW and the default hnsw.ef_search of 40, only 4 rows will match on average ») ; activer hnsw.iterative_scan (0.8.0) ou utiliser des index partiels par dossier 7. Ce n'est pas une fuite mais une perte de rappel qui, elle, se mesure.
Les autres moteurs offrent des équivalents : document-level security d'OpenSearch (incluse) 15 ou d'Elasticsearch (niveau Platinum) 14. Voir base vectorielle.
Isolation logique et multi-tenant
Trois écoles : une collection par tenant (Pinecone : « namespace-per-tenant … provides physical isolation ») 11, un shard par tenant (Weaviate), ou une collection unique partitionnée par payload (Qdrant) 5. Pour la Box, le multi-tenant a deux sens :
- Entre dossiers d'un même cabinet : collection unique, partition par
dossier_id, filtre imposé par le moteur. Une collection par dossier multiplierait les index HNSW et compliquerait les questions transverses des associés. - Entre clients de la Box (plusieurs cabinets sur une infrastructure mutualisée, si ce scénario apparaît) : instance ou base séparée par client, jamais une simple métadonnée. Ce scénario est hors périmètre de la Box « un serveur par cabinet » mais doit être tranché avant toute offre hébergée.
Contrôle d'accès aux embeddings
Les embeddings ne sont pas anonymes : l'OWASP LLM08:2025 liste l'inversion d'embeddings parmi les risques et demande des « permission-aware vector and embedding stores » et un « strict logical and access partitioning of datasets in the vector database » 12. Conséquences : la base vectorielle est traitée comme une copie des documents (mêmes règles de sauvegarde, de chiffrement, de purge), l'accès direct à son API est réservé au service RAG, et les vecteurs d'un document supprimé sont supprimés (voir ingestion).
Sécurité de la base vectorielle
- Réseau : la base écoute uniquement sur l'interface interne Docker ou
localhost; aucun port exposé sur le LAN ; l'interface web n'y accède que via l'API de la Box. - Authentification : clé API ou JWT (Qdrant), rôle dédié non superuser avec mot de passe fort (PostgreSQL) ; la clé « maître » n'est jamais dans l'image ni dans le dépôt.
- Chiffrement : TLS entre services si plusieurs hôtes ; chiffrement au repos du volume (LUKS) comme pour les documents, voir sécurité.
- Sauvegardes : les snapshots contiennent les vecteurs et les métadonnées d'accès ; ils suivent la stratégie sauvegardes et, pour PostgreSQL,
row_security = off.
Révocation et resynchronisation
Retirer l'avocat B d'un dossier doit produire trois effets, dans l'ordre : mise à jour de la table d'ACL de la Box (immédiate), invalidation des jetons JWT ou du cache de portée (immédiate à la requête suivante), et purge de tout cache de réponses ou de résultats de recherche. La CVE-2026-44560 rappelle que le maillon faible est souvent l'après-révocation 1. Les conversations passées contenant des extraits restent visibles par l'utilisateur qui les a menées : c'est acceptable (il y avait droit à l'époque) mais doit être documenté dans la politique du cabinet.
La resynchronisation depuis la source (SMB, Graph) est planifiée (par exemple toutes les 15 minutes, valeur à fixer) et déclenchable à la main ; le délai maximal observé est une métrique de monitoring.
Audit des accès
Chaque requête RAG écrit une ligne : horodatage, identifiant utilisateur, dossiers dans la portée, identifiants des chunks renvoyés au modèle, identifiants des chunks cités, modèle utilisé. Ce journal sert à trois choses : répondre à un client (« qui a consulté mon dossier ? »), détecter une anomalie (un secrétaire dont la portée contient soudain un dossier client), et fournir la preuve d'absence de fuite après un incident. Il contient des données personnelles et suit lui-même une durée de conservation (RGPD). Open WebUI offre un « Preview Access » qui résout les grants effectifs d'un utilisateur 3 : utile en audit préventif, mais ce n'est pas un journal.
Tests de non-régression
Le test de référence est décrit dans l'expérimentation « test de fuite de permissions ». Principe : un corpus fictif de dossiers A à E avec des « canaris » (noms, montants, phrases uniques) ; pour chaque utilisateur de test, une batterie de questions ciblant les canaris interdits ; le test échoue si un canari interdit apparaît dans les chunks récupérés ou dans la réponse. Il tourne à chaque mise à jour de l'interface, de la base vectorielle ou du pipeline, et dans les deux modes (barrière applicative désactivée artificiellement pour vérifier que le moteur tient seul).
Ce que font les acteurs établis
| Acteur | Mécanisme | Ce qu'on en retient |
|---|---|---|
| Azure AI Search 8 | champ ACL dans l'index, filtre construit à partir de l'identité de l'appelant, ACL SharePoint natives évaluées à la requête | filtre dans le moteur, ACL sur chaque chunk, délai de propagation assumé |
| AWS Bedrock 9 | filtre de métadonnées + Verified Permissions : « two-layer, defense-in-depth authorization » | double barrière |
| Glean 10 | « evaluates the signed-in user's identity against mirrored permissions before returning any results » | permissions miroir, évaluation avant tout résultat |
| Pinecone 11 | namespace par tenant | isolation par dossier plutôt que par utilisateur |
| Onyx 13 | mode SYNC (mirroring des ACL source), réservé à l'Enterprise Edition | la fonction a de la valeur : elle est payante chez les éditeurs |
| OWASP LLM08:2025 12 | « fine-grained access controls and permission-aware vector and embedding stores » | référence pour le threat model et l'AIPD |
Anti-patterns à proscrire
- Le prompt système comme contrôle d'accès. Voir plus haut : le contenu est déjà dans le contexte.
- Le post-filtrage. Fuite par canal latéral (compteurs, scores, extraits partiels) et perte de rappel avec HNSW.
- La collection unique sans métadonnées d'accès, ou avec des métadonnées non indexées.
- L'identifiant de portée venant du client (
knowledge_id,collection_name,file_id) : BOLA classique, cf. CVE-2026-54016 et CVE-2026-45402 2. - Les modes « full context » ou « bypass retrieval » ouverts aux non-admins : ils chargent des documents entiers hors pipeline de filtrage.
- Un compte de service tout-puissant (superuser PostgreSQL, clé API Qdrant en lecture-écriture globale) utilisé par l'application en production.
- Une seule couche. L'application seule est faillible ; le moteur seul ne connaît pas l'identité SSO. Il faut les deux.
Questions ouvertes
Ce qu'il reste à tester
- Brancher le RAG de la Box sur Open WebUI (pipeline, endpoint, outil)Un endpoint OpenAI-compatible exposé par le service RAG de la Box, appelé par Open WebUI comme un modèle, transmet l'identité de l'utilisateur de façon vérifiable et affiche les citations (dossier, pièce, page, lien) sans dépendre du RAG intégré ni de ses knowledge bases.À tester
- pgvector vs Qdrant : rappel et latence de la recherche filtrée par ACLAvec hnsw.iterative_scan activé, pgvector conserve au moins 90 % du rappel non filtré pour un utilisateur ayant accès à 10 % des dossiers, à une latence inférieure à 200 ms ; Qdrant y parvient sans réglage grâce au filtrage intégré à HNSW. Dans les deux cas, la barrière moteur (RLS, JWT payload) ne renvoie aucun point hors portée.À tester
- Test de fuite de permissions du RAG (« l'avocat B ne voit jamais A »)Avec un filtre dossier_id calculé côté serveur et imposé par le moteur (RLS PostgreSQL ou JWT Qdrant), aucun chunk ni aucune réponse ne contient un canari hors de la portée de l'utilisateur, même lorsque la barrière applicative est désactivée.À tester
Sources
- 1Open WebUI — Advisory GHSA-h36f-rqpx-j5wx / CVE-2026-44560 (accès non autorisé à des knowledge bases via la recherche vectorielle)GitHubFiabilité hauteOpen WebUI (GitHub Security Advisories) · publié 2026-05-05 · consulté le 16 sept. 2026 · fiche source
- 2Open WebUI — advisories 2026 (CVE-2026-44560, 54016, 45402, 70488)Article techniqueFiabilité moyenneGitLab Advisory DB / OpenCVE / Kodem / GitHub · publié 05/05/2026 à 2026 · consulté le 16 sept. 2026 · fiche source
- 3Open WebUI — permissions et groupes (RBAC)Documentation officielleFiabilité hauteOpen WebUI · publié lu le 16/09/2026 · consulté le 16 sept. 2026 · fiche source
- 4Qdrant — Security (API key, TLS, JWT avec claim payload)Documentation officielleFiabilité hauteQdrant · publié lu le 16/09/2026 ; RBAC JWT introduit en 1.9.0 (blog du 24/04/2024) · consulté le 16 sept. 2026 · fiche source
- 5Qdrant — Multitenancy (payload partitioning, is_tenant)Documentation officielleFiabilité hauteQdrant · publié lu le 16/09/2026 · consulté le 16 sept. 2026 · fiche source
- 6PostgreSQL — Row Security Policies (RLS)Documentation officielleFiabilité hautePostgreSQL · publié documentation courante · consulté le 16 sept. 2026 · fiche source
- 7pgvector — README (filtrage, HNSW, iterative scan 0.8.0)GitHubFiabilité hautepgvector · publié v0.8.6 (README) ; 0.8.0 annoncé sur postgresql.org · consulté le 16 sept. 2026 · fiche source
- 8Azure AI Search — Document-Level Access ControlDocumentation officielleFiabilité hauteMicrosoft · publié 08/08/2026 (mise à jour 31/08/2026) · consulté le 16 sept. 2026 · fiche source
- 9AWS — contrôle d'accès RAG par filtrage de métadonnées et Verified PermissionsConstructeur / éditeurFiabilité moyenneAWS · publié 2024-2025 · consulté le 16 sept. 2026 · fiche source
- 10Glean — principes de sécurité (permissions miroir)Documentation officielleFiabilité moyenneGlean · publié lu le 16/09/2026 · consulté le 16 sept. 2026 · fiche source
- 11Pinecone — Implement multitenancy (namespace par tenant)Documentation officielleFiabilité moyennePinecone · publié lu le 16/09/2026 · consulté le 16 sept. 2026 · fiche source
- 12OWASP Top 10 for LLM Applications 2025 — LLM08 Vector and Embedding WeaknessesDocumentation officielleFiabilité hauteOWASP GenAI Security Project · publié mars 2025 · consulté le 16 sept. 2026 · fiche source
- 13Onyx — Access Controls et Enterprise EditionDocumentation officielleFiabilité hauteOnyx · publié lu le 16/09/2026 · consulté le 16 sept. 2026 · fiche source
- 14Elasticsearch — Document Level Security et hybrideDocumentation officielleFiabilité hauteElastic (et Instaclustr pour le niveau de licence) · publié lu le 16/09/2026 · consulté le 16 sept. 2026 · fiche source
- 15OpenSearch — recherche hybride et sécuritéDocumentation officielleFiabilité hauteOpenSearch · publié lu le 16/09/2026 · consulté le 16 sept. 2026 · fiche source
- 16Microsoft Graph — driveItem permissions (SharePoint / OneDrive)Documentation officielleFiabilité hauteMicrosoft · publié mise à jour 23/07/2025 · consulté le 16 sept. 2026 · fiche source
- 17Samba wiki — client CIFS/SMB3 du noyau LinuxDocumentation officielleFiabilité hauteSamba · publié lu le 16/09/2026 · consulté le 16 sept. 2026 · fiche source