Chez nous, la sécurité n'est pas une couche ajoutée après coup, c'est le cœur de l'architecture. L'isolation des données, la séparation des identités et l'écriture idempotente sont gravées dans la couche la plus basse du code — ce n'est pas un réglage désactivable, mais le mode de fonctionnement du système.
Isolation au niveau des lignes
Écriture idempotente
Requête paramétrée
Isolation loopback
Isolation des données
Isolation au niveau des lignes — fail-closed
Sur chaque table à portée locataire, la Row-Level Security FORCE de PostgreSQL est activée ; la politique lie chaque ligne à la condition « tenant_id = current_setting('app.current_tenant') ».
Une requête arrivant sans que le contexte de locataire soit défini renvoie zéro ligne — pas même une erreur, silencieusement vide. Voir les données d'un cabinet avec la session d'un autre cabinet est architecturalement impossible.
Le test d'isolation inter-locataires est obligatoirement au vert en intégration continue (CI) ; s'il est au rouge, le code n'est pas fusionné.
Écriture atomique
Identité locale à la transaction, opération atomique
L'identité du locataire est définie TOUT AU DÉBUT de la transaction lors de chaque écriture et n'est valable que le temps de cette transaction ; elle ne persiste pas dans le pool de connexions et ne fuit vers aucune autre requête.
Les opérations multi-entités (facture + ligne + paiement + décrémentation de stock) sont atomiques au sein d'une seule transaction : soit tout est écrit ensemble, soit rien. Aucun enregistrement inachevé ou incohérent ne se crée.
Habilitation
L'habilitation basée sur les rôles (RBAC) est appliquée sur le serveur
Le contrôle des habilitations est réalisé au niveau de la couche API ; on ne fait jamais confiance au client ni au frontend. Masquer un menu dans le navigateur ne concerne que l'apparence — la vraie décision est sur le serveur.
Les mutations du cœur (Phase 1) sont protégées selon les rôles admin, secrétaire et praticien. Même si un rôle non autorisé appelle le point d'accès, la requête est refusée.
Protection contre la répétition
Écriture idempotente — pas de double opération
Chaque requête d'écriture porte une Idempotency-Key ; le verrou côté serveur bloque toute seconde opération arrivant avec la même clé.
Même en cas de coupure réseau ou si le client renvoie la même requête, aucun second encaissement ni aucune seconde facture n'est créé. Le résultat est toujours unique et prévisible.
Sécurité des requêtes
Injection SQL impossible — requête paramétrée
Toutes les requêtes de base de données sont des instructions entièrement paramétrées, générées à la compilation par sqlc. Il n'existe pas une seule ligne dans le code qui construise du SQL en concaténant manuellement des chaînes avec des entrées utilisateur.
Cela élimine structurellement la classe d'attaques par injection SQL — non par un effort de « filtrage » ou d'« échappement », mais par la façon même dont la requête est construite.
Surface réseau
Isolation loopback — le cœur fermé vers l'extérieur
L'API centrale n'écoute que sur 127.0.0.1 (loopback). Toute tentative de liaison à 0.0.0.0 ou à une IP publique est rejetée au démarrage ; le cœur n'est en aucun cas exposé à Internet.
L'unique surface publique est une fine passerelle (BFF) : hormis la session, le rendu et le CSRF, elle ne détient aucune règle métier. Le navigateur ne peut jamais voir ni adresser directement l'API centrale.
Identité & session
Mot de passe argon2id, clé de session opaque
Les mots de passe sont hachés avec argon2id, standard du secteur et résistant en mémoire ; aucun hachage en clair ou faible n'est utilisé.
Les clés de session sont des valeurs opaques cryptographiquement aléatoires de 32 octets, conservées dans Redis et expirant par TTL. Le navigateur ne reçoit qu'un cookie HttpOnly + Secure + SameSite ; la clé n'est jamais exposée au JavaScript.
Audit
Journal d'audit inaltérable (append-only)
Les opérations importantes sont écrites dans le journal d'audit append-only au sein de la MÊME transaction que la mutation qui les déclenche. Si l'opération est annulée, l'entrée d'audit ne l'est pas non plus — les deux sont inséparables.
Le droit UPDATE/DELETE a été retiré sur la table d'audit ; les enregistrements ne peuvent être ni modifiés ni supprimés a posteriori. Le passé demeure intact.
Confidentialité
Masquage des données sensibles
Le numéro TCKN, le mot de passe, la clé de session et les données de santé n'entrent en aucune façon dans les journaux. Même dans les sorties de débogage, ces champs sont masqués.
Au besoin, les champs sensibles sont chiffrés au niveau colonne. Les données de santé à caractère particulier sont protégées par des règles intégrées dès la conception.
Intégrité monétaire
Sécurité monétaire — numeric, écriture inverse
L'argent est partout conservé en numeric(14,2) ; aucun calcul monétaire n'est fait en float. L'arrondi se produit en un seul point, sur le serveur — aucun décalage de centime ni écart d'arrondi ne se produit.
L'annulation et le remboursement sont traités comme une écriture inverse plutôt qu'en supprimant l'enregistrement. Le solde du compte n'est pas saisi à la main ; il est toujours dérivé de la somme des factures et des paiements. Le passé n'est pas altéré.
Transfert
Transport & sécurité du navigateur
Tout le trafic est acheminé via HTTPS/TLS et HSTS est actif ; le navigateur n'établit la connexion que de façon chiffrée.
Une politique de sécurité du contenu (CSP) stricte bloque les scripts inline ; X-Frame-Options DENY empêche le clickjacking, X-Content-Type-Options nosniff empêche la confusion de type. Referrer-Policy et Permissions-Policy sont également appliquées.
Important
Séparation des tiers d'identité + plan d'administration distinct
L'utilisateur de la clinique, le portail patient et l'opérateur de la plateforme constituent des niveaux d'identité DISTINCTS : en-tête séparé, espace de session séparé. La clé d'un niveau ne fonctionne pas dans un autre — les frontières d'autorisation sont strictement séparées.
Les points d'accès de plateforme et d'administration (provisionnement des locataires, facturation, gestion de l'automatisation/des extensions, génération de clés API) NE FIGURENT PAS dans l'API publique.
Ces fonctions s'exécutent dans un plan de contrôle distinct, interne, doté d'un schéma d'identité différent (clé d'administration dédiée) ; il est inaccessible et indétectable de l'extérieur. La surface d'attaque s'en trouve nettement réduite.
COMMENT ÇA MARCHE
Transaction unique, isolation fail-closed
Le flux conceptuel ci-dessous montre comment chaque écriture est verrouillée sur le locataire et demeure idempotente. Il n'est fourni qu'à titre illustratif.
# 1) Le contexte du locataire est établi TOUT AU DÉBUT de la transaction (propre à cette seule tx)
BEGIN;
SELECT set_config('app.current_tenant', :session_tenant_id, true);
# 2) Verrou Idempotency-Key — aucune seconde opération n'est créée avec la même cléSELECT lock_idempotency(:idempotency_key);
# 3) Écriture multi-entités — le tout atomique dans une seule txINSERT INTO invoices ...; # RLS: tenant_id couvert automatiquementINSERT INTO payments ...;
INSERT INTO audit_log ...; # append-only, même tx
COMMIT;
# Si le contexte n'est pas défini, la politique RLS ne renvoie aucune ligne :SELECT * FROM patients; --> 0 ligne (pas une erreur, silencieusement vide)
Vérifiable de façon indépendante
Une partie de la sécurité du transport est observable de l'extérieur : vous pouvez vous-même inspecter, depuis les outils de développement de votre navigateur, le certificat HTTPS/TLS de votre connexion ainsi que les en-têtes de sécurité de réponse tels que HSTS et la politique de sécurité du contenu. Nos affirmations reposent sur des contrôles architecturaux, non sur des phrases marketing.
Une question sur l'architecture de sécurité ?
Notre équipe se fera un plaisir d'échanger avec vous sur les détails techniques et vos exigences de conformité.