Le WordPress d'aujourd'hui, décodé pour les développeurs

Sécurité

Transporter une décision d’autorisation entre deux appels d’ability avec un objet readonly

Quand un agent chaîne plusieurs appels d'ability pour une même tâche, la décision d'autorisation initiale peut se retrouver altérée en cours de route. Un objet en lecture seule referme cette faille.

Par Clément Hadrot • 14 janvier 2026 • 4 min de lecture • Aucun commentaire
Transporter une décision d'autorisation entre deux appels d'ability avec un objet readonly

$decision->niveau_autorise = 'ecriture_complete'; — cette ligne ne devrait jamais pouvoir s’exécuter une fois qu’une décision d’autorisation a été calculée pour un agent, et pourtant rien n’empêche techniquement une fonction intermédiaire mal écrite de le faire si cette décision circule sous forme de tableau PHP classique entre plusieurs appels d’ability enchaînés.

Un agent IA qui exécute une tâche complexe appelle rarement une seule ability isolée : il enchaîne souvent plusieurs appels successifs qui partagent un même contexte d’autorisation, calculé une fois au début de la chaîne. Si ce contexte est transporté sous une forme modifiable, une erreur de programmation dans une des étapes intermédiaires peut, par inadvertance ou par exploitation délibérée, élargir la portée de ce qui avait été initialement autorisé.

Le problème du tableau associatif partagé

Une implémentation naïve calcule une décision d’autorisation sous forme de tableau, puis la transmet de fonction en fonction au fil de la chaîne d’appels d’ability :

$decision = [
    'niveau_autorise' => 'lecture_seule',
    'utilisateur_id'  => 42,
    'expire_a'        => time() + 300,
];

executer_premiere_ability($decision);
executer_seconde_ability($decision); // rien n'empêche une modification entre-temps

Rien, dans ce code, n’empêche une fonction intermédiaire de modifier $decision['niveau_autorise'] avant de le transmettre à l’étape suivante. Ce risque reste théorique tant que le code est écrit par une seule équipe attentive, mais il devient bien réel dès qu’une extension tierce ou un module communautaire s’insère dans cette chaîne d’appels.

Remplacer le tableau par une classe readonly

Depuis PHP 8.1, une classe peut déclarer ses propriétés en lecture seule : une fois initialisées dans le constructeur, ces propriétés ne peuvent plus être réassignées, sous peine d’une erreur fatale immédiate.

L'essentiel à retenir : Une décision d'autorisation transmise sous forme de tableau modifiable peut être altérée entre deux appels ; Une classe en lecture seule (readonly, depuis PHP 8.1) garantit l'intégrité de cette décision ; Ce mécanisme ne remplace pas la validation initiale des règles d'autorisation
final class DecisionAutorisationAgent
{
    public function __construct(
        public readonly string $niveau_autorise,
        public readonly int $utilisateur_id,
        public readonly int $expire_a,
    ) {
    }

    public function estExpiree(): bool
    {
        return time() > $this->expire_a;
    }
}

Une tentative de modification d’une propriété readonly après construction déclenche une Error immédiate et explicite, ce qui transforme une altération silencieuse en une panne visible et facile à diagnostiquer, plutôt qu’en un élargissement de portée passé inaperçu.

$decision = new DecisionAutorisationAgent('lecture_seule', 42, time() + 300);

// Cette ligne lève une Error : Cannot modify readonly property
$decision->niveau_autorise = 'ecriture_complete';

Ce que ce mécanisme ne résout pas

Une classe readonly garantit l’intégrité de la décision une fois qu’elle a été calculée ; elle ne garantit en rien que cette décision initiale était elle-même correcte. Le calcul des règles d’autorisation — quel niveau accorder à quel utilisateur, pour quelle durée — reste une logique distincte, à valider séparément, en amont de la construction de cet objet.

Composer avec une nouvelle instance plutôt que muter

Si une étape de la chaîne doit restreindre davantage la décision (par exemple, passer d’un niveau large à un niveau plus restreint pour une sous-tâche spécifique), la bonne pratique consiste à créer une nouvelle instance dérivée, jamais à contourner le caractère readonly par une astuce de réflexion PHP.

function restreindre(DecisionAutorisationAgent $decision): DecisionAutorisationAgent
{
    return new DecisionAutorisationAgent(
        'lecture_seule', // toujours plus restrictif, jamais plus large
        $decision->utilisateur_id,
        $decision->expire_a,
    );
}
  • Une nouvelle instance ne peut que restreindre, jamais élargir, le niveau initial calculé
  • Chaque étape de la chaîne reçoit une décision qu’elle ne peut techniquement pas altérer
  • Une expiration courte, vérifiée à chaque appel via estExpiree(), limite la fenêtre d’exploitation en cas de fuite du contexte

Une décision d’autorisation qui peut être modifiée après son calcul n’est pas vraiment une décision : c’est une suggestion que le reste du code est libre d’ignorer.

En résumé

Transporter une décision d’autorisation sous forme d’objet readonly plutôt que de tableau modifiable ferme une classe de bugs et d’exploitations qui reste invisible tant qu’aucune étape intermédiaire ne cherche à en abuser. Ce mécanisme, disponible nativement depuis PHP 8.1, ne dispense évidemment pas de valider correctement les règles d’autorisation en amont : il garantit seulement que la décision, une fois prise, ne peut plus être détournée en cours de chaîne.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi