Plugins sandboxed são isolados por padrão. Para fazer qualquer coisa além de ler e escrever seu próprio KV e armazenamento, um plugin deve declarar uma capacidade em seu manifesto. A ponte da sandbox controla cada API fornecida pelo host com base nessas declarações — um plugin que não declarou content:read não obtém ctx.content, e um que não declarou network:request não obtém ctx.http.
Esta página cobre o que cada capacidade concede, como a sandbox as aplica e o que não é aplicável.
Declarando capacidades
As capacidades ficam em emdash-plugin.jsonc, junto com slug e o resto do contrato de confiança:
{
"slug": "plugin-hello",
// ...identidade + perfil...
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
Declare apenas o que o plugin realmente precisa. As declarações de capacidades também são o que o Marketplace mostra aos operadores no diálogo de consentimento — capacidades extras são fricção na instalação e um sinal de segurança nas auditorias.
Referência de capacidades
| Capacidade | Concede acesso a |
|---|---|
content:read | ctx.content.get(), ctx.content.list() |
content:write | ctx.content.create(), ctx.content.update(), ctx.content.delete() (implica 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() (implica media:read) |
network:request | ctx.http.fetch() — restrito a allowedHosts |
network:request:unrestricted | ctx.http.fetch() sem restrição de host (apenas para URLs configuradas pelo usuário) |
users:read | ctx.users.get(), ctx.users.getByEmail(), ctx.users.list() |
email:send | ctx.email.send() (requer um plugin provedor de email configurado) |
hooks.email-transport:register | Permite registrar o hook exclusivo email:deliver (provedor de transporte) |
hooks.email-events:register | Permite registrar hooks email:beforeSend / email:afterSend |
hooks.page-fragments:register | Permite registrar o hook page:fragments (apenas plugins nativos) |
Algumas coisas para saber:
- Implicações.
content:writeimplica automaticamentecontent:read;media:writeimplicamedia:read;network:request:unrestrictedimplicanetwork:request. Você não precisa listar ambos. - Taxonomias são uma superfície separada somente leitura.
taxonomies:readconcede acesso às definições de taxonomia, seus termos e os termos atribuídos a uma entrada viactx.taxonomies. É independente decontent:read— declare ambos se o plugin lê conteúdo e sua classificação. Não há acesso de escrita a taxonomias a partir de plugins. network:request:unrestrictedexiste para URLs configuradas pelo usuário. Um plugin de webhook onde o operador insere a URL de destino precisa alcançar hosts que não estão no manifesto. Plugins que sempre chamam APIs conhecidas devem usarnetwork:request+allowedHosts.email:sendé controlado por configuração, não apenas pela capacidade. Um plugin pode declararemail:send, masctx.emailsó é preenchido se outro plugin registrou um transporteemail:deliver.
Listas de hosts permitidos para rede
Plugins com network:request só podem fazer fetch de hosts listados em allowedHosts. Curingas são suportados para subdomínios:
"capabilities": ["network:request"],
"allowedHosts": [
"api.example.com", // host exato
"*.cdn.example.com" // qualquer subdomínio de cdn.example.com
]
A ponte verifica o host da URL da requisição contra a lista de permitidos antes de encaminhar a requisição. Uma requisição para um host não declarado lança uma exceção dentro do plugin sem nunca sair da sandbox.
network:request:unrestricted ignora completamente a verificação da lista de permitidos. É destinado a plugins onde o operador configura a URL de destino em tempo de execução (emissores de webhook, encaminhadores HTTP genéricos). Evite-o para plugins onde o destino é parte do design do plugin — declare network:request com hosts explícitos para que o diálogo de consentimento diga aos operadores exatamente para onde o plugin chamará.
O que a sandbox aplica
Quando um runner de sandbox está ativo, o runtime aplica:
-
Controle por capacidades. A factory PluginContext só preenche
ctx.content,ctx.taxonomies,ctx.media,ctx.http,ctx.users,ctx.emailse a capacidade correspondente estiver declarada. Chamar um método em uma capacidade não declarada não é possível — não há objeto lá. -
Escopo de armazenamento e KV. Cada operação de armazenamento e KV é limitada ao slug do plugin. Um plugin não pode ler o KV ou as coleções de armazenamento de outro, e só pode acessar coleções de armazenamento que declarou no manifesto.
-
Isolamento de rede.
fetch()direto e outras primitivas de rede são bloqueados pelo runner. O único caminho para a rede éctx.http.fetch(), que passa pela validação de host da ponte. -
Sem bindings do host. Plugins sandboxed não veem variáveis de ambiente, sistema de arquivos ou bindings de plataforma — mesmo que seu worker host os tenha. O runtime do plugin é um isolado limpo com apenas a ponte e as capacidades declaradas.
-
Limites de recursos. O runner pode aplicar limites de CPU, sub-requisições, tempo wall-clock e memória por invocação. Os limites exatos dependem de qual runner você está usando; o runner Cloudflare usa os limites do Worker Loader da plataforma (50ms CPU por invocação, 10 sub-requisições, 30 segundos wall-clock, ~128MB de memória). O runner workerd Node.js (
@emdash-cms/sandbox-workerd) aplica tempo wall-clock viaPromise.race; limites de CPU e memória são recursos da plataforma Cloudflare e não são aplicados pelo workerd standalone. Hooks que excedem os limites do runner são cancelados; o timeout de hook do EmDash (timeoutna configuração do hook) aplica um limite superior mais rígido adicionalmente.
O que a sandbox não aplica
Algumas coisas que o sistema de capacidades não cobre e não pode cobrir:
- Comportamento dentro de uma capacidade concedida. Um plugin com
content:writepode editar qualquer conteúdo, não apenas o seu. Capacidades são de granulação grossa — dizem “este plugin pode escrever conteúdo”, não “este plugin só pode escrever o conteúdo que criou”. A revisão em tempo de auditoria é a única verificação do que um plugin realmente faz dentro de sua concessão. - Confiança do operador em Node.js. Se o runner de sandbox configurado reporta que não está disponível (sem Cloudflare Worker Loader, sem runner do lado Node instalado, etc.), plugins
sandboxed: []são ignorados na inicialização. Você pode movê-los paraplugins: []para executar em processo — mas então não há isolado V8, sem limites de recursos, e o plugin pode chamarfetch()diretamente ou ler variáveis de ambiente. Trate isso como confiança em nível nativo. - Canais laterais. Temporização, saída de log e dados armazenados são visíveis para qualquer pessoa com acesso razoável ao ambiente do host. Não use a sandbox como fronteira de confidencialidade contra o operador que a executa.
Consentimento de capacidades
Quando um operador instala um plugin sandboxed do Marketplace, o EmDash mostra um diálogo de consentimento com as capacidades declaradas. Atualizações que adicionam capacidades — por exemplo, um plugin que antes apenas lia conteúdo e agora quer fazer requisições de rede — são mostradas como um diff de capacidades e requerem aprovação nova antes que a nova versão tenha efeito.
É por isso que é importante declarar capacidades extras mesmo se você “pode precisar delas depois”. Elas aparecem como fricção em cada instalação e atualização, e auditorias de segurança sinalizam plugins que pedem mais do que obviamente precisam. Liste exatamente o que o plugin usa e adicione novas capacidades em uma versão real quando o plugin realmente começar a usá-las.
Validação em tempo de build
emdash-plugin bundle e emdash-plugin publish executam verificações adicionais:
- Cada capacidade declarada deve estar no conjunto reconhecido (erros de digitação fazem o build falhar).
network:requestrequer umallowedHostsnão vazio;network:request:unrestrictedrequer que esteja vazio. Veja a referência do manifesto.- O
backend.jsempacotado não pode importar built-ins do Node.js (fs,path,child_process, etc.) — os runtimes de sandbox não os fornecem.
Veja Empacotar e publicar para a lista completa de verificações.