Les plugins sandboxed sont isolés par défaut. Pour faire quoi que ce soit au-delà de la lecture et de l’écriture de leur propre KV et storage, un plugin doit déclarer une capability dans son manifest. Le pont 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 de ctx.content, et un qui n’a pas déclaré network:request n’obtient pas de ctx.http.
Cette page couvre ce que chaque capability accorde, comment la sandbox les applique et ce qui n’est pas applicable.
Déclarer les capabilities
Les capabilities se trouvent dans emdash-plugin.jsonc, aux côtés de slug et du reste du contrat de confiance :
{
"slug": "plugin-hello",
// ...identity + profile...
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
Déclarez uniquement ce dont le plugin a réellement besoin. Les déclarations de capabilities sont également ce que le marketplace montre aux opérateurs de site dans le dialogue de consentement — les capabilities supplémentaires sont une friction au moment de l’installation et un signal de sécurité lors des audits.
Référence des capabilities
| Capability | 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) |
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 d’enregistrer le hook exclusif email:deliver (fournisseurs de transport) |
hooks.email-events:register | Permet d’enregistrer les hooks email:beforeSend / email:afterSend |
hooks.page-fragments:register | Permet d’enregistrer le hook page:fragments (uniquement plugins natifs) |
Quelques points à savoir :
- Implications.
content:writeimplique automatiquementcontent:read;media:writeimpliquemedia:read;network:request:unrestrictedimpliquenetwork:request. Vous n’avez pas besoin de lister les deux. 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 manifest. Les plugins qui appellent toujours des APIs connues devraient utilisernetwork:request+allowedHosts.email:sendest contrôlé par la configuration, pas seulement par la capability. Un plugin peut déclareremail:send, maisctx.emailne sera rempli que si un autre plugin a enregistré un transportemail:deliver.
Listes d’hôtes réseau autorisés
Les plugins avec network:request ne peuvent récupérer que les hôtes listés dans allowedHosts. Les wildcards 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 qui n’a pas été déclaré lance une erreur à l’intérieur du plugin sans jamais quitter la sandbox.
network:request:unrestricted saute complètement la vérification de la liste autorisée. Il est destiné aux plugins où l’opérateur configure l’URL de destination à l’exécution (expéditeurs de webhook, redirecteurs HTTP génériques). Évitez-le pour les plugins où la destination fait partie de la conception du plugin — déclarez plutôt network:request avec des hôtes explicites, pour que le dialogue de consentement indique aux opérateurs exactement où le plugin va appeler.
Ce que la sandbox applique
Lorsqu’un exécuteur sandbox est actif, le runtime applique :
-
Contrôle des capabilities. La factory PluginContext ne remplit que
ctx.content,ctx.media,ctx.http,ctx.users,ctx.emaillorsque la capability correspondante est déclarée. Appeler une méthode sur une capability non déclarée n’est pas possible — il n’y a pas d’objet là. -
Portée du storage et du KV. Chaque opération de storage et KV est limitée au slug du plugin. Un plugin ne peut pas lire le KV ou les collections de storage d’un autre plugin, et il ne peut accéder qu’aux collections de storage qu’il a déclarées dans le manifest.
-
Isolation réseau. Le
fetch()direct et autres primitives réseau sont bloqués par l’exécuteur. Le seul moyen d’atteindre le réseau estctx.http.fetch(), qui passe par la validation d’hôte du pont. -
Pas de liaisons hôte. Les plugins sandboxed ne voient pas les variables d’environnement, le système de fichiers ou les liaisons de plateforme — même si votre worker hôte les a. Le runtime du plugin est un isolat propre avec seulement le pont et les capabilities déclarées.
-
Limites de ressources. L’exécuteur peut appliquer des limites de CPU, subrequest, wall-clock et mémoire par invocation. Les limites exactes dépendent de l’exécuteur que vous utilisez ; l’exécuteur Cloudflare utilise les limites du Worker Loader de la plateforme (50ms CPU par invocation, 10 subrequests, 30 secondes wall-clock, ~128MB mémoire). Les hooks qui dépassent les limites de l’exécuteur sont interrompus ; le timeout de hook EmDash (
timeoutdans la config du hook) applique un plafond plus strict en plus de cela.
Ce que la sandbox n’applique pas
Quelques points que le système de capabilities ne couvre pas et ne peut pas couvrir :
- Comportement au sein d’une capability accordée. Un plugin avec
content:writepeut éditer n’importe quel contenu, pas seulement le sien. Les capabilities sont grossières — elles disent “ce plugin peut écrire du contenu”, pas “ce plugin ne peut écrire que le contenu qu’il a créé”. L’examen au moment de l’audit est la seule vérification sur ce qu’un plugin fait réellement dans le cadre de son autorisation. - Confiance de l’opérateur sur Node.js. Lorsque l’exécuteur sandbox configuré signale qu’il n’est pas disponible (pas de Cloudflare Worker Loader, pas d’exécuteur côté Node installé, etc.), les plugins
sandboxed: []sont ignorés au démarrage. Vous pouvez les déplacer dansplugins: []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. Traitez cela comme une confiance de niveau natif. - Canaux secondaires. Le timing, la sortie de log et les données stockées sont tous visibles par quiconque ayant un accès approprié à l’environnement hôte. N’utilisez pas la sandbox comme une limite de confidentialité contre l’opérateur qui l’exécute.
Consentement des capabilities
Lorsqu’un opérateur installe un plugin sandboxed depuis le marketplace, EmDash affiche un dialogue de consentement listant les capabilities déclarées. Les mises à jour qui ajoutent des capabilities — par exemple, un plugin qui ne lisait auparavant que du contenu veut maintenant faire des requêtes réseau — apparaissent comme une différence de capabilities et nécessitent une nouvelle approbation avant que la nouvelle version ne prenne effet.
C’est pourquoi déclarer des capabilities supplémentaires compte même si vous “pourriez les utiliser plus tard”. Elles apparaissent comme une friction à chaque installation et mise à jour, et les audits de sécurité signalent les plugins qui demandent plus qu’ils n’en ont évidemment besoin. Listez exactement ce que le plugin utilise, et ajoutez de nouvelles capabilities dans une vraie version lorsque le plugin commence réellement à les utiliser.
Validation au moment du bundle
emdash-plugin bundle et emdash-plugin publish effectuent des vérifications supplémentaires :
- Chaque capability déclarée doit être dans l’ensemble reconnu (les fautes de frappe font échouer la construction).
network:requestnécessite unallowedHostsnon vide ;network:request:unrestrictednécessite qu’il soit vide. Voir la référence du manifest.- Le
backend.jsempaqueté ne peut pas importer de built-ins Node.js (fs,path,child_process, etc.) — les runtimes sandbox ne les fournissent pas.
Voir Bundling and publishing pour la liste complète des vérifications.