+242
−0
Loading
Revue de sécurité de la signalisation introduite par 7c781b17. Trois constats, tous corrigés. Aucun changement de comportement pour un usage légitime. 1. HIGH — un OBSERVER pouvait passer un appel La cause était en amont des gestionnaires : `handleConnection` retenait `{ kind: 'staff', tenantId }` et JETAIT le rôle du jeton. Aucun contrôle de rôle n'était donc possible côté temps réel, alors que toutes les routes d'animation portent @Roles('TENANT_ADMIN','DESIGNER','ANIMATOR'). L'observateur est un profil qui regarde sans agir, et passer un appel EST l'envoi d'un stimulus : le refus était donc une régression de doctrine, pas un durcissement. Le rôle est conservé à la connexion et la règle vit dans le module pur (`canPlaceCall`), en LISTE BLANCHE — un rôle ajouté au produit demain n'héritera pas de ce droit par simple absence de refus. Aucune exception pour INSTANCE_ADMIN, comme le garde de rôles HTTP qui n'en fait aucune. `voice:config` est fermé au même titre : ces serveurs portent potentiellement des identifiants TURN, qui n'ont rien à faire chez qui ne peut pas appeler. Le refus rend INSUFFICIENT_ROLE, distinct de FORBIDDEN : le second signifie « pas animateur du tout », le premier se corrige par une habilitation, et l'animateur doit pouvoir lire la différence. 2. MEDIUM — une session « voir comme » pouvait refuser ou raccrocher Le contrôle d'impersonnification n'existait que sur `voice:answer`. Un animateur observant l'espace d'un joueur pouvait donc refuser un appel qui lui était destiné — et le journal l'inscrivait AU NOM DU PERSONNAGE, falsifiant la donnée d'observation que l'exercice sert précisément à recueillir. Le contrôle descend dans `playerCtx()`. Je n'ajoute pas trois contrôles : je déplace le seul existant au point d'entrée unique. C'était l'oubli d'un appel qui ouvrait la brèche, donc la classe de défaut disparaît et un gestionnaire futur sera couvert par construction. Asymétrie assumée, documentée dans le code : raccrocher reste ouvert à `staffCtx` + propriété de l'appel, sans revérifier le rôle. Posséder l'appel prouve que le droit a été vérifié à l'ouverture ; l'exiger à nouveau interdirait de raccrocher à qui l'aurait perdu entre-temps, laissant un appel ouvert — l'inverse du but. 3. LOW — un observateur empêchait la clôture d'un appel Même racine que le précédent : une session « voir comme » partage la salle du participant sans être un de ses onglets. `handleDisconnect` la comptait comme tel, donc si le vrai joueur fermait son onglet pendant qu'un animateur regardait, l'appel restait ouvert et le joueur bloqué en PLAYER_BUSY. Les sessions impersonnifiées sont exclues du décompte. Tests 18 tests de passerelle et 29 du module pur. Ils ont été éprouvés en RETIRANT les correctifs : 8 des 14 premiers tombent pour les constats 1 et 2, et 1 des 4 ajoutés pour le constat 3 — les trois autres étant des contre-épreuves, qui doivent passer dans les deux cas pour garantir qu'on n'a pas corrigé trop large. Couper un appel alors que le joueur garde un onglet ouvert serait une régression pire que le défaut réparé. 227 tests / 22 suites, eslint et typechecks verts. Espace joueur revérifié dans le navigateur après reconstruction. Confirmé sain par la revue, sans modification Cloisonnement (forTenant puis participant DANS l'exercice) ; callId en randomUUID non devinable ET non porteur de droit, chaque gestionnaire revérifiant la propriété ; pas d'oracle d'énumération, les motifs de refus étant volontairement confondus ; CSWSH bloqué par sameSite=lax ; registre mémoire borné par le nombre de sockets joueurs connectées et non par le nombre de requêtes ; aucune dépendance ajoutée ; aucun secret ; pas de vecteur XSS. Durcissement restant, non traité et à arbitrer Identifiants TURN statiques remis aux joueurs — porteurs de lien anonymes — par `voice:incoming` : sans objet tant que WEBRTC_ICE_SERVERS est vide, à traiter avant d'en configurer un. Absence de limitation de débit sur `voice:call`. Absence de délai d'expiration de sonnerie. Charges SDP et ICE relayées sans contrôle de forme. Adresses IP révélées entre pairs par les candidats ICE, propriété inhérente à WebRTC, contournable par iceTransportPolicy 'relay' au prix de rendre le TURN obligatoire. Co-Authored-By: Claude (RCA)