Sandbox de plugins

Nesta página

Plugins sandboxed são executados em um runtime isolado fornecido por um runner de sandbox. Instalações do marketplace e do registro sempre são executadas em sandbox, assim como os plugins listados sob sandboxed: [] na integração emdash(). Plugins listados sob plugins: [] são executados no processo do servidor e não usam o runner.

O runner depende da plataforma de deploy. No Cloudflare Workers, cada plugin é executado como um Dynamic Worker criado através do binding Worker Loader. No Node.js, o servidor inicia workerd, o runtime Workers open-source, como processo filho e executa cada plugin como um serviço dentro dele. A opção sandboxRunner de emdash() seleciona o runner. Sem ela, os plugins sob sandboxed: [] nunca são carregados, e um marketplace configurado faz o build falhar com “Marketplace requires sandboxRunner to be configured”.

A tabela a seguir resume o que cada runner precisa e aplica.

Cloudflare WorkersNode.js
sandboxRunnersandbox() de @emdash-cms/cloudflare"@emdash-cms/sandbox-workerd/sandbox"
RequisitosPlano Workers Paid, um binding worker_loaders, PluginBridge exportado do ponto de entrada do WorkerO pacote workerd
Acesso ao BDO binding D1 DB, independente do adaptador configuradoO banco de dados configurado
Limites aplicadosTempo de CPU, subrequests, wall timeWall time

Cloudflare Workers

Dynamic Workers estão disponíveis no plano Workers Paid. O runner precisa do binding e da exportação do ponto de entrada abaixo; os templates *-cloudflare fornecem ambos.

  1. Adicione o binding Worker Loader ao wrangler.jsonc. O runner o lê com o nome LOADER:

    {
    	"worker_loaders": [
    		{
    			"binding": "LOADER",
    		},
    	],
    }
  2. Exporte PluginBridge do ponto de entrada do Worker e aponte main para esse arquivo. PluginBridge é o ponto de entrada pelo qual plugins sandboxed acessam conteúdo, mídia, armazenamento e email; o runner o procura nas exportações do 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. Selecione o runner na integração emdash():

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

Node.js

  1. Instale o runner junto com workerd, que é uma dependência peer:

    npm install @emdash-cms/sandbox-workerd workerd

    O pacote workerd instala o binário para a plataforma atual (Linux, macOS e Windows em x64; Linux e macOS em arm64) através de uma dependência opcional. Instale com dependências opcionais habilitadas, na plataforma onde o servidor é executado. Em um build Docker multi-stage, execute a instalação em um estágio com a mesma plataforma que o estágio de runtime.

  2. Selecione o runner na integração emdash():

    import { sqlite } from "emdash/db";
    
    emdash({
    	database: sqlite({ url: "file:./data/emdash.db" }),
    	sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
    });
  3. Para desenvolvimento, instale miniflare como dependência de desenvolvimento:

    npm install -D miniflare

    Quando NODE_ENV é development, que astro dev define, e miniflare está instalado, o runner passa os plugins para o Miniflare, que gerencia seu próprio processo workerd; a política de crash abaixo não se aplica. astro preview define NODE_ENV como production e node ./dist/server/entry.mjs o deixa indefinido; ambos usam workerd.

Como o processo workerd funciona

O EmDash inicia workerd durante a inicialização na primeira requisição ao site, após os plugins sandboxed serem carregados, e aguarda até 10 segundos para que os serviços de plugins respondam. Instalar ou atualizar um plugin pelo admin o reinicia. Tudo que workerd escreve em stdout ou stderr aparece na saída do servidor com o prefixo [emdash:workerd].

Os serviços de plugins escutam em 127.0.0.1, e o canal de volta ao servidor é um socket de domínio Unix (uma porta TCP 127.0.0.1 no Windows). Nenhuma porta de entrada precisa ser aberta.

O processo filho recebe apenas PATH, HOME, TMPDIR, TMP, TEMP, LANG e LC_ALL do ambiente do servidor, então segredos no ambiente do servidor ficam fora do sandbox. Para passar mais variáveis, defina EMDASH_WORKERD_PASSTHROUGH_ENV com uma lista de nomes de variáveis separados por vírgula.

Se workerd sair inesperadamente, o runner registra [emdash:workerd] workerd exited with <reason> e o reinicia na próxima invocação, com um atraso que começa em 1 segundo e dobra até 30 segundos. Quando workerd crasha mais de cinco vezes em 60 segundos, o runner para de reiniciá-lo e registra [emdash:workerd] workerd crashed 5 times in 60 seconds, giving up. A partir daí, todo hook e rota de plugin sandboxed falha com Plugin sandbox unavailable for <plugin>: workerd is not running até que o servidor seja reiniciado. Um SIGTERM para o servidor termina workerd com ele.

Limites de recursos

Cada runner aplica o mesmo conjunto de limites por invocação de plugin. Os limites são fixos; a integração emdash() não tem opção para eles.

LimiteValorCloudflare WorkersNode.js
Tempo CPU50 msAplicado pelo Worker Loader; o plugin lança quando atinge o limiteNão aplicado
Subrequests10Aplicado pelo Worker Loader; o plugin lança quando atinge o limiteNão aplicado
Memória128 MBNão aplicado por plugin; o teto de memória de isolamento da plataforma se aplicaNão aplicado
Wall time30 sAplicado pelo runnerAplicado pelo runner

Quando um hook ou rota excede o limite de wall-time, a invocação falha com Plugin <id> exceeded wall-time limit of 30000ms during hook:<name> (ou route:<name>). Para um hook, o EmDash registra a falha com o prefixo EmDash: Sandboxed plugin <id> e continua a requisição sem o resultado daquele plugin. Uma rota de plugin que excede o limite falha para seu chamador.

Quando o runner não está disponível

Um runner configurado ainda pode estar indisponível: no Cloudflare Workers quando falta o binding worker_loaders ou a exportação PluginBridge, no Node.js quando workerd não está instalado ou seu binário não executa. O EmDash então registra o seguinte aviso quando o runtime inicia:

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.

Plugins sob sandboxed: [] não são carregados, plugins instalados do marketplace e registro não são executados, e uma nova instalação pelo admin falha com o código de erro SANDBOX_NOT_AVAILABLE. O restante do site não é afetado.

Executar plugins sandboxed no processo

Defina sandbox: false em emdash() para executar os plugins sob sandboxed: [] e plugins instalados do marketplace no processo do servidor, sem isolamento ou limites. É uma opção de depuração que distingue um bug em um plugin de um bug no sandbox. A seguinte configuração desativa o sandbox em um site Node.js:

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

No Cloudflare Workers, o runtime recusa iniciar com sandbox: false is not supported in Cloudflare Workers.

Solução de problemas

Cada entrada é encabeçada pela mensagem como o servidor a registra, ou pelo código de erro que o admin retorna.

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

No Cloudflare Workers, verifique ambos os requisitos: wrangler.jsonc tem um binding worker_loaders chamado LOADER, e main aponta para um arquivo que exporta PluginBridge. O deploy do binding requer o plano Workers Paid.

No Node.js, execute o binário que o runner usa:

npx workerd --version

Se o comando falhar, workerd está faltando em node_modules ou o binário instalado não executa nesta plataforma. Reinstale na plataforma alvo com dependências opcionais habilitadas.

”workerd failed to start within 10 seconds”

O processo filho foi iniciado, mas seus serviços de plugins não responderam em 10 segundos. As linhas com prefixo [emdash:workerd] antes desta mensagem contêm a saída do workerd em si, incluindo erros de configuração e inicialização. O runner tenta novamente na próxima invocação.

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

O runner parou de reiniciar workerd. As linhas [emdash:workerd] workerd exited with <reason> antes desta mensagem nomeiam o código de saída ou sinal de cada crash. Corrija a causa, então reinicie o servidor.

SANDBOX_NOT_AVAILABLE ao instalar um plugin

A requisição de instalação do admin foi recusada porque o runner está faltando ou indisponível. Configure o runner para a plataforma, ou corrija a causa do aviso de inicialização acima, e reimplante.