Les plugins sandboxés sont isolés par défaut. Pour faire quoi que ce soit au-delà de la lecture et l’écriture de son propre KV et stockage, un plugin doit déclarer une capacité dans son manifeste. Le pont de la sandbox contrôle chaque API fournie par l’hôte en fonction de ces déclarations — un plugin qui n’a pas déclaré content:read n’obtient pas ctx.content, et un qui n’a pas déclaré network:request n’obtient pas ctx.http.
Cette page couvre ce que chaque capacité accorde, comment la sandbox les applique et ce qui n’est pas applicable.
Déclarer des capacités
Les capacités se trouvent dans emdash-plugin.jsonc, aux côtés de slug et du reste du contrat de confiance :
{
"slug": "plugin-hello",
// ...identité + profil...
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
Ne déclare que ce dont le plugin a réellement besoin. Les déclarations de capacités sont aussi ce que le Marketplace montre aux opérateurs dans le dialogue de consentement — des capacités supplémentaires sont de la friction à l’installation et un signal de sécurité lors des audits.
Référence des capacités
| Capacité | Accorde l’accès à |
|---|---|
content:read | ctx.content.get(), ctx.content.list() |
content:write | ctx.content.create(), ctx.content.update(), ctx.content.delete() (implique content:read) |
taxonomies:read | ctx.taxonomies.getAll(), ctx.taxonomies.getTerms(), ctx.taxonomies.getEntryTerms() |
media:read | ctx.media.get(), ctx.media.list() |
media:write | ctx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (implique media:read) |
network:request | ctx.http.fetch() — restreint à allowedHosts |
network:request:unrestricted | ctx.http.fetch() sans restriction d’hôte (uniquement pour les URLs configurées par l’utilisateur) |
users:read | ctx.users.get(), ctx.users.getByEmail(), ctx.users.list() |
email:send | ctx.email.send() (nécessite un plugin fournisseur d’email configuré) |
hooks.email-transport:register | Permet l’enregistrement du hook exclusif email:deliver (fournisseur de transport) |
hooks.email-events:register | Permet l’enregistrement des hooks email:beforeSend / email:afterSend |
hooks.page-fragments:register | Permet l’enregistrement du hook page:fragments (plugins natifs uniquement) |
Quelques points à noter :
- Implications.
content:writeimplique automatiquementcontent:read;media:writeimpliquemedia:read;network:request:unrestrictedimpliquenetwork:request. Tu n’as pas besoin de lister les deux. - Les taxonomies sont une surface séparée en lecture seule.
taxonomies:readaccorde l’accès aux définitions de taxonomies, leurs termes et les termes assignés à une entrée viactx.taxonomies. C’est indépendant decontent:read— déclare les deux si le plugin lit du contenu et sa classification. Il n’y a pas d’accès en écriture aux taxonomies depuis les plugins. network:request:unrestrictedexiste pour les URLs configurées par l’utilisateur. Un plugin webhook où l’opérateur saisit l’URL de destination doit atteindre des hôtes qui ne sont pas dans le manifeste. Les plugins qui appellent toujours des APIs connues devraient utilisernetwork:request+allowedHosts.email:sendest contrôlé par la configuration, pas seulement par la capacité. Un plugin peut déclareremail:send, maisctx.emailn’est rempli que si un autre plugin a enregistré un transportemail:deliver.
Listes d’hôtes autorisés pour le réseau
Les plugins avec network:request ne peuvent récupérer que les hôtes listés dans allowedHosts. Les caractères génériques sont pris en charge pour les sous-domaines :
"capabilities": ["network:request"],
"allowedHosts": [
"api.example.com", // hôte exact
"*.cdn.example.com" // tout sous-domaine de cdn.example.com
]
Le pont vérifie l’hôte de l’URL de la requête par rapport à la liste autorisée avant de transmettre la requête. Une requête vers un hôte non déclaré lance une exception à l’intérieur du plugin sans jamais quitter la sandbox.
network:request:unrestricted contourne entièrement la vérification de la liste autorisée. C’est destiné aux plugins où l’opérateur configure l’URL de destination au moment de l’exécution (émetteurs de webhook, redirecteurs HTTP génériques). Évite-le pour les plugins où la destination fait partie de la conception du plugin — déclare plutôt network:request avec des hôtes explicites pour que le dialogue de consentement indique aux opérateurs exactement où le plugin appellera.
Ce que la sandbox applique
Lorsqu’un runner de sandbox est actif, le runtime applique :
-
Contrôle par capacités. La factory PluginContext ne remplit
ctx.content,ctx.taxonomies,ctx.media,ctx.http,ctx.users,ctx.emailque si la capacité correspondante est déclarée. Appeler une méthode sur une capacité non déclarée n’est pas possible — il n’y a pas d’objet. -
Portée du stockage et du KV. Chaque opération de stockage et KV est limitée au slug du plugin. Un plugin ne peut pas lire le KV ou les collections de stockage d’un autre, et ne peut accéder qu’aux collections de stockage qu’il a déclarées dans le manifeste.
-
Isolation réseau.
fetch()direct et les autres primitives réseau sont bloqués par le runner. Le seul chemin vers le réseau estctx.http.fetch(), qui passe par la validation d’hôte du pont. -
Pas de bindings de l’hôte. Les plugins sandboxés ne voient pas les variables d’environnement, le système de fichiers ni les bindings de plateforme — même si ton worker hôte les a. Le runtime du plugin est un isolat propre avec uniquement le pont et les capacités déclarées.
-
Limites de ressources. Le runner peut appliquer des limites de CPU, sous-requêtes, temps horloge et mémoire par invocation. Les limites exactes dépendent du runner utilisé ; le runner Cloudflare utilise les limites du Worker Loader de la plateforme (50ms CPU par invocation, 10 sous-requêtes, 30 secondes de temps horloge, ~128Mo de mémoire). Le runner workerd Node.js (
@emdash-cms/sandbox-workerd) applique le temps horloge viaPromise.race; les limites CPU et mémoire sont des fonctionnalités de la plateforme Cloudflare et ne sont pas appliquées par workerd autonome. Les hooks qui dépassent les limites du runner sont annulés ; le timeout de hook EmDash (timeoutdans la configuration du hook) applique en plus un plafond plus strict.
Ce que la sandbox n’applique pas
Certaines choses que le système de capacités ne couvre pas et ne peut pas couvrir :
- Comportement au sein d’une capacité accordée. Un plugin avec
content:writepeut modifier n’importe quel contenu, pas seulement le sien. Les capacités sont à gros grain — elles disent « ce plugin peut écrire du contenu », pas « ce plugin ne peut écrire que le contenu qu’il a créé ». La revue au moment de l’audit est la seule vérification de ce qu’un plugin fait réellement au sein de sa concession. - Confiance de l’opérateur sur Node.js. Si le runner de sandbox configuré signale qu’il n’est pas disponible (pas de Cloudflare Worker Loader, pas de runner côté Node installé, etc.), les plugins
sandboxed: []sont ignorés au démarrage. Tu peux les déplacer versplugins: []pour les exécuter en processus — mais alors il n’y a pas d’isolat V8, pas de limites de ressources, et le plugin peut appelerfetch()directement ou lire les variables d’environnement. Considère cela comme une confiance au niveau natif. - Canaux auxiliaires. La temporisation, la sortie des logs et les données stockées sont visibles pour quiconque a un accès raisonnable à l’environnement hôte. N’utilise pas la sandbox comme frontière de confidentialité contre l’opérateur qui l’exécute.
Consentement aux capacités
Lorsqu’un opérateur installe un plugin sandboxé depuis le Marketplace, EmDash affiche un dialogue de consentement avec les capacités déclarées. Les mises à jour qui ajoutent des capacités — par exemple, un plugin qui ne faisait que lire du contenu et qui veut maintenant faire des requêtes réseau — sont affichées comme un diff de capacités et nécessitent une nouvelle approbation avant que la nouvelle version ne prenne effet.
C’est pourquoi il est important de déclarer des capacités supplémentaires même si tu « pourrais en avoir besoin plus tard ». Elles apparaissent comme de la friction à chaque installation et mise à jour, et les audits de sécurité signalent les plugins qui demandent plus que ce dont ils ont manifestement besoin. Liste exactement ce que le plugin utilise et ajoute de nouvelles capacités dans une vraie version quand le plugin commence réellement à les utiliser.
Validation au moment du build
emdash-plugin bundle et emdash-plugin publish exécutent des vérifications supplémentaires :
- Chaque capacité déclarée doit être dans l’ensemble reconnu (les fautes de frappe font échouer le build).
network:requestnécessite unallowedHostsnon vide ;network:request:unrestrictednécessite qu’il soit vide. Voir la référence du manifeste.- Le
backend.jsempaquetée ne peut pas importer les built-ins Node.js (fs,path,child_process, etc.) — les runtimes de sandbox ne les fournissent pas.
Voir Empaqueter et publier pour la liste complète des vérifications.