Un client de base de données exécute des requêtes. Beaucoup de requêtes. En développement, en staging, en production. Certaines sont inoffensives, d'autres peuvent détruire des données en une fraction de seconde. La question n'est pas de savoir si un développeur va un jour lancer un DROP TABLE au mauvais endroit, mais quand.
Dans QoreDB, chaque requête passe par un pipeline d'interception avant et après son exécution. Ce pipeline, appelé Universal Query Interceptor, remplit trois rôles : journaliser chaque opération pour la traçabilité, mesurer les performances pour détecter les requêtes lentes, et appliquer des règles de sécurité pour bloquer ou demander confirmation sur les opérations dangereuses. Le tout est implémenté côté Rust, dans le backend Tauri.
Pourquoi tout se passe côté backend
La première version de l'intercepteur était implémentée côté frontend, en TypeScript. Elle fonctionnait, mais elle posait un problème fondamental : tout ce qui tourne dans le processus de rendu d'une application Tauri est, par nature, contournable. Un utilisateur qui ouvre la console développeur peut modifier le comportement JavaScript. Les règles de sécurité appliquées côté frontend ne sont que des suggestions.
La réécriture backend-first a supprimé cette surface d'attaque. L'interception se fait maintenant dans le processus Rust, entre le moment où la commande Tauri reçoit la requête et celui où elle atteint le driver de base de données. Le frontend ne fait qu'afficher les résultats et proposer l'interface de configuration. Il ne prend aucune décision de sécurité.
Ce choix a aussi simplifié la base de code. L'ancienne implémentation frontend dupliquait la logique du backend sans apporter de garantie supplémentaire. Elle a été entièrement supprimée.
Le pipeline en quatre étapes
L'intercepteur est structuré autour d'une struct Rust InterceptorPipeline qui orchestre trois composants : AuditStore pour la journalisation, ProfilingStore pour les métriques de performance, et SafetyEngine pour les règles de sécurité. Chacun est protégé par un verrou en lecture/écriture (via parking_lot) et partagé entre les threads Tokio.
Le flux d'une requête suit quatre étapes. D'abord, le pipeline construit un contexte qui rassemble les métadonnées de la requête : identifiant de session, driver utilisé, environnement cible, type d'opération détecté, base de données, et résultat de l'analyse SQL. Ensuite, la phase de pré-exécution soumet ce contexte au SafetyEngine, qui évalue les règles de sécurité. Si une règle bloque la requête, l'exécution s'arrête immédiatement et l'événement est journalisé. Si la requête est autorisée, elle est transmise au driver de base de données. Enfin, en post-exécution, le résultat (succès, erreur, nombre de lignes, durée) est enregistré dans l'audit et dans le profiling.
Ce design a un overhead mesuré entre 0.1 et 0.5 ms par requête. Sur une requête qui prend 50 ms à s'exécuter sur PostgreSQL, c'est négligeable. Et cette mesure inclut la sérialisation de l'entrée d'audit en JSONL.
Des règles de sécurité intégrées et extensibles
Le SafetyEngine embarque six règles de base, non supprimables, qui couvrent les cas les plus critiques. Les DROP et TRUNCATE sont bloqués en production, sans possibilité de contournement. Les DELETE en production demandent une confirmation explicite. Les UPDATE et DELETE sans clause WHERE déclenchent aussi une confirmation, en production comme en staging. Les ALTER en production émettent un avertissement.
Chaque règle cible un ou plusieurs environnements et un ou plusieurs types d'opérations. Certaines utilisent un pattern regex pour affiner la détection. Par exemple, la règle qui détecte un UPDATE sans WHERE utilise une expression régulière qui vérifie l'absence de clause conditionnelle après le SET.
Les utilisateurs peuvent ajouter leurs propres règles via l'interface de configuration. Une règle custom suit la même structure qu'une règle intégrée : un nom, des environnements cibles, des types d'opérations, un pattern regex optionnel, et une action (bloquer, avertir, ou demander confirmation). Cela permet d'adapter les garde-fous au contexte de chaque projet.
Profiling et détection des requêtes lentes
Le ProfilingStore collecte les métriques de chaque requête exécutée : temps d'exécution, succès ou échec, type d'opération, environnement. À partir de ces données, il calcule les percentiles P50, P95 et P99 sur les 10 000 dernières requêtes. Ces indicateurs donnent une vision réaliste de la distribution des latences, pas seulement la moyenne.
Le mécanisme de slow queries capture automatiquement toute requête dont le temps d'exécution dépasse un seuil configurable (1000 ms par défaut). Les 100 dernières slow queries sont conservées en mémoire avec leur contexte complet : requête, durée, base de données cible, environnement. Ce sont des métriques opérationnelles bornées, conçues pour le diagnostic en temps réel, pas un historique exhaustif.
L'ensemble est exportable en JSON pour analyse externe. Un développeur qui observe un P95 anormalement élevé peut exporter les métriques, identifier les requêtes responsables, et agir directement.
Audit : traçabilité complète en JSONL
L'AuditStore enregistre chaque requête avec un ensemble de métadonnées : timestamp, identifiant de session, driver, requête complète, environnement, type d'opération, base de données cible, résultat (succès ou erreur), temps d'exécution, nombre de lignes affectées, et si la requête a été bloquée par une règle.
La persistance utilise le format JSONL (JSON Lines), où chaque ligne est un objet JSON indépendant. Ce format est adapté à l'écriture en append, sans besoin de réécrire le fichier entier à chaque entrée. Le fichier est stocké localement dans le répertoire de données de l'application, et une rotation automatique se déclenche quand le nombre d'entrées dépasse la limite configurée (10 000 par défaut).
Un cache mémoire des 1 000 dernières entrées permet un accès rapide depuis l'interface. L'API expose des filtres pour parcourir les logs par type d'opération, par environnement, par résultat, ainsi que des statistiques agrégées.
En pratique : ce que voit l'utilisateur
L'intercepteur est transparent en usage normal. Un SELECT s'exécute sans friction, le profiling et l'audit tournent en arrière-plan. L'utilisateur ne voit l'intercepteur que quand il doit intervenir : une requête bloquée affiche un message clair expliquant quelle règle s'est déclenchée. Une requête nécessitant confirmation présente un dialogue avec le détail de l'opération et l'environnement concerné.
Trois panneaux dédiés dans l'interface permettent de consulter les logs d'audit, les métriques de profiling avec les slow queries, et les règles de sécurité avec la possibilité d'en ajouter ou d'en modifier. La configuration complète (activation des modules, seuils, règles custom) est persistée dans un fichier JSON local et modifiable depuis l'UI sans redémarrage.
Le choix de tout implémenter côté Rust garantit que ces garde-fous fonctionnent indépendamment de l'état du frontend. Même si l'interface plante, le backend continue de journaliser et de bloquer les requêtes dangereuses. C'est un filet de sécurité qui ne dépend pas de l'affichage.
Restez informé des nouveautés
Rejoignez notre newsletter pour recevoir les mises à jour majeures, les nouveaux drivers et nos coulisses techniques.

