Au-delà des garde-fous : utiliser des couches sémantiques pour l’autorisation des données IA

Louis-Philippe Perron

La plupart des tutoriels sur l’accès d’un agent LLM à une base de données pour des applications comme « discuter avec vos données » abordent la sécurité comme un problème d’ingénierie de prompt. Ils ajoutent des instructions système demandant au modèle de ne pas interroger en dehors du locataire actuel, filtrent les sorties pour les données sensibles et se défendent contre l’injection de prompt. Toutes ces pratiques sont pertinentes, mais elles présentent une limitation fondamentale : elles comptent sur le LLM pour faire respecter les règles.
Dans un projet récent de création d’un agent conversationnel sur des données de pratiques de santé, nous avons adopté une approche différente. Plutôt que d’espérer que le modèle respecte les limites d’accès, nous avons placé une couche sémantique entre l’agent et la base de données qui applique l’isolation des locataires au niveau de l’API. Le LLM n’accède jamais directement à la base de données. Les jetons utilisateurs traversent tout le flux agentique, et l’autorisation est vérifiée avant chaque exécution de requête. Résultat : le modèle n’a pas besoin de « bien se comporter », car il ne peut structurellement pas accéder à ce qu’il ne devrait pas voir.
Le problème des garde-fous par prompt
Le schéma classique « parler à vos données » est simple : l’utilisateur pose une question, le LLM la traduit en SQL, la requête s’exécute, les résultats sont renvoyés. Pour gérer le contrôle d’accès multi-locataires, on ajoute des instructions au prompt comme « interroger uniquement les tables du locataire X » ou on traite a posteriori la sortie pour détecter tout résultat indésirable. Le problème, c’est que ce sont des contraintes souples. Le LLM génère toujours du SQL brut avec un accès complet à la base de données. Si une injection de prompt manipule les instructions du modèle, si le modèle interprète mal les règles de portée, ou si un cas particulier du schéma provoque une jointure inattendue, les mauvaises données peuvent apparaître. Dans un contexte santé avec des dossiers personnels et financiers sensibles, « ça marche la plupart du temps » n’est pas une posture de sécurité. C’est la différence entre dire « merci de ne pas ouvrir cette porte » et ne pas donner la clé.
Ce que change une couche sémantique
Une couche sémantique se place au-dessus de la base de données et expose une API gouvernée au lieu d’un accès brut aux tables. Elle définit un modèle de données soigné comprenant dimensions, mesures, relations, et n’autorise que les requêtes exprimées selon ces termes. L’outil utilisé dans ce cas est Cube, mais ce schéma s’applique à toute couche sémantique prenant en charge l’autorisation par jeton. Dans notre projet, Cube était placé sur un entrepôt Redshift contenant des données de santé issues de Dossiers de Santé Électronique (DSE) et autres systèmes. Le changement clé : notre agent LLM ne génère pas de SQL et n’accède pas directement à la base de données. Il orchestre une série d’outils : un pour récupérer les vues pertinentes à partir de métadonnées vectorisées, un autre pour construire les requêtes Cube valides, et un troisième pour les exécuter via l’API Cube. Cube traduit ces requêtes en SQL en interne, appliquant les règles d’autorisation avant toute exécution. L’agent travaille via une interface contrainte, et non contre une base ouverte.
Cela présente un avantage additionnel au-delà de la sécurité. Lorsqu’un agent cible une base brute, il doit raisonner sur des tables, des jointures et des conventions de noms conçues pour les administrateurs, pas pour les modèles linguistiques. Le modèle soigné d’une couche sémantique offre une interface réduite et bien définie avec des descriptions lisibles. En vectorisant ces métadonnées dans un système de recherche, nous avons atteint environ 98 % d’exactitude pour la récupération des vues correctes à chaque question — le modèle sémantique soigneusement structuré a largement facilité la recherche par rapport au schéma brut.
Comment l’autorisation circule dans la stack
Le mécanisme s’appuie sur la propagation des jetons tout au long du flux agentique. L’utilisateur s’authentifie sur le frontend (Vercel) via Supabase, qui fournit un JWT avec identité et locataire. Quand l’utilisateur pose une question, ce JWT accompagne la requête à travers les appels d’outils de l’agent : recherche sémantique, récupération du schéma, construction de la requête jusqu’à l’API Cube.
La couche d’autorisation de Cube valide le jeton et utilise les informations du locataire pour limiter chaque requête. Même si le LLM crée une demande pour des données hors de la portée de l’utilisateur, Cube la rejette ou filtre les résultats selon les autorisations du jeton. Le LLM n’a pas besoin de connaître les frontières du locataire. Il interroge la couche sémantique, et celle-ci décide ce qui est renvoyé.
Pour valider le tout, nous avons déployé deux applications parallèles sur le même backend : une démo publique limitée aux données Medicare et une version privée pour le client avec accès complet. L’appli publique ne pouvait récupérer de données privées du client par aucun chemin de requête, confirmant l’isolation du locataire quel que soit le comportement du LLM.
Les compromis en pratique
Cette approche a un coût, et certains apprentissages méritent d’être partagés à ceux qui envisagent une architecture similaire.
La latence compte
Ajouter un saut par la couche sémantique ajoute un surcoût. Nous avons d’abord essayé l’API d’inférence IA de Cube pour la génération de requêtes, mais les temps de réponse dépassaient systématiquement 60 secondes avec une mauvaise qualité de réponses. La solution fut de garder Cube comme couche d’autorisation et d’exécution, mais de remplacer entièrement sa génération de requêtes. En vectorisant les métadonnées des vues Cube pour la recherche et en utilisant un LLM dédié entraîné sur la syntaxe Cube pour construire les requêtes, nous sommes passés sous les 15 secondes de latence. Ce n’est pas la couche sémantique elle-même qui était le goulot d’étranglement, mais la manière dont on génère les requêtes : c’est un choix de design qui impacte directement l’expérience utilisateur.
La maintenance du modèle doit être faite.
Au fil de l’évolution du schéma, le modèle sémantique doit rester synchronisé. Nous avons constaté que des lacunes dans la documentation des métadonnées expliquaient une part des échecs de construction de requêtes en évaluation. La documentation de la couche sémantique est un travail continu, pas une opération ponctuelle.
L’autorisation n’est pas toute la surface d’attaque.
Il faut toujours des défenses contre l’injection de prompt, la validation des entrées et le filtrage de la sortie. Nous avons ajouté un LLM de classification à l’entrée pour catégoriser les questions et les router, ce qui servait aussi de défense contre les tentatives d’injection visant l’agent principal. La couche sémantique gère l’autorisation ; elle ne remplace pas l’ensemble de la pile de sécurité.
L’essentiel à retenir
Si vous bâtissez un accès agentique à des données multi-locataires, poussez l’autorisation vers le bas de la stack. Ne vous fiez pas au LLM pour gérer le contrôle d’accès par des instructions de prompt. Une couche sémantique offre une API gouvernée à travers laquelle l’agent travaille, en validant chaque requête selon les permissions réelles de l’utilisateur, avant toute action sur la base de données. Le schéma : authentifier l’utilisateur, lui attribuer un jeton avec les bons droits, le propager à travers les outils de l’agent, et laisser la couche sémantique vérifier chaque demande. Le LLM devient une couche de traduction entre le langage naturel et une API gouvernée — un rôle bien plus adapté que celui de générateur de requête et contrôleur de sécurité à la fois.