Aller au contenu principal

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

RouteMéthodeContrôleurRequestactor_idAutorisation ajoutée
/api/v1/ai/publication/generatePOSTAIController::generatePublicationGeneratePublicationRequestbody nullablemanage_publications
/api/v1/ai/publication/improvePOSTAIController::improvePublicationImprovePublicationRequestbody nullablemanage_publications
/api/v1/ai/publication/variantsPOSTAIController::generateVariantsGenerateVariantsRequestbody nullablemanage_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_workspace et dmv_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_acteurs seule.

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_publications refusé;
  • collaborateur inactif refusé;
  • utilisateur sans acteur refusé;
  • projection legacy user_acteurs seule 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.php ne reconstruit pas à elle seule le schéma actuellement attendu par le code;
  • AIQuotaService::resolvePlanLimit() lit actor_subscriptions.started_at;
  • la migration corrective 000065_add_missing_columns_to_actor_subscriptions_table.php s’exécute avant la migration de création 0000_00_00_000014 dans l’ordre Laravel actuel;
  • elle peut donc no-op sur une base fraîche si actor_subscriptions n’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 ActorAccessService dans AIController;
  • 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_id via ActorAccessService;
  • 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.