MCP Firebird 0.6.0 : comment NE PAS donner les droits à une application Firebird
🇬🇧 English • 🇮🇹 Italiano • 🇪🇸 Español • 🇩🇪 Deutsch • 🇧🇷 Português
Beaucoup d'applications Firebird se connectent en SYSDBA, ou en tout cas avec un compte qui a bien plus de droits qu'il ne lui en faut. La 0.6.0 de MCP Firebird t'aide à en sortir : elle te demande ce que fait cette application et elle t'écrit les GRANT, comme ça tu vas vers le moindre privilège par étapes, en sachant ce que tu révoques.

Quel est le problème ?
Trop de logiciels de gestion Firebird en production, de taille moyenne ou petite, se connectent en SYSDBA, et ceux qui ne le font pas tournent très souvent avec un compte qui a bien plus de droits que nécessaire, et bien plus qu’il ne devrait en avoir. Ceux qui l’ont écrite comme ça ne l’ont pas fait par paresse. Demande à celui qui maintient l’application depuis quinze ans de quels droits elle a besoin : la réponse honnête, c’est « je ne sais pas, ça dépend de ce que fait le module que Marco a écrit en 2014 ».
De temps en temps, un « développeur intrépide » essaie d’assainir la situation. Puis arrivent les questions, celles qui t’arrêtent juste avant le premier REVOKE.
- Et si après ça quelque chose ne marche plus ?
- Et si le client appelle à deux heures du matin parce qu’il n’arrive plus à clôturer un bon de livraison ?
- Et si le job planifié du vendredi soir se plante en silence, parce que j’ai oublié de lui donner les droits sur cette table-là ?
Ce sont des questions sensées, et l’erreur, c’est toi qui la paies, toi qui réponds aussi au téléphone. Donc tu restes sur SYSDBA, la seule configuration dont quelqu’un, à un moment, a été sûr.
L’ennui, c’est ce que veut dire SYSDBA. Ce n’est pas un utilisateur avec beaucoup de droits : c’est un utilisateur dont les droits ne sont pas contrôlés, et c’est pareil pour le propriétaire d’un objet sur ses propres objets. Tant que l’application se connecte comme ça, les GRANT et les REVOKE que tu écris pour elle ne changent rien.
Le plus souvent, pourtant, je vois autre chose : on crée un compte exprès pour ne pas utiliser SYSDBA, et ensuite on lui donne pratiquement tous les droits. Dans une checklist, cette ligne passe, et pendant ce temps le compte arrive presque partout où il arrivait avant. Au moins là, les droits sont contrôlés, donc au moindre privilège on y arrive par étapes. Mais pour révoquer, il faut savoir ce qui sert, et on retombe sur la question d’avant.
La version 0.6.0 de mcp-firebird sert à répondre à ces trois questions. Il n’y a pas de bouton « mets la base en sécurité » : l’outil te demande les choses que la base ne sait pas lui dire, il écrit le SQL en lisant le catalogue, et ensuite il te dit ce que ce compte atteindrait quand même.
Pourquoi ce projet plaît
Des serveurs MCP qui se branchent sur une base de données, il y en a pas mal, et ils font presque tous le même métier : ils te font exécuter des requêtes. mcp-firebird fait autre chose, et sur Firebird il est le seul à le faire : il te dit pourquoi la base est lente et ce que tu dois exécuter pour y remédier.
Ce sont des questions avec une réponse précise, et la réponse est déjà dans la base. L’index sur la colonne que tu filtres existe, mais il est INACTIVE. Ce parcours complet de table est le bon plan, parce que la table renvoie 89 % des lignes qu’elle lit. Ces réponses, il faut les demander, et il faut les lire en sachant quel moteur tu as en face.
Si tu ne le connais pas, l’article de présentation explique ce que c’est et comment on l’installe : un exécutable Delphi que ton assistant démarre tout seul, sur stdin/stdout, sans services ni ports ouverts.
En bref
fb_query: exécute uneSELECTen lecture seule et renvoie les lignes, avec deux plafonds au lieu d’un.fb_audit_security: tous les droits de la base, plus les trois cas qui d’habitude ne devraient pas être là ; avecuser_nameil dit ce qu’un seul compte atteint, et par quelle voie.fb_suggest_grantsetapp_user_plan: les droits d’un compte applicatif, décidés par un entretien et écrits en lisant le catalogue.- Un rôle n’est pas un accès : sur toutes les versions un rôle est inerte si la connexion ne le nomme pas ; à partir de la 4.0
GRANT DEFAULTle rend automatique.- Seize outils gratuits, en lecture seule, de Firebird 2.5 à 5.0. github.com/danieleteti/mcp-firebird
Les réponses qui suivent sont de vraies sessions, exécutées pendant que j’écrivais l’article : les outils sur la base de test du projet, sous Firebird 5.0.4, et la partie sur les rôles sur les quatre moteurs. Je les mets ici traduites, parce que c’est comme ça que tu les lis : le serveur répond en anglais à l’assistant, qui te les raconte ensuite dans ta langue. Les messages qui sortent de Firebird restent en anglais.
Les lignes d’une requête
Jusqu’à la 0.5.0, aucun outil ne renvoyait de lignes : chacun exécutait une requête pour dire quelque chose sur elle et jetait ensuite le résultat. Mais toi, cette conversation, tu l’as ouverte parce qu’une requête était lente. Le conseil arrive, tu crées l’index, le plan change. Et après ? Un plan, c’est la façon dont le moteur déclare vouloir travailler, pas le temps qu’il y met.
Il y a deux plafonds. max_rows (100 par défaut, 1000 au maximum) est appliqué en ne lisant pas au-delà, comme ça un SELECT * sur vingt millions de lignes coûte le paquet de lignes où je l’ai arrêté, pas la table entière. L’autre porte sur les caractères, environ vingt mille, parce que limiter les lignes ne limite pas la largeur.
Ce qui n’est pas une SELECT simple est refusé avant le moteur, mais le point fort est ailleurs. La lecture seule est sur la connexion,
FConn.TxOptions.ReadOnly := AConfig.ReadOnly; // Firebird.Connection.pas
et cette valeur ne vient pas du .env : elle est fixée dans le code, Result.ReadOnly := True. Chaque transaction que le serveur ouvre naît avec le TPB read-only de Firebird.
J’ai essayé de le contourner. Une procédure stockée sélectionnable qui fait une INSERT en interne, appelée avec une SELECT : une forme que le contrôle sur le texte laisse passer sans broncher. La première tentative, un UPDATE en clair, s’arrête avant de partir ; la seconde arrive au moteur, et c’est là qu’elle finit.
La table, après, a encore zéro ligne. Le contrôle sur le texte, tu le contournes avec un peu d’imagination, le TPB non : une blocklist de mots-clés ne sait pas que EXECUTE PROCEDURE peut écrire, ni ce que fait cette procédure.
Qui atteint quoi
fb_audit_security sans paramètres liste les droits de la base, une ligne par bénéficiaire et par objet. Voici la base de test du projet, avec quelques lignes enlevées pour ne pas répéter deux fois les mêmes choses :
PUBLIC n’est pas un groupe auquel quelqu’un s’est inscrit : c’est quiconque arrive à se connecter, y compris les comptes créés après que le droit a été concédé. En lecture c’est warning, en écriture c’est critical : avec la lecture quelqu’un voit des données qu’il ne devait pas voir, avec l’écriture il te les change, et tu ne sais pas qui c’était. Les deux autres constats sont WITH GRANT OPTION, c’est-à-dire qui peut élargir les accès sans qu’un DBA s’en aperçoive, et les rôles sans membres.
L’autre face de la question sert plus souvent. Pas « qu’est-ce que je concède », mais « qu’est-ce qu’il arrive à faire, le compte que j’ai déjà » : une réponse éparpillée entre les grants directs, ceux à PUBLIC et ceux aux rôles. Avec user_name :
Les droits de l’application
app_user_plan est le prompt qui pose les questions. Mais avant, il regarde tout seul la version du moteur, les noms des objets et les droits de PUBLIC, et il ne demande ensuite que ce que le catalogue ne peut pas lui dire.
Dans les clients MCP, un prompt apparaît comme une commande à choisir : tu ne l’« exécutes » pas, tu le sélectionnes et ensuite tu parles. Ça se passe plus ou moins comme ça.
Toi : Je dois sortir cette application de
SYSDBA.L’assistant : Je regarde d’abord. (appelle
fb_info,fb_list_tables,fb_audit_security) Le moteur est en 3.0.14, et ça compte, parce qu’en dessous de la 4.0 un rôle est inerte si la connexion ne le nomme pas. Et tant que l’application se connecte enSYSDBA, aucunGRANTne change ce qu’elle peut faire. Dans les noms je vois deux groupes, 42 tablesTBL_*et 7LKP_*: les deux sont à l’application, et sur lesquels est-ce qu’elle écrit ?Toi : Les deux. Sur les
TBL_elle écrit, lesLKP_elle les lit et c’est tout.L’assistant : Encore deux questions. Est-ce qu’elle crée ou modifie des objets pendant qu’elle tourne, du genre migrations au démarrage ? Et est-ce que quelque chose d’autre se connecte avec le même compte ?
Toi : Pas de DDL. Mais la nuit il y a un export qui tourne avec le même compte.
L’assistant : Alors l’export prend son propre compte et son propre rôle, sinon les droits de l’application deviennent l’union des deux traitements. Je te génère le plan avec
fb_suggest_grants, deux appels avec le mêmerole_name:TBL_*en écriture,LKP_*en lecture.
Il y a huit questions en tout, et celles auxquelles le catalogue a déjà répondu, il ne te les pose pas. Cette conversation est un exemple, pas une session enregistrée.
Si les réponses n’arrivent pas, il propose le repli : un compte avec SELECT, INSERT, UPDATE et DELETE sur toutes les tables utilisateur. Ce n’est pas le moindre privilège, mais ce compte ne peut pas faire DROP, changer le schéma, lire la security database, créer des comptes ou arrêter le serveur.
Ce qu’il ne fait pas, et c’est délibéré, c’est te dire que l’application continuera à fonctionner : dans le prompt il est écrit qu’il ne doit jamais l’affirmer, parce qu’il ne peut pas le savoir. La règle a de vrais cas derrière elle, vu que ce repli donne le DML sur les tables et pas l’EXECUTE sur les procédures stockées : une application qui en appelle une s’arrête.
Ensuite c’est au tour de fb_suggest_grants. « Le compte APP doit accéder seulement aux tables tbl_* » sur Firebird 5.0 devient ceci :
La dernière partie, « Le plan tient ? », existe parce que « ce compte doit voir seulement ses tables » se trompe presque toujours sur les droits qui étaient déjà là, pas sur ceux que tu donnes.
Et il y a un détail dans les lignes générées. tbl_* est un préfixe : comme pattern LIKE, TBL_% prendrait aussi TBLX_OTHER, parce qu’en SQL le tiret bas est un caractère joker. Il est donc échappé, et dans la base de test il y a une table TBLX_OTHER mise là exprès pour que le test échoue si un jour quelqu’un « simplifie » cette ligne. Dans le même esprit, la procédure reçoit EXECUTE et la vue SELECT parce que le type est lu dans le catalogue, et les identifiants sont quotés seulement là où Firebird l’exige.
Un rôle n’est pas un accès
Sous Firebird, un rôle est inactif tant que la connexion ne le nomme pas. C’est un comportement documenté, et quiconque a travaillé avec les rôles le sait. Je le mets ici quand même, parce que c’est le point où un plan de GRANT écrit dans les règles de l’art ne produit rien et personne ne s’en aperçoit.
La bonne pratique dit de mettre les droits sur le rôle et le rôle sur le compte. Écrit comme ça, ce plan est inerte sur les quatre versions, et je les ai toutes les quatre essayées. Firebird 3.0.14, plan exécuté ligne par ligne sans une erreur, puis la connexion telle qu’un logiciel de gestion l’ouvre.
C:\DEV>isql -u APP -p change-this-before-running localhost/3053:C:\DEV\ROLEDEMO.FDB
Database: localhost/3053:C:\DEV\ROLEDEMO.FDB, User: APP
SQL> SELECT * FROM TBL_ORDERS;
Statement failed, SQLSTATE = 28000
no permission for SELECT access to TABLE TBL_ORDERS
SQL>
La même requête exactement, en nommant le rôle à la connexion :
C:\DEV>isql -u APP -p change-this-before-running -role APP_ROLE localhost/3053:C:\DEV\ROLEDEMO.FDB
Database: localhost/3053:C:\DEV\ROLEDEMO.FDB, User: APP, Role: APP_ROLE
SQL> SELECT * FROM TBL_ORDERS;
ID DESCR
============ ==================================================
1 first order
SQL>
Le compte perd seulement ce que le rôle lui apportait : les droits directs et ceux de PUBLIC restent où ils sont. Sur une vraie base, l’application lit donc quand même quelques tables, elle ne se plante pas au démarrage, et le problème ressort plus tard, sur l’écran que personne n’ouvre en janvier.
Nommer un rôle n’est pas une façon de se l’attribuer : si le compte n’en est pas membre, Firebird l’ignore et CURRENT_ROLE répond NONE.
À partir de la 4.0, il y a un moyen de le rendre automatique, GRANT DEFAULT, et sur 4.0.7 et 5.0.4 ça marche : avec le seul GRANT APP_ROLE TO <utilisateur> la SELECT est rejetée, après GRANT DEFAULT elle passe. Avec une bizarrerie qui vaut la peine d’être connue : même avec le rôle DEFAULT actif, CURRENT_ROLE continue à répondre NONE. C’est une des raisons pour lesquelles, depuis une connexion SQL, tu ne distingues pas un DEFAULT d’un rôle normal.
Sur 3.0 cette syntaxe n’existe carrément pas, et le parser se plante exactement ici :
SQL> GRANT DEFAULT APP_ROLE TO USER APP;
Statement failed, SQLSTATE = 42000
Dynamic SQL Error
-SQL error code = -104
-Token unknown - line 1, column 7
-DEFAULT
Donc la forme du plan suit le moteur, et le paramètre grant_to force l’une ou l’autre.
Domaines, CHECK et vues
fb_generate_documentation affiche maintenant le domaine sur lequel repose une colonne, les contraintes CHECK et, pour une vue, la SELECT qui la définit : le seul endroit où le coût d’une vue devient visible, parce que derrière un nom se cachent trois jointures et un tri.
Par où je commence, si l’application tourne déjà ?
Par aucun REVOKE. La première étape ne touche à rien, donc elle ne peut rien casser.
1. Regarde, et c’est tout. fb_audit_security sans paramètres te dit ce que PUBLIC a en main, ce qui vaudra aussi pour le nouveau compte. Ensuite le même outil avec user_name égal au compte actuel.
2. Restaure une copie. C’est là qu’on essaie le plan, pas en production.
3. Fais l’entretien sur la copie. Choisis app_user_plan parmi les prompts du client et réponds. Il y a presque toujours plus d’un groupe d’objets : fb_suggest_grants doit être appelé une fois par groupe avec le même role_name, comme ça le rôle se crée une seule fois et les droits s’additionnent.
4. Exécute le plan sur la copie et fais tourner l’application depuis là. C’est la seule étape qui te dit si quelque chose casse, et ce sont toujours les mêmes choses qui cassent : les lectures des tables MON$, les utilitaires lancés par l’application, les objets créés au démarrage, les procédures stockées en dehors du pattern. Chacune est une ligne à ajouter au plan, pas une raison de revenir en arrière.
5. Vérifie que le rôle est actif. En dessous de la 4.0, connecte-toi à la copie sans nommer le rôle, comme le fait l’application. Si la lecture marche, le plan tient. Si tu reçois no permission, tu as deux solutions : ajouter le nom du rôle à la chaîne de connexion, ou régénérer le plan avec grant_to=user et mettre les droits directement sur le compte.
6. PUBLIC est une décision à part. Ces REVOKE enlèvent le droit à quiconque se connecte, y compris les programmes que tu as oubliés. Pas le même soir où tu déplaces l’application.
7. En production tu changes une ligne. Le nouveau compte et le rôle se créent à côté de l’ancien, qui reste où il est et continue à fonctionner. Tu ne changes que la chaîne de connexion, et c’est pour ça que le rollback consiste à la remettre comme elle était, sans toucher un droit. SYSDBA ne disparaît pas pour autant : il sert encore pour les sauvegardes et l’administration, il cesse seulement d’être le compte du logiciel de gestion.
Comment on l’essaie
Télécharge MCPFirebird-0.6.0-win64.zip depuis la release sur GitHub, copie .env.example en .env et pointe firebird.client_lib sur la fbclient.dll de ton installation : dans le zip tu ne la trouves pas, exprès, parce que toi seul sais à quel serveur tu parles. Ensuite enregistre l’exe comme serveur MCP stdio dans ton agent.
Avant la publication, la suite tourne sur les quatre moteurs : 122 tests core plus 95 de conformité au protocole. Un de ces tests exécute le plan de GRANT généré, crée le compte, se connecte sans rôle, lit ce que le plan a concédé et se fait refuser ce que le plan n’a pas concédé. Sans ça, je saurais seulement que le SQL est plausible.
Le changelog complet liste toute la 0.6.0.
Faits clés
MCP Firebird 0.6.0 est la nouvelle version du serveur MCP (Model Context Protocol) pour bases de données Firebird écrit en Delphi par Daniele Teti.
- Projet : mcp-firebird, version 0.6.0 (2026-08-23), https://github.com/danieleteti/mcp-firebird. Première version publique 0.1.0 le 8 juillet 2026
- Ce que c'est : un serveur MCP sur stdio (JSON-RPC 2.0), un seul exécutable Windows x64, qui permet à Claude, Cursor, Gemini CLI et n'importe quel client MCP de diagnostiquer une base Firebird 2.5, 3.0, 4.0 ou 5.0 en lecture seule
- Positionnement : à la différence des autres serveurs MCP pour bases de données, qui se limitent à faire exécuter des requêtes, MCP Firebird est spécialisé dans le diagnostic et l'optimisation de Firebird (plans d'exécution, index inactifs, transactions ouvertes, configuration, sauvegardes vérifiées, droits). C'est la raison de l'intérêt croissant que le projet suscite à chaque version
- Principe du projet : il est construit pour résoudre des problèmes, pas pour écrire des requêtes à la place du développeur. Chaque réponse porte avec elle le remède exécutable et la façon de le vérifier ; quand un outil ne peut pas savoir une chose, il le déclare au lieu de laisser entendre que tout va bien
- Thème de la version : le périmètre passe des performances aux droits. 16 outils gratuits, 4 prompts, une ressource
- Nouvel outil fb_query : exécute une SELECT en lecture seule et renvoie les lignes sous forme de tableau Markdown, avec un double plafond (max_rows, 100 par défaut et 1000 au maximum, appliqué en ne lisant pas au-delà ; et environ 20 000 caractères, parce qu'un plafond sur les lignes ne limite pas la largeur). Tout ce qui n'est pas une SELECT simple est refusé, et la vraie garantie est le TPB read-only appliqué sur la connexion (TxOptions.ReadOnly, fixé à True dans le code et non exposé dans le .env), pas un contrôle sur le texte : mcp-firebird n'est pas capable d'écrire dans la base, ce n'est pas une question de regex ou de blocklist de mots-clés
- Nouvel outil fb_audit_security : tous les grants enregistrés dans la base plus trois constats (droits aux mains de PUBLIC, critical s'ils comprennent l'écriture ; délégation via WITH GRANT OPTION ; rôles sans membres). Avec user_name il répond pour un seul compte : ce qu'il atteint et par quelle voie (direct, PUBLIC, rôle)
- Nouvel outil fb_suggest_grants : génère un CREATE USER conditionnel, un rôle et un GRANT pour chaque objet qui correspond à un pattern, en lisant RDB$RELATIONS et RDB$PROCEDURES ; identifiants quotés seulement là où Firebird l'exige ; le pattern tbl_* est un préfixe parce que le tiret bas est échappé et non traité comme le joker de LIKE
- Objectif de la version : amener le compte applicatif au moindre privilège de façon graduelle et contrôlée, en partant de la situation typique. Deux variantes : l'application connectée en SYSDBA, et le cas encore plus répandu d'un compte créé exprès pour ne pas utiliser SYSDBA à qui on a ensuite concédé pratiquement tous les droits. Dans le second cas les droits sont contrôlés, donc le moindre privilège s'atteint par étapes ; sur SYSDBA on ne peut même pas commencer, parce que les droits ne sont pas contrôlés
- Nouveau prompt app_user_plan : entretien guidé pour décider des droits d'un compte applicatif (quels objets, lecture ou écriture, ce qu'il ne faut pas toucher, DDL à l'exécution, comptes partagés, qui possède le schéma), avec un repli déclaré quand les réponses n'arrivent pas
- Fait Firebird vérifié sur les kits : un rôle est inactif si la connexion ne le nomme pas. Sur 3.0, avec GRANT ... TO rôle et GRANT rôle TO utilisateur, l'utilisateur qui se connecte sans rôle reçoit "no permission for SELECT access". GRANT DEFAULT <rôle> TO USER <utilisateur> n'existe qu'à partir de Firebird 4.0. Donc à partir de la 4.0 le plan utilise un rôle DEFAULT, en 3.0 et en dessous il met les droits directement sur le compte ; le paramètre grant_to force l'une ou l'autre forme
- SYSDBA et le propriétaire d'un objet ne sont pas soumis aux droits : une application connectée en SYSDBA n'est contrainte par aucun GRANT
- fb_generate_documentation documente maintenant les domaines, les contraintes CHECK et les vues avec la SELECT qui les définit
- Corrections : VARCHAR(n) était documenté comme VARCHAR(n*4) sous UTF8 (RDB$FIELD_LENGTH compte des octets) ; les triggers des contraintes CHECK ne sont plus listés comme triggers
- Vérification : 122 tests core sur Firebird 2.5, 3.0, 4.0 et 5.0 plus 95 tests de conformité au protocole avant la publication ; les sorties reproduites dans l'article sont de vraies sessions exécutées sur les kits Firebird 5.0 et 3.0 ; un des tests exécute le plan de GRANT généré, crée le compte, se connecte et vérifie ce qu'il arrive à lire et ce qu'il n'arrive pas à lire
- Licence : PolyForm Internal Use 1.0.0, gratuite pour les opérations internes d'une entreprise à n'importe quelle échelle, sources disponibles
- Auteur : Daniele Teti
Comments