Conception d'une recherche globale respectueuse des permissions sans fuites de données
Apprenez à concevoir une recherche globale respectueuse des permissions avec indexation rapide et contrôles d'accès par enregistrement, pour des résultats rapides sans fuites.

Pourquoi la recherche globale peut entraîner des fuites de données
La recherche globale signifie généralement une seule barre qui parcourt toute l'application : clients, tickets, factures, documents, utilisateurs, et tout le reste. Elle alimente souvent l'autocomplétion et une page de résultats rapide pour que les utilisateurs accèdent directement à un enregistrement.
La fuite se produit quand la recherche renvoie quelque chose que l'utilisateur n'est pas censé savoir qu'il existe. Même s'il ne peut pas ouvrir l'enregistrement, une simple ligne comme un titre, un nom, un tag ou un extrait mis en valeur peut révéler des informations sensibles.
La recherche parait « en lecture seule », donc les équipes la sous-estiment. Mais elle peut divulguer des données via les titres et aperçus, l'autocomplétion, les totaux de résultats, des facettes comme « Clients (5) », et même par des différences de temps de réponse (rapide pour certains termes, plus lent pour d'autres).
Cela se manifeste souvent plus tard, pas le premier jour. Au démarrage, les équipes livrent la recherche quand il n'y a qu'un rôle, ou quand tout est visible dans une base de test. À mesure que le produit grandit, vous ajoutez des rôles (support vs ventes, managers vs agents) et des fonctionnalités comme boîtes partagées, notes privées, clients restreints et « seulement mes comptes ». Si la recherche repose encore sur les anciennes hypothèses, elle commence à renvoyer des indices inter-équipes ou inter-clients.
Un mode d'échec fréquent est d'indexer « tout » pour la vitesse, puis d'essayer de filtrer les résultats dans l'application après la requête. C'est déjà trop tard. Le moteur de recherche a déjà décidé ce qui correspond, et il peut exposer des enregistrements restreints via des suggestions, des comptes ou des champs partiels.
Imaginez un agent support qui ne doit voir que les tickets de ses clients assignés. Il tape « Acme » et l'autocomplétion affiche « Acme - Escalade juridique » ou « Avis de violation Acme ». Même si cliquer renvoie « accès refusé », le titre seul est une fuite.
L'objectif d'une recherche globale consciente des permissions est simple à énoncer et difficile à réaliser : renvoyer des résultats rapides et pertinents tout en appliquant les mêmes règles d'accès que pour l'ouverture de chaque enregistrement. Chaque requête doit se comporter comme si l'utilisateur ne voyait que sa tranche de données, et l'interface doit éviter de laisser fuiter des indices supplémentaires (comme des totaux) en dehors de cette tranche.
Ce que vous indexez et ce que vous devez protéger
La recherche globale paraît simple parce que les utilisateurs tapent des mots et attendent des réponses. En coulisses, vous créez une nouvelle surface d'exposition des données. Avant de choisir un index ou une fonctionnalité de base, clarifiez deux choses : quels objets vous recherchez (entités), et quelles parties de ces objets sont sensibles.
Une entité est tout enregistrement que quelqu'un pourrait vouloir trouver rapidement. Dans la plupart des apps métier, cela inclut clients, tickets support, factures, commandes et fichiers (ou métadonnées de fichiers). Cela peut aussi inclure des fiches personnes (utilisateurs, agents), des notes internes et des objets système comme intégrations ou clés API. Si ça a un nom, un identifiant ou un statut que quelqu'un peut taper, ça finit souvent dans la recherche globale.
Règles par enregistrement vs règles par table
Les règles par table sont brutales : soit vous pouvez accéder à toute la table, soit non. Exemple : seul le service Finance peut ouvrir la page Factures. C'est facile à raisonner, mais cela échoue quand différentes personnes doivent voir différentes lignes dans la même table.
Les règles par enregistrement décident visibilité ligne par ligne. Exemple : un agent support peut voir les tickets assignés à son équipe, tandis qu'un manager peut voir tous les tickets de sa région. Une règle courante est la propriété du locataire : dans une app multi-tenant, un utilisateur ne voit que les enregistrements où customer_id = their_customer_id.
Ce sont ces règles par enregistrement qui font souvent fuiter la recherche. Si votre index renvoie un hit avant que vous vérifiiez l'accès ligne à ligne, vous avez déjà révélé qu'une chose existe.
Ce que « autorisé à voir » signifie en pratique
« Autorisé » n'est rarement un simple oui/non. Cela combine souvent propriété (créé par moi, assigné à moi), appartenance (mon équipe, mon département, mon rôle), périmètre (ma région, unité métier, projet), état de l'enregistrement (publié, non archivé), et cas spéciaux (clients VIP, mesures légales, tags restreints).
Écrivez ces règles en langage clair d'abord. Ensuite, vous les transformerez en modèle de données et en contrôles côté serveur.
Décidez ce qu'il est sûr d'afficher dans un aperçu
Les résultats de recherche incluent souvent un extrait, et ces extraits peuvent fuir des données même si l'utilisateur ne peut pas ouvrir l'enregistrement.
Un choix par défaut sûr est d'afficher uniquement des champs minimaux et non sensibles tant que l'accès n'est pas confirmé : un nom d'affichage ou un titre (parfois masqué), un identifiant court (numéro de commande), un statut haut niveau (Ouvert, Payé, Expédié), une date (création ou mise à jour) et une étiquette d'entité générique (Ticket, Facture).
Exemple concret : si quelqu'un recherche « Acme merger » et qu'un ticket restreint existe, renvoyer « Ticket : Acme merger draft - Legal » est déjà une fuite. Un résultat plus sûr est « Ticket : Restreint » sans extrait, ou pas de résultat du tout, selon votre politique.
Bien définir cela en amont simplifie les décisions suivantes : ce que vous indexez, comment vous filtrez et ce que vous êtes prêt à révéler.
Exigences de base pour une recherche sûre et rapide
Les gens utilisent la recherche globale quand ils sont pressés. Si c'est plus lent qu'une seconde, ils arrêtent de lui faire confiance et reviennent au filtrage manuel. Mais la vitesse n'est qu'une moitié du travail. Une recherche rapide qui divulgue ne serait-ce qu'un titre d'enregistrement, un nom de client ou un sujet de ticket est pire que pas de recherche du tout.
La règle centrale est non-négociable : faites respecter les permissions au moment de la requête, pas seulement dans l'interface. Cacher une ligne après l'avoir récupérée, c'est déjà trop tard, car le système a touché des données qu'il n'aurait pas dû retourner.
La même chose s'applique à tout ce qui entoure la recherche, pas seulement la liste finale. Suggestions, top hits, totaux et même le comportement « pas de résultats » peuvent fuir de l'information. Une autocomplétion qui montre « Acme Renewal Contract » à quelqu'un qui ne peut pas l'ouvrir est une fuite. Une facette qui dit « 12 factures correspondantes » est une fuite si l'utilisateur ne peut en voir que 3. Même le temps de réponse peut fuir si des correspondances restreintes ralentissent la requête.
Une recherche globale sûre a besoin de quatre choses :
- Correction : chaque élément retourné est autorisé pour cet utilisateur, pour ce locataire, maintenant.
- Vitesse : résultats, suggestions et totaux restent rapides et constants, même à grande échelle.
- Cohérence : quand l'accès change (mise à jour d'un rôle, réaffectation d'un ticket), le comportement de la recherche change rapidement et de manière prévisible.
- Auditabilité : vous pouvez expliquer pourquoi un élément a été retourné et consigner l'activité de recherche pour les enquêtes.
Changez de perspective : traitez la recherche comme une autre API de données, pas comme une simple fonctionnalité UI. Cela signifie appliquer les mêmes règles d'accès pour la construction d'index, l'exécution des requêtes et chaque endpoint lié (autocomplétion, recherches récentes, requêtes populaires).
Trois schémas de conception courants (et quand les utiliser)
Une barre de recherche est facile à construire. Une recherche globale consciente des permissions est plus difficile car l'index veut renvoyer instantanément des résultats, tandis que votre application ne doit jamais révéler des enregistrements auxquels l'utilisateur n'a pas accès, même indirectement.
Voici trois schémas fréquemment utilisés. Le bon choix dépend de la complexité de vos règles d'accès et du niveau de risque acceptable.
Approche A : indexer uniquement des champs « sûrs », puis récupérer après vérification des permissions. Vous stockez un document minimal dans l'index de recherche, par exemple un ID plus une étiquette non sensible sûre à afficher à quiconque peut atteindre l'interface. Quand l'utilisateur clique, l'application charge l'enregistrement complet depuis la base primaire et applique les règles réelles côté serveur.
Cela réduit le risque de fuite, mais la recherche peut paraître peu fournie car les résultats manquent de contexte. Il faut aussi une formulation UI soignée pour qu'une étiquette « sûre » n'expose pas accidentellement un secret.
Approche B : stocker les attributs de permission dans l'index et filtrer là. Vous incluez des champs comme tenant_id, team_id, owner_id, flags de rôle ou project_id dans chaque document indexé. Chaque requête ajoute des filtres correspondant au périmètre de l'utilisateur.
Cela donne des résultats riches et rapides et une bonne autocomplétion, mais cela ne fonctionne que quand les règles d'accès peuvent s'exprimer sous forme de filtres. Si les permissions dépendent d'une logique complexe (par ex. « assigné OU de garde cette semaine OU impliqué dans un incident »), il devient difficile de rester correct.
Approche C : hybride. Filtre grossier dans l'index, vérification finale en base. Vous filtrez dans l'index en utilisant des attributs larges et stables (tenant, workspace, customer), puis vous revérifiez les permissions sur le petit ensemble d'IDs candidats dans la base primaire avant de tout retourner.
C'est souvent le chemin le plus sûr pour les apps réelles : l'index reste rapide et la base reste source de vérité.
Choisir un schéma
Choisissez A quand vous voulez la mise en place la plus simple et que vous pouvez vivre avec des extraits minimaux. Choisissez B quand vous avez des périmètres clairs et majoritairement statiques (multi-tenant, accès par équipe) et que vous avez besoin d'une autocomplétion très rapide. Choisissez C quand vous avez de nombreux rôles, des exceptions ou des règles par enregistrement qui changent souvent. Pour des données à haut risque (RH, finance, médical), préférez C car « presque correct » n'est pas acceptable.
Étape par étape : concevoir un index qui respecte les règles d'accès
Commencez par écrire vos règles d'accès comme si vous les expliquiez à un nouveau collègue. Évitez « admin voit tout » sauf si c'est vraiment vrai. Dites plutôt pourquoi : « Les agents support voient les tickets de leur locataire. Les responsables d'équipe voient aussi les tickets de leur unité. Seul le propriétaire du ticket et l'agent assigné voient les notes privées. » Si vous ne pouvez pas expliquer pourquoi quelqu'un peut voir un enregistrement, vous aurez du mal à l'encoder en toute sécurité.
Ensuite, choisissez un identifiant stable et définissez un document de recherche minimal. L'index ne doit pas être une copie complète de votre ligne de base de données. Conservez seulement ce qui est nécessaire pour trouver et afficher dans la liste de résultats, comme le titre, le statut et éventuellement un court extrait non sensible. Placez les champs sensibles derrière une seconde lecture qui vérifie aussi les permissions.
Puis décidez quels signaux de permission vous pouvez filtrer rapidement. Ce sont des attributs qui verrouillent l'accès et peuvent être stockés sur chaque document indexé, comme tenant_id, org_unit_id et quelques flags de périmètre. L'objectif est que chaque requête puisse appliquer des filtres avant de retourner des résultats, y compris pour l'autocomplétion.
Un flux de travail pratique :
- Définissez les règles de visibilité pour chaque entité (tickets, clients, factures) en langage clair.
- Créez un schéma de document de recherche avec record_id plus seulement des champs sûrs et recherchables.
- Ajoutez des champs de permission filtrables (tenant_id, org_unit_id, visibility_level) à chaque document.
- Gérez les exceptions avec des permissions explicites : stockez une allowlist (IDs utilisateurs) ou des IDs de groupe pour les éléments partagés.
Les éléments partagés et les exceptions sont là où les designs cassent. Si un ticket peut être partagé entre équipes, n'ajoutez pas juste un booléen. Utilisez des allowlists explicites contrôlables par filtres. Si l'allowlist est grande, préférez des grants par groupe plutôt que par utilisateur.
Garder l'index synchronisé sans surprises
Une expérience de recherche sécurisée dépend d'une chose ennuyeuse mais essentielle : l'index doit refléter la réalité. Si un enregistrement est créé, modifié, supprimé ou que ses permissions changent, les résultats doivent suivre rapidement et de façon prévisible.
Suivre créations, mises à jour, suppressions
Traitez l'indexation comme faisant partie du cycle de vie des données. Un modèle mental utile : à chaque fois que la source de vérité change, vous émettez un événement et l'indexeur réagit.
Les approches courantes incluent triggers de base, événements applicatifs ou une file de jobs. L'important est que les événements ne se perdent pas. Si votre app peut sauvegarder l'enregistrement mais échouer à l'indexer, vous obtiendrez des comportements déroutants comme « je sais que ça existe mais la recherche ne le trouve pas. »
Les changements de permissions sont des changements d'index
Beaucoup de fuites surviennent quand le contenu est mis à jour mais pas les métadonnées d'accès. Les changements de permissions viennent d'une mise à jour de rôle, d'un transfert d'équipe, d'un changement de propriétaire, d'une réaffectation client ou d'une fusion de tickets.
Faites des changements de permission des événements de première classe. Si votre recherche dépend de filtres tenant ou équipe, assurez-vous que les documents indexés contiennent les champs nécessaires pour appliquer ces filtres (tenant_id, team_id, owner_id, allowed_role_ids). Quand ces champs changent, réindexez.
La partie délicate est le rayon d'impact. Un changement de rôle peut affecter des milliers d'enregistrements. Prévoyez une voie de réindexation en masse avec progression, retries et possibilité de pause.
Prévoir la cohérence éventuelle
Même avec de bons événements, il y aura une fenêtre où la recherche est en retard. Décidez ce que les utilisateurs doivent voir dans les premières secondes après un changement.
Deux règles aident :
- Soyez constant sur les délais. Si l'indexation termine habituellement en 2–5 secondes, communiquez cette attente quand c'est pertinent.
- Préférez l'absence à la fuite. Il est plus sûr qu'un enregistrement nouvellement autorisé apparaisse un peu tard que qu'un enregistrement récemment révoqué continue d'apparaître.
Ajouter une solution de repli sûre quand l'index est obsolète
La recherche sert à la découverte, mais la consultation des détails est là où les fuites font mal. Faites une seconde vérification des permissions au moment de la lecture avant d'afficher des champs sensibles. Si un résultat a glissé à travers parce que l'index est obsolète, la page de détails doit encore bloquer l'accès.
Un bon schéma : afficher des extraits minimaux en recherche, puis revérifier les permissions quand l'utilisateur ouvre l'enregistrement (ou développe un aperçu). Si la vérification échoue, afficher un message clair et retirer l'élément de la liste visible au prochain rafraîchissement.
Erreurs courantes qui causent des fuites de données
La recherche peut fuir des données même si votre page d'« ouvrir l'enregistrement » est verrouillée. Un utilisateur peut ne jamais cliquer sur un résultat et apprendre des noms, des IDs clients ou la taille d'un projet caché. La recherche consciente des permissions doit protéger non seulement les documents, mais aussi les indices sur les documents.
L'autocomplétion est une source fréquente de fuite. Les suggestions sont souvent alimentées par une recherche préfixe rapide qui esquive les vérifications complètes de permission. L'UI semble inoffensive, mais une seule lettre tapée peut révéler un nom de client ou un email d'employé. L'autocomplétion doit exécuter les mêmes filtres d'accès que la recherche complète, ou être construite à partir d'un jeu de suggestions pré-filtré (par exemple, par locataire et par rôle).
Les totaux de facettes et la bannière « Environ 1 243 résultats » sont une autre fuite discrète. Les totaux peuvent confirmer qu'une chose existe même si vous cachez les enregistrements. Si vous ne pouvez pas calculer les totaux en toute sécurité selon les mêmes règles d'accès, montrez moins de détails ou omettez-les.
Le caching est un autre coupable fréquent. Des caches partagés entre utilisateurs, rôles ou locataires peuvent créer des « fantômes de résultats », où un utilisateur voit des résultats générés pour un autre. Cela peut arriver avec des caches en edge, des caches applicatifs ou des caches en mémoire dans un service de recherche.
Pièges à vérifier tôt :
- L'autocomplétion et les recherches récentes sont filtrées par les mêmes règles que la recherche complète.
- Les comptes de facettes et totaux sont calculés après permissions.
- Les clés de cache incluent l'ID du locataire et une signature de permission (rôle, équipe, ID utilisateur).
- Les logs et analytics n'enregistrent pas les requêtes brutes ni des extraits pour des données restreintes.
Enfin, méfiez-vous des filtres trop larges. « Filtrer par locataire seulement » est l'erreur classique multi-tenant, mais ça arrive aussi à l'intérieur d'un même locataire : filtrer par « département » quand l'accès est ligne-par-ligne. Exemple : un agent support recherche « refund » et obtient des résultats pour tous les clients du locataire, y compris des comptes VIP destinés à une équipe restreinte. La solution est simple en principe : appliquez des règles au niveau des lignes dans chaque chemin de requête (recherche, autocomplétion, facettes, exports), pas seulement dans la vue d'enregistrement.
Détails de confidentialité et sécurité qu'on oublie
Beaucoup de conceptions se concentrent sur « qui peut voir quoi », mais les fuites surviennent aussi via les bords : états vides, temps de réponse et petits indices dans l'UI. La recherche consciente des permissions doit être sûre même quand elle ne retourne rien.
Une fuite facile est la confirmation par l'absence. Si un utilisateur non autorisé recherche un nom de client, un ID de ticket ou un email et obtient un message spécial comme « Accès refusé », vous avez confirmé que l'enregistrement existe. Traitez « aucun résultat » comme résultat par défaut pour « n'existe pas » et « existe mais non autorisé ». Gardez temps de réponse et formulation constants pour éviter les devinettes basées sur la vitesse.
Correspondances partielles sensibles
L'autocomplétion et la recherche au fil de la saisie sont les endroits où la vie privée glisse. Les correspondances partielles sur emails, numéros de téléphone et identifiants gouvernementaux ou clients peuvent révéler plus que prévu. Décidez d'emblée comment se comportent ces champs.
Un ensemble de règles pratiques :
- Exiger une correspondance exacte pour les champs à haut risque (email, téléphone, IDs).
- Éviter d'afficher des extraits mis en évidence qui révèlent du texte caché.
- Envisager de désactiver l'autocomplétion pour les champs sensibles.
Si afficher même un caractère aide à deviner une donnée, considérez-le comme sensible.
Contrôles anti-abus qui ne créent pas de nouveaux risques
Les endpoints de recherche sont parfaits pour des attaques d'énumération : tester de nombreuses requêtes pour cartographier l'existence. Ajoutez des limites de débit et la détection d'anomalies, mais faites attention à ce que vous stockez. Les logs contenant des requêtes brutes peuvent devenir une seconde fuite.
Restez simple : rate-limit par utilisateur, par IP et par locataire ; loggez des comptes, des timings et des motifs grossiers (pas le texte complet de la requête) ; alertez sur des « near-miss » répétés (comme des IDs séquentiels) ; et bloquez ou renforcez l'authentification après des échecs répétés.
Rendez vos erreurs ennuyeuses. Utilisez le même message et l'état vide pour « pas de résultats », « non autorisé » et « filtres invalides ». Moins l'UI dit, moins elle peut révéler accidentellement.
Exemple : une équipe support cherchant des tickets parmi des clients
Un agent support, Maya, travaille sur une équipe qui gère trois comptes clients. Elle a une seule barre de recherche dans l'entête de l'app. Le produit a un index global sur tickets, contacts et entreprises, mais chaque résultat doit respecter les règles d'accès.
Maya tape « Alic » car un appelant a dit s'appeler Alice. L'autocomplétion affiche quelques suggestions. Elle clique « Alice Nguyen - Ticket : Réinitialisation de mot de passe. » Avant d'ouvrir quoi que ce soit, l'app reverifie l'accès pour cet enregistrement. Si le ticket est toujours assigné à son équipe et que son rôle le permet, elle arrive sur le ticket.
Ce que Maya voit à chaque étape :
- Barre de recherche : des suggestions rapides, mais uniquement pour les enregistrements qu'elle peut accéder maintenant.
- Liste de résultats : sujet du ticket, nom du client, date de dernière mise à jour. Pas de placeholders « vous n'avez pas accès ».
- Détails du ticket : la vue complète ne se charge qu'après une vérification serveur des permissions. Si l'accès a changé, l'app affiche « Ticket introuvable » (pas « interdit »).
Comparez cela avec Leo, un nouvel agent en formation. Son rôle ne voit que les tickets marqués « Public to Support » et seulement pour un client. Leo tape la même requête « Alic ». Il voit moins de suggestions, et aucune des suggestions manquantes n'est insinuée. Il n'y a pas de compteur « 5 résultats » qui révélerait d'autres correspondances. L'UI montre simplement ce qu'il peut ouvrir.
Plus tard, un manager réassigne « Alice Nguyen - Réinitialisation de mot de passe » de l'équipe de Maya à une équipe d'escalade spécialisée. Dans une courte fenêtre (souvent secondes à quelques minutes, selon votre méthode de synchronisation), la recherche de Maya ne renvoie plus ce ticket. Si elle a la page de détails ouverte et rafraîchit, l'app reverifie les permissions et le ticket disparaît.
C'est le comportement souhaité : saisie rapide et résultats rapides, sans odeur de donnée via totaux, extraits ou entrées d'index obsolètes.
Checklist et prochaines étapes pour une implémentation sûre
Une recherche globale consciente des permissions n'est « terminée » que quand les petits bords sont sûrs. Beaucoup de fuites se produisent dans des endroits qui paraissent anodins : autocomplétion, totaux, exports.
Vérifications rapides de sécurité
Avant de déployer, parcourez ces contrôles avec de vraies données, pas des échantillons :
- Autocomplétion : ne suggérez jamais un titre, un nom ou un ID que l'utilisateur ne peut pas ouvrir.
- Totaux et facettes : si vous affichez des totaux ou des comptes groupés, calculez-les après application des permissions (ou omettez-les).
- Exports et actions en masse : l'export de la « recherche actuelle » doit reverifier l'accès par ligne au moment de l'export.
- Tri et mise en évidence : ne triez ni ne mettez en valeur en utilisant des champs que l'utilisateur n'a pas le droit de voir.
- « Introuvable » vs « interdit » : pour les entités sensibles, envisagez la même forme de réponse pour que l'on ne puisse pas confirmer l'existence.
Un plan de test à exécuter
Créez une petite matrice de rôles (rôles x entités) et un jeu de données avec des cas piégés : enregistrements partagés, accès récemment révoqués et homonymes cross-tenant.
Testez en trois passes : (1) tests matrice de rôles où vous vérifiez que les enregistrements refusés n'apparaissent jamais dans résultats, suggestions, totaux ou exports ; (2) tests « essayer de casser » où vous collez des IDs, cherchez par email ou téléphone et testez des correspondances partielles qui ne devraient rien renvoyer ; (3) tests de timing et cache où vous changez des permissions et vérifiez que les résultats se mettent à jour rapidement sans suggestions obsolètes.
Opérationnellement, prévoyez le jour où les résultats « ont l'air faux ». Consignez le contexte de la requête (utilisateur, rôle, locataire) et les filtres de permission appliqués, mais évitez de stocker les requêtes sensibles ou des extraits. Pour le débogage sûr, construisez un outil admin-only qui peut expliquer pourquoi un enregistrement a matché et pourquoi il a été autorisé.
Si vous construisez sur AppMaster (appmaster.io), une approche pratique est de garder la recherche comme flux côté serveur : modélisez entités et relations dans le Data Designer, appliquez les règles d'accès dans les Business Processes et réutilisez ces mêmes vérifications pour l'autocomplétion, la liste de résultats et les exports afin qu'il n'y ait qu'un seul endroit à corriger.


