SEC-P0-001B1 — Blocage IDOR IA sur génération de publications
Statut : rapport d’implémentation Date : 2026-08-08 Périmètre traité : routes IA publication uniquement
1. Objectif
SEC-P0-001B1 corrige le premier sous-chantier applicatif de SEC-P0-001B : empêcher qu’un utilisateur authentifié puisse fournir un actor_id tiers dans les routes IA de génération de publications.
La correction ne modifie pas l’architecture IA globale. Elle ajoute un garde ciblé dans AIController, conformément à SEC-P0-001B0, avant tout appel à AIGatewayService.
2. Routes corrigées
| Route | Méthode | Contrôleur | Request | actor_id | Autorisation ajoutée |
|---|---|---|---|---|---|
/api/v1/ai/publication/generate | POST | AIController::generatePublication | GeneratePublicationRequest | body nullable | manage_publications |
/api/v1/ai/publication/improve | POST | AIController::improvePublication | ImprovePublicationRequest | body nullable | manage_publications |
/api/v1/ai/publication/variants | POST | AIController::generateVariants | GenerateVariantsRequest | body nullable | manage_publications |
Routes volontairement exclues :
/api/v1/ai/publication/tags, car la route ne porte pas d’actor_id;- routes IA service, réservées à SEC-P0-001B2;
- route quota, réservée à SEC-P0-001B3;
- onboarding, réservé à SEC-P0-001B4;
- frontends
dmv_workspaceetdmv_public, hors modification B1.
3. Vulnérabilité initiale
Avant B1, les trois routes acceptaient un actor_id nullable dans le payload validé, puis le transmettaient directement à AIGatewayService.
Le pipeline pouvait donc exécuter, pour un acteur non autorisé :
- résolution ou création de quota acteur;
- lecture de boost assistant;
- appel provider IA;
- écriture cache;
- écriture
ai_usage_logs; - consommation de crédits ou de boost selon le cas.
La seule validation existante portait sur la forme et l’existence de l’identifiant. Elle ne prouvait pas l’ownership métier.
4. Règle d’autorisation
Pour tout actor_id non nul sur les routes IA publication, l’utilisateur authentifié doit satisfaire :
ActorAccessService::hasPermission(auth()->user(), $actorId, 'manage_publications')
Cas autorisés :
- admin global;
- owner de l’acteur;
- collaborateur actif dont le rôle porte
manage_publications.
Cas refusés :
- owner d’un autre acteur;
- utilisateur authentifié sans rattachement acteur;
- collaborateur actif sans
manage_publications; - collaborateur inactif;
- projection legacy
user_acteursseule.
5. Point d’application
Le contrôle est placé dans AIController, immédiatement après validation de la Form Request et avant :
- calcul du flag
noCreditDebit; - appel à
AIGatewayService::execute; - accès quota;
- accès boost;
- accès cache;
- appel provider;
- log d’usage.
Ce positionnement respecte l’option B1 validée par SEC-P0-001B0 : helper local au contrôleur, sans nouveau service transversal ni déplacement de responsabilité dans le gateway.
6. Comportement après correction
Si actor_id est absent ou nul, le comportement historique des routes est conservé.
Si actor_id est fourni :
- un utilisateur autorisé continue d’obtenir la génération IA attendue;
- un utilisateur non autorisé reçoit
403; - aucun effet de bord IA acteur-bound ne se produit avant le refus.
Le message HTTP reste générique : Accès non autorisé à cet acteur.
7. Effets de bord bloqués sur 403
Les tests B1 vérifient explicitement qu’un refus 403 ne déclenche pas :
- requête HTTP provider;
- écriture
ai_usage_logs; - écriture
ai_cache; - mutation de
ai_user_quotas.monthly_used; - mutation de
ai_user_quotas.bonus_credits; - écriture
boost_usages; - consommation ou désactivation de boost.
8. Tests ajoutés
Fichier : api/tests/Feature/AI/AITest.php
Scénarios positifs :
- génération publication autorisée pour owner;
- amélioration publication autorisée pour collaborateur actif avec
manage_publications; - variantes publication autorisées pour admin global.
Scénarios de refus :
- actor A ne peut pas générer pour actor B;
- collaborateur viewer sans
manage_publicationsrefusé; - collaborateur inactif refusé;
- utilisateur sans acteur refusé;
- projection legacy
user_acteursseule refusée.
Chaque scénario de refus vérifie aussi l’absence d’effet de bord quota, boost, cache, log et provider.
9. Dette DB identifiée hors B1
Pendant la validation B1, une dette indépendante a été identifiée sur la reconstruction locale du schéma actor_subscriptions.
Constats :
- la migration initiale
0000_00_00_000014_create_actor_subscriptions_table.phpne reconstruit pas à elle seule le schéma actuellement attendu par le code; AIQuotaService::resolvePlanLimit()litactor_subscriptions.started_at;- la migration corrective
000065_add_missing_columns_to_actor_subscriptions_table.phps’exécute avant la migration de création0000_00_00_000014dans l’ordre Laravel actuel; - elle peut donc no-op sur une base fraîche si
actor_subscriptionsn’existe pas encore; - les schémas locaux/backups disponibles contiennent déjà des colonnes supplémentaires sur
actor_subscriptions.
Cette dette n’est pas corrigée dans SEC-P0-001B1 afin de ne pas embarquer une modification de schéma dans une PR de sécurité IDOR ciblée.
Chantier futur recommandé : DB-001 — Alignement migrations Laravel / schéma réel / base de test.
Les tests positifs B1 isolent donc localement AIGatewayService pour vérifier l’autorisation owner/collaborateur/admin sans dépendre de AIQuotaService ni du schéma historique incomplet. Les tests de refus ne mockent pas ActorAccessService et prouvent que le 403 intervient avant gateway et avant tout effet de bord.
10. Limites explicites
B1 ne traite pas :
- autorisation IA service;
- autorisation IA quota;
- autorisation IA onboarding;
- durcissement du cache IA;
- journalisation sécurité dédiée;
- centralisation d’un guard IA global;
- changements Workspace ou Public.
Ces sujets restent dans les étapes suivantes de SEC-P0-001B.
11. Rollback
Rollback applicatif minimal :
- retirer l’injection de
ActorAccessServicedansAIController; - retirer les appels au helper dans les trois méthodes publication;
- retirer le helper local;
- retirer les tests B1 associés.
Le rollback réouvrirait l’IDOR initial sur les routes IA publication avec actor_id.
12. Définition de Done
B1 est terminé lorsque :
- les trois routes IA publication contrôlent
actor_idviaActorAccessService; - le contrôle est exécuté avant tout effet de bord IA;
- les cas owner, collaborateur autorisé et admin restent verts;
- les cas IDOR et profils non autorisés retournent
403; - les tests prouvent l’absence d’effet de bord sur refus;
publication/tags, IA service, quota et onboarding restent inchangés;- aucune modification frontend, aucun commit et aucune PR ne sont créés pendant la mission.