6.2 KiB
SEC-18 — Droits payants conservés sur un abonnement inactif
Gravité : Moyenne, avant correction.
État au 14 septembre 2026 : Correctif local non commité au début de cette rédaction ; déploiement inconnu.
Executive Summary
Un utilisateur d'une organisation dont l'abonnement est passé dans un état sans droits conserve une offre facturée GOLD ou PLATINIUM en base. Il utilise une fonctionnalité payante ou une clé API déjà créée. Il n'a pas besoin de modifier Stripe ni de falsifier son rôle.
Le commit c09b8be contient encore le comportement vulnérable ; le correctif examiné est dans les modifications locales du 14 septembre. Aucun tag local ni version de production vérifiée ne permet d'annoncer une première release affectée ou une release déployée corrigée. La validation combine relecture du source et tests locaux documentés ; aucun incident réel n'est affirmé.
Background
La frontière de sécurité est celle décrite par les prérequis ci-dessus. Le paramétrage fourni par le dépôt ne permet pas de connaître la topologie et les valeurs effectivement en ligne. Les preuves disponibles doivent donc être lues séparément des conditions de déploiement restant à vérifier.
Vulnerability Details
SubscriptionStatus.allowsAccess exclut UNPAID, PAUSED, INCOMPLETE, INCOMPLETE_EXPIRED et CANCELED. Mais Subscription.hasFeature consultait uniquement props.plan ; plusieurs consommateurs lisaient directement subscription.plan pour les quotas et les frais. FeatureFlagGuard acceptait aussi un tableau planFeatures présent sur request.user avant la lecture en base. Le JwtStrategy HTTP courant ne transporte pas nécessairement ces claims, tandis que d'autres contextes d'authentification peuvent en disposer : la branche de confiance de claims est un défaut défensif confirmé, pas une preuve que toute requête JWT normale exploite ce raccourci. La branche DB était elle-même insuffisante puisqu'elle ignorait le statut. L'API key service, le JWT émis, l'aperçu et le résolveur MCP partageaient cette confusion entre offre facturée et droits actuels.
Sources courantes, fonctions et tests concernés :
- apps/backend/src/domain/entities/subscription.entity.ts
- apps/backend/src/domain/value-objects/subscription-status.vo.ts
- apps/backend/src/application/guards/feature-flag.guard.ts
- apps/backend/src/application/api-keys/api-keys.service.ts
- apps/backend/src/application/auth/auth.service.ts
- apps/backend/src/application/mcp/mcp.controller.ts
- apps/backend/src/application/guards/subscription-access.security.spec.ts
- apps/backend/src/application/api-keys/api-keys-entitlement.security.spec.ts
- apps/backend/src/application/mcp/mcp-entitlement.security.spec.ts
- apps/backend/src/application/controllers/csv-bookings.security.spec.ts
Pour comparer au snapshot vulnérable, consulter ces mêmes chemins dans la révision citée, sans supposer que les numéros de lignes actuels correspondent à l'ancienne version.
Exploitability Analysis
Conservation de fonctionnalités, clés API et avantages de quota/frais au-delà de l'état qui doit les autoriser. La frontière est celle de l'abonnement de sa propre organisation ; aucune élévation de rôle ou lecture inter-tenant n'est nécessaire. Le catalogue MCP actuel n'impose pas de fonctionnalité payante à ses outils : son défaut porte sur la résolution/annonce de l'offre et la cohérence du futur contrôle, pas sur un outil premium identifié et exploité aujourd'hui.
Le problème ne requiert pas de supprimer les contrôles métier ou cryptographiques voisins. Il exploite précisément la différence entre le contrôle attendu et celui effectivement exécuté. Les contre-exemples ci-dessous précisent ce que les tests isolent ; ils ne constituent pas un test de pénétration du site en ligne.
Proof of Concept
Avant correction, la matrice domaine/garde présentait 12 refus attendus qui échouaient et 7 contrôles légitimes réussis. Les 19 passent ensuite. Huit tests API vérifient refus de création et perte d'usage après suspension, ainsi que ACTIVE/TRIALING/PAST_DUE. Six tests MCP vérifient l'offre courante de whoami et l'exception ADMIN. Le quota Bronze après suspension est testé au contrôleur avec la véritable entité. Les dépôts et Stripe restent simulés.
Les artefacts sont déjà dans les fichiers de test liés ci-dessus. Aucun faux journal d'exploitation ni nouvelle commande d'attaque de production n'est fourni. Voir VALIDATION.md pour le périmètre et les résultats consolidés.
Remediation
Subscription.accessPlan conserve l'offre payante seulement pour ACTIVE, TRIALING et PAST_DUE, conformément à la grâce existante ; les autres états donnent Bronze. Le plan de facturation reste persisté. hasFeature, quota et frais utilisent accessPlan ; les consommateurs ont été alignés. Le garde consulte les droits actuels plutôt qu'une déclaration ancienne. Les exceptions ADMIN déjà présentes restent inchangées et aucune exception ADMIN n'a été ajoutée aux clés API. Les allocations de licences possédaient déjà des contrôles isActive distincts.
La présente passe documente ce changement antérieur ; elle n'ajoute aucun correctif applicatif et ne confirme pas son déploiement.
Summary
Le mécanisme décrit est confirmé dans la révision vulnérable citée, et la portée du correctif local est bornée par les tests disponibles. Correctif local non commité au début de cette rédaction ; déploiement inconnu. Les limites de couverture générale sont détaillées dans COUVERTURE.md.