Sandbox des plugins

Sur cette page

Les plugins sandboxés s’exécutent dans un runtime isolé fourni par un runner de sandbox. Les installations du marketplace et du registre s’exécutent toujours en sandbox, tout comme les plugins listés sous sandboxed: [] dans l’intégration emdash(). Les plugins listés sous plugins: [] s’exécutent dans le processus serveur et n’utilisent pas le runner.

Le runner dépend de la plateforme de déploiement. Sur Cloudflare Workers, chaque plugin s’exécute en tant que Dynamic Worker créé via le binding Worker Loader. Sur Node.js, le serveur lance workerd, le runtime Workers open-source, en tant que processus enfant et exécute chaque plugin comme un service à l’intérieur. L’option sandboxRunner de emdash() sélectionne le runner. Sans elle, les plugins sous sandboxed: [] ne sont jamais chargés, et un marketplace configuré fait échouer le build avec « Marketplace requires sandboxRunner to be configured ».

Le tableau suivant résume ce que chaque runner nécessite et applique.

Cloudflare WorkersNode.js
sandboxRunnersandbox() de @emdash-cms/cloudflare"@emdash-cms/sandbox-workerd/sandbox"
PrérequisPlan Workers Paid, un binding worker_loaders, PluginBridge exporté depuis le point d’entrée du WorkerLe package workerd
Accès BDLe binding D1 DB, indépendant de l’adaptateur configuréLa base de données configurée
Limites appliquéesTemps CPU, sous-requêtes, wall timeWall time

Cloudflare Workers

Les Dynamic Workers sont disponibles sur le plan Workers Paid. Le runner nécessite le binding et l’export du point d’entrée ci-dessous ; les templates *-cloudflare fournissent les deux.

  1. Ajoutez le binding Worker Loader à wrangler.jsonc. Le runner le lit sous le nom LOADER :

    {
    	"worker_loaders": [
    		{
    			"binding": "LOADER",
    		},
    	],
    }
  2. Exportez PluginBridge depuis le point d’entrée du Worker, et pointez main vers ce fichier. PluginBridge est le point d’entrée par lequel les plugins sandboxés accèdent au contenu, aux médias, au stockage et à l’email ; le runner le cherche dans les exports du module d’entrée :

    import handler, { createScheduledHandler, PluginBridge } from "@emdash-cms/cloudflare/worker";
    
    export { PluginBridge };
    
    export default {
    	...handler,
    	scheduled: createScheduledHandler(),
    } satisfies ExportedHandler;
    {
    	"main": "./src/worker.ts",
    }
  3. Sélectionnez le runner dans l’intégration emdash() :

    import { d1, r2, sandbox } from "@emdash-cms/cloudflare";
    
    emdash({
    	database: d1({ binding: "DB" }),
    	storage: r2({ binding: "MEDIA" }),
    	sandboxRunner: sandbox(),
    });

Node.js

  1. Installez le runner avec workerd, qui est une dépendance peer :

    npm install @emdash-cms/sandbox-workerd workerd

    Le package workerd installe le binaire pour la plateforme courante (Linux, macOS et Windows sur x64 ; Linux et macOS sur arm64) via une dépendance optionnelle. Installez avec les dépendances optionnelles activées, sur la plateforme où le serveur s’exécute. Dans un build Docker multi-stage, exécutez l’installation dans une étape avec la même plateforme que l’étape runtime.

  2. Sélectionnez le runner dans l’intégration emdash() :

    import { sqlite } from "emdash/db";
    
    emdash({
    	database: sqlite({ url: "file:./data/emdash.db" }),
    	sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
    });
  3. Pour le développement, installez miniflare comme dépendance de développement :

    npm install -D miniflare

    Lorsque NODE_ENV est development, ce que astro dev définit, et que miniflare est installé, le runner transmet les plugins à Miniflare, qui gère son propre processus workerd ; la politique de crash ci-dessous ne s’applique pas. astro preview définit NODE_ENV à production et node ./dist/server/entry.mjs le laisse non défini ; les deux utilisent workerd.

Comment le processus workerd fonctionne

EmDash démarre workerd lors de l’initialisation à la première requête vers le site, une fois les plugins sandboxés chargés, et attend jusqu’à 10 secondes que les services de plugins répondent. L’installation ou la mise à jour d’un plugin depuis l’admin le redémarre. Tout ce que workerd écrit sur stdout ou stderr apparaît dans la sortie du serveur avec le préfixe [emdash:workerd].

Les services de plugins écoutent sur 127.0.0.1, et le canal retour vers le serveur est un socket de domaine Unix (un port TCP 127.0.0.1 sous Windows). Aucun port entrant n’a besoin d’être ouvert.

Le processus enfant ne reçoit que PATH, HOME, TMPDIR, TMP, TEMP, LANG et LC_ALL de l’environnement du serveur, de sorte que les secrets dans l’environnement du serveur restent hors du sandbox. Pour passer plus de variables, définissez EMDASH_WORKERD_PASSTHROUGH_ENV avec une liste de noms de variables séparés par des virgules.

Si workerd se termine de manière inattendue, le runner journalise [emdash:workerd] workerd exited with <reason> et le redémarre à la prochaine invocation, avec un délai qui commence à 1 seconde et double jusqu’à 30 secondes. Lorsque workerd plante plus de cinq fois en 60 secondes, le runner arrête de le redémarrer et journalise [emdash:workerd] workerd crashed 5 times in 60 seconds, giving up. À partir de là, chaque hook et route de plugin sandboxé échoue avec Plugin sandbox unavailable for <plugin>: workerd is not running jusqu’au redémarrage du serveur. Un SIGTERM envoyé au serveur termine workerd avec lui.

Limites de ressources

Chaque runner applique le même ensemble de limites par invocation de plugin. Les limites sont fixes ; l’intégration emdash() n’a pas d’option pour les modifier.

LimiteValeurCloudflare WorkersNode.js
Temps CPU50 msAppliqué par le Worker Loader ; le plugin lève une erreur à la limiteNon appliqué
Sous-requêtes10Appliqué par le Worker Loader ; le plugin lève une erreur à la limiteNon appliqué
Mémoire128 MoNon appliqué par plugin ; le plafond mémoire des isolats de la plateforme s’appliqueNon appliqué
Wall time30 sAppliqué par le runnerAppliqué par le runner

Lorsqu’un hook ou une route dépasse la limite de wall-time, l’invocation échoue avec Plugin <id> exceeded wall-time limit of 30000ms during hook:<name> (ou route:<name>). Pour un hook, EmDash journalise l’échec avec le préfixe EmDash: Sandboxed plugin <id> et poursuit la requête sans le résultat de ce plugin. Une route de plugin qui dépasse la limite échoue pour son appelant.

Lorsque le runner n’est pas disponible

Un runner configuré peut être indisponible : sur Cloudflare Workers quand le binding worker_loaders ou l’export PluginBridge est manquant, sur Node.js quand workerd n’est pas installé ou que son binaire ne s’exécute pas. EmDash journalise alors l’avertissement suivant au démarrage du runtime :

EmDash: Plugin sandbox is configured but not available on this platform. Sandboxed plugins will not be loaded. If using @emdash-cms/sandbox-workerd/sandbox, ensure workerd is installed.

Les plugins sous sandboxed: [] ne sont pas chargés, les plugins installés du marketplace et du registre ne s’exécutent pas, et une nouvelle installation depuis l’admin échoue avec le code d’erreur SANDBOX_NOT_AVAILABLE. Le reste du site n’est pas affecté.

Exécuter les plugins sandboxés dans le processus

Définissez sandbox: false dans emdash() pour exécuter les plugins sous sandboxed: [] et les plugins installés du marketplace dans le processus serveur, sans isolation ni limites. C’est une option de débogage qui distingue un bug dans un plugin d’un bug dans le sandbox. La configuration suivante désactive le sandbox sur un site Node.js :

emdash({
	sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
	sandbox: false,
});

Sur Cloudflare Workers, le runtime refuse de démarrer avec sandbox: false is not supported in Cloudflare Workers.

Dépannage

Chaque entrée est précédée du message tel que le serveur le journalise, ou du code d’erreur retourné par l’admin.

”Plugin sandbox is configured but not available on this platform”

Sur Cloudflare Workers, vérifiez les deux prérequis : wrangler.jsonc a un binding worker_loaders nommé LOADER, et main pointe vers un fichier qui exporte PluginBridge. Le déploiement du binding nécessite le plan Workers Paid.

Sur Node.js, exécutez le binaire que le runner utilise :

npx workerd --version

Si la commande échoue, workerd est absent de node_modules ou le binaire installé ne s’exécute pas sur cette plateforme. Réinstallez sur la plateforme cible avec les dépendances optionnelles activées.

”workerd failed to start within 10 seconds”

Le processus enfant a démarré, mais ses services de plugins n’ont pas répondu dans les 10 secondes. Les lignes préfixées [emdash:workerd] avant ce message contiennent la sortie de workerd lui-même, y compris les erreurs de configuration et de démarrage. Le runner réessaie à la prochaine invocation.

”workerd crashed 5 times in 60 seconds, giving up”

Le runner a cessé de redémarrer workerd. Les lignes [emdash:workerd] workerd exited with <reason> avant ce message nomment le code de sortie ou le signal de chaque crash. Corrigez la cause, puis redémarrez le serveur.

SANDBOX_NOT_AVAILABLE lors de l’installation d’un plugin

La demande d’installation de l’admin a été refusée parce que le runner est manquant ou indisponible. Configurez le runner pour la plateforme, ou corrigez la cause de l’avertissement de démarrage ci-dessus, et redéployez.