Security
7 min read
1428 views

Fuite Multi-Tenant : Quand la "Row-Level Security" échoue dans le SaaS

IT
InstaTunnel Team
Published by our engineering team
Fuite Multi-Tenant : Quand la "Row-Level Security" échoue dans le SaaS

Dans le monde du Software-as-a-Service (SaaS), il n’y a pas de catastrophe plus terminale que la “fuite de données”. C’est le “Code Rouge” de l’industrie — une défaillance catastrophique de l’isolation multi-tenant où le Client A se connecte et voit les dossiers financiers sensibles, les données personnelles ou la stratégie privée du Client B.

Depuis des années, les développeurs pointent la Row-Level Security (RLS) comme la solution définitive. La logique est simple : “Il suffit d’étiqueter chaque ligne avec un tenant_id et laisser la base de données gérer le reste.” Mais à mesure que les architectures SaaS deviennent plus complexes, se reposer uniquement sur la RLS devient un pari dangereux.

Dans cette analyse approfondie, nous dépassons les simples attaques de Broken Object Level Authorization (BOLA) pour explorer les défaillances architecturales — telles que la contamination du Connection Pool, le Poisoning du cache partagé, et les fuites de contexte asynchrone — qui peuvent faire échouer la RLS silencieusement, entraînant d’énormes expositions de données.

1. L’illusion de la Row-Level Security

La Row-Level Security (RLS) est une fonctionnalité de base de données (notamment dans PostgreSQL, SQL Server, et Oracle) qui permet de définir des politiques pour restreindre les lignes qu’un utilisateur peut voir ou modifier en fonction de son identité.

Pourquoi cela semble une solution miracle

Dans une configuration RLS typique, votre application établit une connexion et exécute une commande comme :

SET app.current_tenant_id = 'client_abc';

À partir de ce moment, chaque SELECT * FROM factures devient automatiquement SELECT * FROM factures WHERE tenant_id = 'client_abc'. La logique de sécurité passe de l’application (où les développeurs pourraient oublier une clause WHERE) au moteur de la base.

La faille fatale : c’est seulement aussi bon que le contexte

Le problème principal de la RLS est qu’elle est sans état en principe, mais avec un état en pratique. La base de données ne sait pas qui est “Client A” tant que l’application ne lui dit pas. Si le pont entre l’identité de l’application et le contexte de session de la base de données se brise, tout le modèle de sécurité s’effondre.

Dernières recherches : CVE-2024-10976 et au-delà

Des vulnérabilités récentes ont montré que la RLS n’est pas infaillible. CVE-2024-10976 a mis en évidence un scénario dans PostgreSQL où les politiques de sécurité des lignes sous les sous-requêtes pouvaient ignorer les changements d’ID utilisateur. De plus, l’avis CVE-2025-8713 a révélé que les statistiques de l’optimiseur pouvaient divulguer des données échantillonnées de lignes que la RLS était censée cacher. Ces “fuites d’informations” permettent à des attaquants astucieux d’inférer le contenu des données d’autres tenants via des analyses de canaux auxiliaires des plans de requête et des messages d’erreur.

2. Le fantôme dans la machine : contamination du Connection Pool

Dans le SaaS moderne, on n’ouvre pas une nouvelle connexion à la base pour chaque requête ; ce serait trop lent. À la place, on utilise le Connection Pooling (par exemple PgBouncer, HikariCP, ou Prisma). C’est là que la première grande défaillance architecturale se produit.

Comment la contamination se produit

Imaginez une application SaaS à fort trafic.

  1. Requête 1 (Tenant A) arrive. l’application récupère la connexion #42 du pool.
  2. L’application définit le contexte de session : SET app.tenant_id = 'Tenant_A'.
  3. La requête s’exécute, les données sont renvoyées, et la requête se termine.
  4. L’erreur critique : en raison d’une exception non gérée ou d’une erreur de codage, l’application ne “nettoie” pas la connexion avant de la rendre au pool.
  5. Requête 2 (Tenant B) arrive. Elle récupère la connexion #42.
  6. L’application suppose qu’il s’agit d’une nouvelle connexion ou ne surcharge pas immédiatement le tenant_id.
  7. La fuite : la requête 2 exécute SELECT * FROM secrets et — parce que la connexion #42 conserve encore l’état de Tenant A — la base de données sert les secrets de Tenant A à Tenant B.

La défaillance du “nettoyage”

De nombreux développeurs comptent sur le “middleware” pour définir et réinitialiser les tenant_id. Cependant, si un service backend plante ou si une transaction de base de données est interrompue en cours de route, la logique de “reset” peut ne jamais s’exécuter. Sans une commande RESET ALL ou DISCARD ALL imposée par le proxy de pooling lui-même, la connexion reste “empoisonnée” avec l’identité du précédent utilisateur.

3. Poisoning du cache partagé : quand Redis devient la fuite

Pour atteindre une latence inférieure au milliseconde, les applications SaaS s’appuient fortement sur des caches partagés comme Redis ou Memcached. Cela introduit une seconde couche, souvent invisible, de risque multi-tenant.

Collisions dans l’espace clé

L’échec le plus courant du cache est simple : ne pas préfixer les clés avec le tenant_id.

  • Mauvais : GET user_profile_123
  • Bon : GET tenant_A:user_profile_123

Mais même avec le préfixage, des “conditions de course” architecturales peuvent survenir.

La course dans le backend

Considérez un scénario où l’application utilise un pattern “Cache-Aside”. Lorsqu’une requête arrive, l’application vérifie Redis. Si c’est un miss, elle interroge la base et écrit dans Redis.

  1. Le tenant A demande son tableau de bord.
  2. L’application calcule les données du tableau, mais, à cause d’un bug dans la logique asynchrone, elle écrit le résultat dans une clé générique comme latest_dashboard_stats.
  3. Le tenant B demande son tableau de bord quelques millisecondes plus tard. Il atteint le cache et reçoit les données tout juste écrites par le tenant A.

Poisoning du cache partagé (perspective 2025)

En 2024 et 2025, une nouvelle frontière de fuite du cache a émergé : le service multi-tenant LLM. La recherche sur le partage de KV-Cache (comme l’attaque “PROMPTPEEK”) a montré que lorsque plusieurs utilisateurs partagent le même cache GPU sous-jacent pour l’efficacité, un utilisateur peut reconstituer les prompts d’un autre en analysant les hits de cache et les canaux auxiliaires de timing. Bien que spécifique à l’IA, cela illustre une vérité plus large : toute ressource partagée utilisée pour l’optimisation est une voie potentielle de fuite.

4. Le tueur silencieux : fuites de contexte asynchrone

Les backends SaaS modernes sont presque entièrement asynchrones (Node.js, Go, Python FastAPI). Ces langages utilisent des objets “contexte” pour transmettre les tenant IDs à travers une chaîne d’appels de fonctions sans “prop drilling”.

Le piège du thread unique

Dans Node.js, AsyncLocalStorage est la méthode standard pour suivre l’état du tenant. Cependant, si un développeur utilise une variable globale ou un singleton mal scoped pour stocker un tenant_id, il crée un risque majeur de fuite de données.

  • Le scénario : Node.js gère des milliers de requêtes simultanées sur un seul thread.
  • L’échec : si un développeur écrit accidentellement dans une variable globale partagée — même pour une microseconde — lors d’un bloc await, toutes les autres requêtes concurrentes sur ce thread pourraient adopter cette valeur.

Une condition de course dans un service backend peut mener à un échange d’identité, où le “contexte” de la Requête A est accidentellement écrasé par celui de la Requête B parce qu’ils ont tous deux accédé à une ressource partagée mal isolée au niveau du thread ou de la tâche.

5. Au-delà de la RLS : stratégies pour un multi-tenancy renforcé

Si la RLS ne suffit pas, que faire ? Pour éviter la fuite de données, les architectes SaaS doivent adopter une approche de Defense-in-Depth.

1. Le “Reset” obligatoire pour le Connection Pool

Ne faites pas confiance à votre code applicatif pour nettoyer les connexions. Configurez votre proxy de pooling (comme PgBouncer) pour utiliser le “Session Pooling” avec une requête server_reset_query obligatoire. Chaque fois qu’une connexion est rendue au pool, le proxy doit exécuter DISCARD ALL pour effacer les tables temporaires, variables de session, et déclarations préparées.

2. Isolation cryptographique (le “Standard d’or”)

La défense ultime est de s’assurer que même si un tenant voit les données d’un autre, il ne peut pas les lire.

Chiffrement côté application (ALE) : chiffrer les colonnes sensibles avec une clé propre à chaque tenant.

Si le Tenant B récupère accidentellement la ligne du Tenant A via une connexion contaminée, il ne verra qu’un blob chiffré. Il ne possède pas la clé de déchiffrement de Tenant A (qui doit être stockée dans un KMS comme AWS KMS ou HashiCorp Vault).

3. Vérifications au niveau logique

Ne vous fiez jamais uniquement à la base pour la vérification. Même avec la RLS activée, votre code applicatif doit effectuer une vérification manuelle :

if record.tenant_id != current_user.tenant_id:
    raise SecurityLeakError("Accès inter-tenant détecté!")

Cela peut sembler redondant, mais dans un environnement multi-tenant, la redondance est la seule voie vers la sécurité.

4. Cache tenant-aware avec ACLs

Si vous utilisez Redis 6.0 ou plus récent, cessez d’utiliser un seul mot de passe “admin”. Utilisez les ACLs Redis pour créer des utilisateurs spécifiques à chaque tenant, limités à des motifs de clés précis :

ACL SETUSER tenant_A on epassword ~tenant_A:* +get +set

En appliquant l’isolation au niveau du cache, vous vous assurez qu’une erreur dans votre logique applicative ne peut pas conduire à un “GET” inter-tenant.

6. Conclusion : Le paradoxe de la sécurité SaaS

Plus nous rendons nos architectures SaaS efficaces — via le connection pooling, le cache partagé, et l’exécution asynchrone — plus nous augmentons le risque de fuite multi-tenant.

La Row-Level Security est un outil puissant, mais ce n’est pas une solution complète. C’est un “filet de sécurité”, pas un “mur d’enceinte”. La véritable isolation des données nécessite une approche holistique qui considère la gestion d’état comme la principale frontière de sécurité.

Alors qu’on se dirige vers 2026, la complexité du SaaS ne fera qu’augmenter. Les organisations qui se contentent d’une seule couche de défense (comme la RLS) finiront par faire face à la redoutable “fuite de données”. Celles qui survivront seront celles qui ont construit leur architecture en partant du principe que le contexte de la base de données peut échouer, que le cache peut être empoisonné, et que la connexion peut être contaminée — et qui s’y sont préparées en conséquence.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#multi-tenant data leakage, saas data bleed, row level security failure, tenant isolation vulnerability, multi-tenant security risk, saas architecture flaws, connection pool contamination, shared cache poisoning, redis tenant leakage, memcached vulnerability, session data cross-tenant, backend race condition, data isolation failure, saas security breach, cross-tenant data exposure, multi-tenant misconfiguration, broken tenant isolation, cloud multi-tenant risk, database row level security flaws, saas authorization failure, data partitioning vulnerability, session mix-up attack, backend concurrency bug, distributed cache poisoning, cache key collision, tenant id injection, saas access control flaw, cross customer data leak, b2b saas security, enterprise saas breach, tenant boundary violation, microservices isolation failure, api multi-tenant security, identity scoping vulnerability, customer data exposure, saas data segregation, cloud application security risk, multi-tenant threat model, broken data tenancy, privilege escalation across tenants, backend state pollution, pooled connection vulnerability, web session contamination, authentication context leak, distributed systems security flaw, race condition exploit, isolation boundary failure, saas breach prevention, zero trust multi-tenancy, shared infrastructure risk, cloud data isolation, tenant aware architecture, authorization context bug, object ownership failure, saas backend vulnerability, application level data leak, cloud tenancy risk, devsecops saas, security architecture failure, cross-account access bug, backend design flaw, shared resource exploitation, saas data protection, multi-tenant audit controls, isolation testing saas, data residency breach

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles