Configurar el sandbox de plugins

En esta página

Los plugins en sandbox necesitan un runner de plataforma además de su declaración de plugin. Las instalaciones desde el marketplace y el registro siempre usan ese runner, al igual que los plugins listados bajo sandboxed: []. Los plugins nativos bajo plugins: [] se ejecutan en el proceso del servidor EmDash y no obtienen aislamiento de sandbox.

El runner depende de la plataforma de despliegue. En Cloudflare Workers, cada plugin se ejecuta como un Dynamic Worker creado a través del binding Worker Loader. En Node.js, el servidor inicia workerd, el runtime de Workers de código abierto, como proceso hijo y ejecuta cada plugin como un servicio dentro de él. La opción sandboxRunner de emdash() selecciona el runner y habilita el catálogo del registro alojado. Sin ella, los plugins bajo sandboxed: [] no se cargan. Un registro configurado explícitamente sigue siendo navegable, pero la instalación o actualización de un plugin en sandbox falla con SANDBOX_NOT_AVAILABLE.

La siguiente tabla resume lo que cada runner necesita y aplica.

Cloudflare WorkersNode.js
sandboxRunnersandbox() de @emdash-cms/cloudflare"@emdash-cms/sandbox-workerd/sandbox"
RequisitosPlan Workers Paid, un binding worker_loaders, PluginBridge exportado desde el punto de entrada del WorkerEl paquete workerd
Acceso a BDEl binding D1 DB, independiente del adaptador configuradoLa base de datos configurada
Límites aplicadosTiempo de CPU, subrequests, tiempo de paredTiempo de pared

Cloudflare Workers

Los Dynamic Workers están disponibles en el plan Workers Paid. Las plantillas *-cloudflare incluyen la exportación del punto de entrada a continuación pero dejan el binding comentado, por lo que los nuevos proyectos se despliegan en el plan Workers free a menos que habilites los plugins en sandbox durante el scaffolding.

  1. Habilita el binding Worker Loader en wrangler.jsonc. El runner lo lee bajo el nombre LOADER y selecciona el sandbox de Cloudflare solo cuando este binding está presente:

    {
    	"worker_loaders": [
    		{
    			"binding": "LOADER",
    		},
    	],
    }

    Si la configuración de Wrangler usa entornos con nombre, establece CLOUDFLARE_ENV durante el build de Astro. El plugin Vite de Cloudflare y sandbox() leerán entonces el mismo entorno. Los bindings no se heredan, así que añade LOADER a cada entorno con nombre que ejecute plugins en sandbox.

  2. Exporta PluginBridge desde el punto de entrada del Worker y apunta main a ese archivo. PluginBridge es el punto de entrada a través del cual los plugins en sandbox acceden a contenido, medios, almacenamiento y correo electrónico; el runner lo busca en las exportaciones del módulo de entrada:

    import handler, { createScheduledHandler, PluginBridge } from "@emdash-cms/cloudflare/worker";
    
    export { PluginBridge };
    
    export default {
    	...handler,
    	scheduled: createScheduledHandler(),
    } satisfies ExportedHandler;
    {
    	"main": "./src/worker.ts",
    }
  3. Selecciona el runner en la integración emdash():

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

Node.js

  1. Instala el runner junto con workerd, que es una dependencia peer:

    npm install @emdash-cms/sandbox-workerd workerd

    El paquete workerd instala el binario para la plataforma actual (Linux, macOS y Windows en x64; Linux y macOS en arm64) a través de una dependencia opcional. Instala con las dependencias opcionales habilitadas, en la plataforma donde se ejecutará el servidor. En un build Docker multi-stage, ejecuta la instalación en una etapa con la misma plataforma que la etapa de runtime.

  2. Selecciona el runner en la integración emdash():

    import { sqlite } from "emdash/db";
    
    emdash({
    	database: sqlite({ url: "file:./data/emdash.db" }),
    	sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
    });

El runner declara Miniflare como dependencia opcional. Los gestores de paquetes la instalan por defecto. Cuando NODE_ENV es development, lo que astro dev establece, el runner entrega los plugins a Miniflare, que gestiona su propio proceso workerd; la política de fallos de abajo no se aplica. Si las dependencias opcionales fueron omitidas, el runner usa workerd en su lugar. astro preview establece NODE_ENV a production y node ./dist/server/entry.mjs lo deja sin establecer; ambos usan workerd.

Cómo se ejecuta el proceso workerd

EmDash inicia workerd mientras se inicializa en la primera solicitud al sitio, una vez que los plugins en sandbox están cargados, y espera hasta 10 segundos a que los servicios del plugin respondan. Instalar o actualizar un plugin desde el admin lo reinicia. Todo lo que workerd escribe en stdout o stderr aparece en la salida del servidor con el prefijo [emdash:workerd].

Los servicios de plugin escuchan en 127.0.0.1, y el canal de vuelta al servidor es un socket de dominio Unix (un puerto TCP 127.0.0.1 en Windows). No es necesario abrir ningún puerto entrante.

El proceso hijo recibe solo PATH, HOME, TMPDIR, TMP, TEMP, LANG y LC_ALL del entorno del servidor, por lo que los secretos en el entorno del servidor no llegan al sandbox. Para pasar más variables, establece EMDASH_WORKERD_PASSTHROUGH_ENV a una lista separada por comas de nombres de variables.

Si workerd termina inesperadamente, el runner registra [emdash:workerd] workerd exited with <reason> y lo reinicia en la siguiente invocación, con un retardo que comienza en 1 segundo y se duplica hasta 30 segundos. Cuando workerd falla más de cinco veces en 60 segundos, el runner deja de reiniciarlo y registra [emdash:workerd] workerd crashed 5 times in 60 seconds, giving up. A partir de entonces, cada hook y ruta de plugin en sandbox falla con Plugin sandbox unavailable for <plugin>: workerd crashed 5 times in 60 seconds and the runner stopped retrying; restart the server. Reiniciar el servidor inicia workerd de nuevo, al igual que instalar o actualizar un plugin desde el admin. Un SIGTERM al servidor termina workerd con él.

Límites de recursos

Cada runner aplica el mismo conjunto de límites por invocación de plugin. Los límites son fijos; la integración emdash() no tiene opción para ellos.

LímiteValorCloudflare WorkersNode.js
Tiempo CPU50 msAplicado por el Worker Loader; el plugin lanza excepción al alcanzar el límiteNo aplicado
Subrequests10Aplicado por el Worker Loader; el plugin lanza excepción al alcanzar el límiteNo aplicado
Memoria128 MBNo aplicado por plugin; se aplica el techo de memoria de isolate de la plataformaNo aplicado
Tiempo pared30 sAplicado por el runnerAplicado por el runner

Cuando un hook o ruta excede el límite de tiempo de pared, la invocación falla con Plugin <id> exceeded wall-time limit of 30000ms during hook:<name> (o route:<name>). Para un hook, EmDash registra el fallo con el prefijo EmDash: Sandboxed plugin <id> y continúa la solicitud sin el resultado de ese plugin. Una ruta de plugin que excede el límite falla para quien la llama.

Cuando el runner no está disponible

En Cloudflare Workers, sandbox() verifica wrangler.jsonc en tiempo de build. Sin un binding worker_loaders llamado LOADER, deja el runner sin establecer y registra la siguiente advertencia:

[emdash] Sandboxed plugins are disabled because wrangler.jsonc has no LOADER Worker Loader binding. Worker Loader requires a Workers paid plan.

Un runner seleccionado puede seguir no disponible en tiempo de ejecución: en Cloudflare Workers cuando el binding LOADER desplegado o la exportación PluginBridge faltan, y en Node.js cuando workerd no está instalado o su binario no se ejecuta. EmDash entonces registra una advertencia con la causa que el runner reporta después de los dos puntos. La siguiente advertencia se registra en Cloudflare Workers cuando el binding falta:

EmDash: Plugin sandbox is configured but not available on this platform: the worker has no worker_loaders binding named LOADER. Sandboxed plugins will not be loaded.

Los plugins bajo sandboxed: [] no se cargan, los plugins instalados del marketplace y registro no se ejecutan, y una nueva instalación desde el admin falla con el código de error SANDBOX_NOT_AVAILABLE. El resto del sitio no se ve afectado.

Ejecutar plugins en sandbox dentro del proceso

Establece sandbox: false en emdash() para ejecutar los plugins bajo sandboxed: [] y los plugins instalados del marketplace en el proceso del servidor, sin aislamiento ni límites. Es una opción de depuración que distingue un error en un plugin de un error en el sandbox. La siguiente configuración desactiva el sandbox en un sitio Node.js:

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

En Cloudflare Workers, el runtime se niega a iniciar con sandbox: false is not supported in Cloudflare Workers.

Solución de problemas

Cada entrada está encabezada por el mensaje tal como lo registra el servidor, o por el código de error que devuelve el admin.

”[emdash] Sandboxed plugins are disabled because wrangler.jsonc has no LOADER Worker Loader binding”

El adaptador de Cloudflare no seleccionó un runner de sandbox porque la configuración de Wrangler en tiempo de build no tiene un binding worker_loaders llamado LOADER. Esta es la configuración esperada en el plan Workers free. En un plan Workers paid, habilita el binding en wrangler.jsonc y reconstruye el sitio.

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

El texto después de los dos puntos nombra la causa. En Cloudflare Workers, the worker has no worker_loaders binding named LOADER significa que wrangler.jsonc necesita un binding worker_loaders llamado LOADER, y the worker entrypoint does not export PluginBridge significa que el archivo al que apunta main debe exportar PluginBridge. Desplegar el binding requiere el plan Workers Paid.

En Node.js, workerd is missing or its binary does not run on this platform significa que el runner no pudo ejecutar workerd. Ejecuta el binario instalado directamente para que la verificación no pueda descargar un paquete faltante:

./node_modules/.bin/workerd --version

En Windows, ejecuta node_modules\\.bin\\workerd.cmd --version. Si el comando falla, workerd falta en node_modules o el binario instalado no se ejecuta en esta plataforma. Reinstala en la plataforma de destino con las dependencias opcionales habilitadas.

”workerd failed to start within 10 seconds”

El proceso hijo se inició, pero sus servicios de plugin no respondieron dentro de 10 segundos. Las líneas con prefijo [emdash:workerd] antes de este mensaje contienen la salida de workerd mismo, incluyendo errores de configuración y arranque. El runner lo reintenta en la siguiente invocación.

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

El runner ha dejado de reiniciar workerd. Las líneas [emdash:workerd] workerd exited with <reason> antes de este mensaje nombran el código de salida o señal de cada fallo. Corrige la causa y luego reinicia el servidor.

SANDBOX_NOT_AVAILABLE al instalar un plugin

La solicitud de instalación del admin fue rechazada porque el runner falta o no está disponible. Cuando un runner está configurado, el mensaje de error termina con la misma causa que la advertencia de inicio anterior. Configura el runner para la plataforma, o corrige esa causa, y redespliega.