Déclaration d’incident
Politiques d’accès ouvertes sur la base de production
Cette page ne publie que des faits datés : ce qui était ouvert, depuis quand ce n’est pas connu, quand cela a été fermé, ce qui reste ouvert, et ce que Kontia ne sait pas.
Ce qui était ouvert
55 politiques PostgreSQL « PERMISSIVE FOR ALL TO public USING (true) » étaient posées sur 55 tables du schéma public de la base de production, dont le journal d’audit (audit_logs), les cabinets (tenants), les profils et les permissions des utilisateurs, les jetons Microsoft, les écritures validées, les notes de frais et la facturation.
Les rôles anon et authenticated — ceux que porte la clé publique embarquée dans le navigateur — détenaient SELECT, INSERT, UPDATE et DELETE sur 112 tables du schéma public. Une politique permissive sans filtre annule toutes les autres politiques de la même table : le cloisonnement par cabinet ne s’appliquait pas sur ces 55 tables.
Une politique de lecture sans filtre de seau couvrait la totalité de storage.objects, seaux privés compris (mission-files, entrees-moteur), et une politique d’envoi de fichier sans filtre était ouverte aux comptes authentifiés.
Conséquence, telle qu’elle se mesure au catalogue Postgres : avec la clé publique du navigateur et sans compte Kontia, ces tables se lisaient et s’écrivaient directement par l’interface PostgREST, hors de toute route serveur et donc hors de tout contrôle de rôle.
Depuis quand : la fenêtre d’exposition ne peut pas être bornée
Aucune de ces politiques ne vient d’un fichier du dépôt : elles ont été posées à la main dans l’éditeur SQL de Supabase, au premier âge du produit. PostgreSQL ne conserve pas la date de création d’une politique — le jour où elles ont été posées n’est donc pas établi.
Aucune journalisation des appels directs à l’interface PostgREST n’était en place, et le journal d’audit de Kontia n’enregistre que les actions passées par ses propres routes serveur. La fenêtre d’exposition ne peut pas être bornée : ni sa date de début, ni sa durée ne sont établies.
Chronologie
- Date inconnueLes politiques sans filtre sont posées à la main, hors du dépôt. PostgreSQL n’en conserve pas la date de création.
- 25 septembre 2026, avant 03:08 UTCLa confrontation des politiques déclarées par le dépôt avec celles de la base établit le constat, par lecture du catalogue Postgres.
- 25 septembre 2026, 03:08 UTCLa migration 100 est appliquée sur la base de production. Le registre des migrations en porte l’horodatage.
- 25 septembre 2026, 03:25 UTC135 pages de l’application sont balayées en production sous les politiques fermées, pour établir ce que la fermeture a cassé.
- 25 septembre 2026, 04:55 UTCLa migration 102 est appliquée sur la base de production. Elle nomme les droits de storage que le compte des migrations ne peut pas retirer, et pose ce qui les neutralise : RLS active, déclencheurs BEFORE TRUNCATE, droits par défaut retirés.
- 25 septembre 2026, 05:26 UTCPremier passage du gardien des politiques sur la base de production. Depuis, il est programmé tous les jours à 03:20 UTC ; son dernier passage réel est publié par /api/health (champ rls.dernierPassage), et c’est cette valeur qui fait foi, pas l’horaire programmé.
La fermeture
La migration 100 (100_rls_fermeture_politiques_ouvertes.sql) a été appliquée sur la base de production le 25 septembre 2026 à 03:08 UTC, heure inscrite au registre des migrations. Elle supprime les politiques sans filtre nom par nom, active la RLS sur les tables concernées, retire au rôle anon tout droit sur le schéma public, et ne rend au rôle authenticated que les commandes justifiées par les requêtes que le navigateur émet réellement — chacune sous une politique cloisonnée par cabinet.
Ce que cette migration ne fait pas : aucune ligne du journal d’audit, du registre des scellements, des contrôles nocturnes ou des décisions n’est modifiée ni supprimée ; aucun déclencheur n’est désactivé ; aucune table n’est vidée. Seules des politiques et des droits changent.
État relevé après application, par lecture du catalogue Postgres : plus aucune politique sans filtre pour un rôle du navigateur, hors la table de doctrine fiscale publique (bofip_articles), et aucun droit du rôle anon sur le schéma public.
Ce qui reste ouvert
Les rôles anon et authenticated détiennent toujours des droits sur storage.objects et storage.buckets, et ces droits ne sont pas retirables avec les accès du dépôt : ces tables appartiennent au rôle supabase_storage_admin, et un REVOKE ne retire que ce qu’a accordé celui qui le joue. La migration 102, appliquée sur la base de production le 25 septembre 2026 à 04:55 UTC, l’écrit et fait ce qui est à sa portée : RLS active sur les deux tables, TRUNCATE refusé par un déclencheur BEFORE TRUNCATE, droits par défaut retirés pour les tables à naître. Elle laissait une politique servir ces droits pour la lecture du seau des logos par le navigateur ; la migration 107 l’a retirée, parce qu’elle rendait la liste des cabinets à quiconque tenait la clé publique : depuis, aucune politique ne sert plus ces droits au navigateur. Ce qui subsiste est recensé par le gardien à chaque passage, sans alerte parce que personne ne peut le retirer ; le nombre exact est publié dans GET /api/health, champ rls.nbDroitsNonRevocables — il n’est pas figé ici, il se lit là.
La clé publique du navigateur a pu être observée pendant que les politiques étaient ouvertes. Sa rotation est une opération à la main, à la charge de l’éditeur ; elle n’est pas faite à ce jour.
Ce que la base porte n’est toujours pas décrit par le dépôt. Le gardien confronte la base à un CONTRAT écrit dans lib/rls-gardien.ts, pas aux fichiers de migration : la comparaison, politique par politique, de ce que le dépôt déclare avec ce que la base porte reste une opération à la main, en lecture seule (scripts/confronter-rls.mjs), et elle relevait au 25 septembre 2026 71 politiques attendues absentes sous leur nom et 30 divergentes. Rejouée le 29 septembre 2026, après réécriture de l’état déclaré (migration 106) depuis le catalogue de la base, elle rend 131 politiques identiques au catalogue, 0 politique attendue absente et 0 politique lisant user_metadata. L’écart du 25 septembre venait des noms déclarés par le dépôt, qui n’étaient plus ceux que la base portait, et non de politiques manquantes ; la seule politique sans filtre de toute la base est la lecture des articles BOFiP par tout compte connecté, une référence publique.
Ce qui surveille ces politiques aujourd’hui
Depuis le 25 septembre 2026, un contrôle automatique programmé chaque jour à 03:20 UTC — son dernier passage réel est publié dans GET /api/health, champ rls.dernierPassage — lit l’état RÉEL de la base dans le catalogue PostgreSQL : politiques permissives sans filtre, droits des rôles du navigateur sur les schémas public et storage, fonctions SECURITY DEFINER sans search_path figé, déclencheurs BEFORE TRUNCATE manquants sur les registres, droits par défaut des tables à naître. Il compare ce relevé au contrat de sécurité fixé par les migrations 100 et suivantes (la version en vigueur est publiée dans GET /api/health, champ rls.contratVersion), journalise chaque passage et envoie un courriel dès qu’un écart apparaît.
Son dernier passage est public : GET /api/health, champ rls — la date du passage, le nombre d’écarts, et l’indication que le passage n’est pas lisible quand il ne l’est pas. Une valeur que la base n’a pas rendue n’y est jamais présentée comme un relevé sans écart.
Ce que ce contrôle ne fait pas : il ne lit pas le dépôt, il ne rejoue aucune migration et il n’empêche rien. Il constate après coup, une fois par jour : une politique posée à la main puis retirée dans l’intervalle ne laisserait aucune trace. Et il ne dit rien de ce qui a pu se passer avant le 25 septembre 2026 — rien ne le journalisait.
Ce que Kontia ne sait pas
Kontia ne sait pas si quelqu’un a lu ou écrit ces tables pendant que les politiques étaient ouvertes. Aucune trace ne permet de l’établir, ni dans un sens ni dans l’autre : l’absence de preuve d’un accès n’est pas la preuve qu’il n’y en a pas eu.
Le journal d’audit lui-même figurait parmi les tables ouvertes, en lecture comme en écriture. Une ligne qui y aurait été ajoutée, modifiée ou retirée avant le scellement ne se distingue pas d’une ligne légitime.
Ce que le scellement établit commence au premier jour scellé publié au centre de confiance : à partir de là, toute modification du journal rompt la chaîne d’empreintes relevée par un tiers. Avant, il n’établit rien.
Notification
La notification aux cabinets est une décision du responsable de traitement ; ce que cette page publie en tient lieu d’information publique.
Les obligations d’information qui pèsent sur chaque cabinet envers ses propres clients relèvent du cabinet : c’est lui le responsable de traitement des données qu’il confie à Kontia.
Questions sur cet incident : dpo@kontia.fr.
Vérifier ces faits par soi-même
Le fichier de la migration est dans le dépôt, sous supabase/migrations/100_rls_fermeture_politiques_ouvertes.sql : il porte le constat, la liste nom par nom des politiques supprimées, et ce qu’il rend au rôle authenticated.
Le script scripts/confronter-rls.mjs compare, en lecture seule, les politiques que le dépôt déclare et celles que la base porte ; docs/migrations-provenance.md décrit sa méthode, ses huit normalisations, et ce qu’il ne prétend pas.
Incident du 25 septembre 2026 : base de données injoignable en accès direct
25 septembre 2026, depuis 09:00 UTC
Depuis 09:00 UTC, le point d’accès direct à la base PostgreSQL (le pooler de connexion Supabase) ne répond plus depuis le poste qui applique les migrations ; Supabase publie de son côté « Partially Degraded Service » (status.supabase.com). L’application, elle, répond : ses routes lisent la base par l’API du fournisseur, qui n’est pas touchée, et /status le montre minute par minute, à côté de l’état que le fournisseur publie de lui-même.
Conséquence : la migration 108 (notes de frais, plans dans app_metadata, RIB du collaborateur) est restée écrite dans le dépôt sans être appliquée pendant l’indisponibilité ; les notes de frais avec justificatif et l’enregistrement du RIB ont été indisponibles sur l’instance de production du déploiement de 7.99.24 (12:45 UTC) à l’application de la migration, et l’écran le disait au lieu d’échouer en silence. /api/health publie les fichiers en attente dans le champ migrations.enAttente, et /status les affiche.
Fin : le point d’accès a répondu de nouveau vers 21:44 UTC ; la migration 108 a été appliquée à 22:30:51 UTC le 25 septembre 2026 (horodatage du registre _kontia_migrations), après correction d’une comparaison uuid/texte refusée à la première tentative (transaction annulée, rien d’appliqué à moitié). Kontia ne connaît pas la cause chez le fournisseur ; la durée constatée depuis ce poste est d’environ douze heures et quarante minutes.
La porte du second facteur a été inerte en production jusqu’à la version 7.99.25
Constaté le 29 septembre 2026 ; corrigé par la version 7.99.25
Le réglage « second facteur obligatoire » du cabinet est ouvert depuis le 25 septembre 2026. Il n’a pourtant jamais fermé la porte en production : `lireReglageMfa` lisait la table `tenant_settings` sous le rôle `authenticated`, celui du navigateur, à qui la migration 106 retire tout droit sur cette table. Chaque lecture échouait donc en 42501 (droit refusé), le code en concluait « réglage indisponible » — l’issue prévue pour une panne de base — et la navigation passait, avec l’en-tête x-kontia-mfa: indisponible pour seul témoin.
Ce que cela impliquait : sur toutes les navigations et pour tous les cabinets, y compris ceux qui avaient réglé « tous », aucun compte n’a jamais été tenu de présenter son second facteur par cette porte. Le réglage était enregistré et affiché comme actif ; il n’était appliqué nulle part. La correction est entrée en version 7.99.25 : le réglage et le rôle métier sont lus côté serveur (lib/middleware-lecture-service.ts), sous la clé de service, filtrés par le cabinet et le compte que porte le jeton signé — jamais par le cookie — et la décision est prise sur ce même jeton.
Ce que Kontia ne sait pas, et le publie : la période exacte pendant laquelle la porte a laissé passer n’est pas bornée autrement que par les dates ci-dessus, car aucun refus n’était journalisé avant la 7.99.25. Et ce chiffre-ci, à zéro, est publié parce qu’il est vrai : à ce jour, 0 cabinet a posé une politique de second facteur. La porte fermée depuis la 7.99.25 n’a donc encore été exercée par personne.
Architecture de sécurité : ce que le code tient, y compris ce qui n’est pas en place
Faits arrêtés au 29/09/2026 ; page mise à jour le 29/09/2026. Cette date est celle du fait daté le plus récent que cette page publie, relue dans ses propres textes : la page est complétée quand un fait change ; elle n’est pas réécrite.