Écrire un plugin EmDash : ce que le bac à sable autorise, et ce qu'il refuse
Les plugins EmDash tournent dans des isolats V8 avec des permissions explicites. Ce que ce choix d'architecture change concrètement quand on développe une extension.
Le modèle de plugins d’EmDash est probablement sa rupture la plus nette avec WordPress. Là où une extension WordPress s’exécute avec les mêmes droits que le cœur du CMS — accès complet à la base, au système de fichiers et au réseau — un plugin EmDash s’exécute dans un isolat V8 et ne dispose que des permissions qui lui ont été explicitement accordées.
C’est une excellente nouvelle pour la sécurité d’un site. C’est aussi une contrainte réelle pour qui développe, et mieux vaut la connaître avant d’écrire la première ligne.
Ce qu’est un isolat, et ce que ça implique
Un isolat V8 est un contexte d’exécution JavaScript indépendant, avec son propre tas mémoire, ses propres objets globaux, et aucune visibilité sur les autres isolats. C’est la même technologie qui fait tourner les fonctions serverless en périphérie, et elle a été retenue pour une raison simple : elle démarre en quelques millisecondes, là où un conteneur en demande des centaines.
La conséquence directe est que l’isolat n’est pas un petit serveur. Il ne dispose pas des choses qu’un processus Node.js a naturellement :
- Pas de système de fichiers. Aucun
fs, aucune lecture ou écriture de fichier local, aucun répertoire temporaire durable. - Pas de modules natifs. Une dépendance qui compile du C++ ne fonctionnera pas. C’est la première mauvaise surprise pour beaucoup de développeurs, et elle exclut des bibliothèques entières.
- Pas de processus enfants. Aucun appel à un binaire externe, aucun traitement délégué à un outil système.
- Pas d’accès réseau libre. Les sorties passent par les permissions déclarées, pas par un socket ouvert à volonté.
Ce ne sont pas des limitations arbitraires : chacune d’elles est précisément ce qui rend un plugin tiers inoffensif pour le reste du site.
Le modèle de permissions
Un plugin déclare ce dont il a besoin, et l’utilisateur qui l’installe voit cette déclaration. C’est l’équivalent des permissions d’une application mobile, appliqué à un CMS.
Deux conséquences pratiques pour l’auteur d’un plugin.
Demandez le minimum. Une déclaration de permissions large est un signal négatif à l’installation. Un plugin qui réclame l’accès à tous les contenus alors qu’il n’agit que sur les médias sera installé moins souvent, et légitimement.
Prévoyez le refus. Une permission peut être refusée ou révoquée après coup. Un plugin qui plante au lieu de se dégrader proprement fera porter la responsabilité de l’échec sur son auteur, pas sur l’utilisateur qui a refusé.
C’est un changement culturel par rapport à l’écosystème WordPress, où un plugin suppose avoir tout et le vérifie rarement.
L’état ne survit pas
Point le plus contre-intuitif pour qui vient d’un serveur classique : une variable globale ne persiste pas de façon fiable entre deux exécutions.
Un isolat peut être recyclé pour plusieurs requêtes successives, ou détruit après une seule. Rien ne le garantit dans un sens ni dans l’autre. En pratique cela signifie :
- Un cache en mémoire fonctionne parfois, et disparaît sans prévenir. Il peut servir d’optimisation, jamais de source de vérité.
- Un compteur stocké dans une variable de module donnera des résultats faux.
- Une file d’attente en mémoire perdra des éléments.
Tout état qui doit survivre doit être écrit dans un stockage prévu pour cela. C’est une discipline à prendre dès la conception, parce que le code qui suppose la persistance fonctionne parfaitement en développement et échoue de façon aléatoire en production — le pire des profils de bug.
Le temps de calcul est compté
Un isolat dispose d’un budget de temps processeur limité par invocation. Un traitement long est interrompu.
Cela exclut un certain nombre de choses qu’un plugin WordPress fait couramment :
- Traiter un import de plusieurs milliers d’entrées en une seule passe.
- Redimensionner une série d’images à la volée dans la même requête.
- Faire tourner une migration de données synchrone.
La réponse architecturale est toujours la même : découper et enchaîner. Un traitement long devient une suite de traitements courts, chacun reprenant où le précédent s’est arrêté, avec l’état intermédiaire dans un stockage persistant. C’est plus de code, et c’est aussi ce qui rend le traitement reprenable après une erreur — un bénéfice qu’on n’obtenait pas avec une boucle monolithique.
À noter : le temps passé à attendre une réponse réseau ne se compte généralement pas de la même façon que le temps de calcul. Un plugin qui interroge une API externe n’est pas pénalisé pour l’attente, seulement pour ce qu’il calcule.
Ce que la contrainte apporte
Il serait facile de ne voir là qu’une liste d’interdictions. Le bilan est plus intéressant que cela.
Un plugin compromis ne compromet pas le site. C’est l’argument central, et il répond à ce qui reste la principale voie d’entrée sur les installations WordPress : une extension abandonnée, vulnérable, disposant de tous les droits.
Les démarrages sont instantanés. Pas de préchauffage, pas de première requête lente.
Le comportement est prévisible. Un plugin qui ne peut pas écrire sur le disque ne laissera pas de fichiers orphelins, et un plugin qui ne peut pas ouvrir un socket ne créera pas de fuite de connexions.
Le débogage est plus simple qu’il n’y paraît. Un plugin qui ne peut agir que sur ce qu’il a déclaré offre une surface d’investigation réduite. Quand quelque chose casse, la liste des causes possibles est courte.
Avant de commencer un plugin
Trois questions à se poser, dans cet ordre.
Mon traitement tient-il en JavaScript pur ? Si la fonctionnalité repose sur une bibliothèque native — traitement d’image bas niveau, cryptographie exotique, parsing binaire spécialisé — il faut trouver un équivalent en WebAssembly ou en JavaScript, ou déporter le traitement vers un service externe appelé par le plugin.
De quel état ai-je réellement besoin entre deux appels ? Répondre à cette question dès le départ évite de découvrir en production que le cache était en fait une base de données.
Quelle est ma plus longue opération ? Si la réponse dépasse quelques centaines de millisecondes de calcul, la découper fait partie de la conception, pas de l’optimisation ultérieure.
Un plugin conçu avec ces trois réponses en tête s’écrit sans heurt. Un plugin porté depuis WordPress sans se les poser rencontrera les trois murs successivement, et généralement dans le désordre.