Plugins em sandbox são isolados por padrão. Para fazer qualquer coisa além de ler e escrever seu próprio KV e armazenamento, um plugin tem que declarar uma capability em seu descritor. A ponte do sandbox controla cada API fornecida pelo host com base nessas declarações — um plugin que não declarou content:read não obtém um ctx.content, e um que não declarou network:request não obtém ctx.http.
Esta página cobre o que cada capability concede, como o sandbox as aplica e o que não é aplicável.
Declarar capabilities
As capabilities residem no descritor (o arquivo importado por astro.config.mjs), junto com id, version e entrypoint:
export function helloPlugin(): PluginDescriptor {
return {
id: "plugin-hello",
version: "0.1.0",
format: "standard",
entrypoint: "@my-org/plugin-hello/sandbox",
capabilities: ["content:read", "network:request"],
allowedHosts: ["api.example.com"],
};
}
Declare apenas o que o plugin realmente precisa. As declarações de capabilities também são o que o marketplace mostra aos operadores do site no diálogo de consentimento — capabilities extras são atrito no momento da instalação e uma bandeira de segurança em auditorias.
Referência de capabilities
| Capability | 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) |
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 (provedores 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 que vale a pena saber:
- Implicações.
content:writeimplica automaticamentecontent:read;media:writeimplicamedia:read;network:request:unrestrictedimplicanetwork:request. Você não precisa listar ambos. network:request:unrestrictedexiste para URLs configuradas pelo usuário. Um plugin de webhook onde o operador digita 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 pela configuração, não apenas pela capability. Um plugin pode declararemail:send, masctx.emailsó será populado se algum outro plugin tiver registrado um transporteemail:deliver.
Listas de permissão de hosts de rede
Plugins com network:request só podem buscar 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 solicitação contra a lista de permissões antes de encaminhar a solicitação. Uma solicitação para um host que não foi declarado lança dentro do plugin sem nunca sair do sandbox.
network:request:unrestricted pula a verificação da lista de permissões inteiramente. É destinado a plugins onde o operador configura a URL de destino em tempo de execução (remetentes de webhook, encaminhadores HTTP genéricos). Evite-o para plugins onde o destino faz parte do design do plugin — declare network:request com hosts explícitos em vez disso, para que o diálogo de consentimento diga aos operadores exatamente para onde o plugin vai chamar.
O que o sandbox aplica
Quando um executor de sandbox está ativo, o runtime aplica:
-
Controle de capabilities. A factory PluginContext só popula
ctx.content,ctx.media,ctx.http,ctx.users,ctx.emailquando a capability correspondente é declarada. Chamar um método em uma capability não declarada não é possível — não há nenhum objeto lá. -
Escopo de armazenamento e KV. Cada operação de armazenamento e KV é delimitada pelo id do plugin. Um plugin não pode ler o KV de outro plugin ou suas coleções de armazenamento, e só pode acessar coleções de armazenamento que declarou no descritor.
-
Isolamento de rede.
fetch()direto e outras primitivas de rede são bloqueadas pelo executor. A única maneira de alcançar a rede éctx.http.fetch(), que passa pela validação de host da ponte. -
Sem vinculações de host. Plugins em sandbox não veem variáveis de ambiente, o sistema de arquivos ou quaisquer vinculações de plataforma — mesmo se seu worker host as tiver. O runtime do plugin é um isolado limpo com apenas a ponte e as capabilities declaradas.
-
Limites de recursos. O executor pode aplicar limites de CPU, subsolicitações, tempo de relógio e memória por invocação. Os limites exatos dependem de qual executor você está usando; o executor Cloudflare usa os limites do Worker Loader da plataforma (50ms de CPU por invocação, 10 subsolicitações, 30 segundos de tempo de relógio, ~128MB de memória). Hooks que excedem os limites do executor são abortados; o timeout do hook EmDash (
timeoutna configuração do hook) aplica um teto mais rigoroso em cima disso.
O que o sandbox não aplica
Algumas coisas que o sistema de capabilities não pode cobrir:
- Comportamento dentro de uma capability concedida. Um plugin com
content:writepode editar qualquer conteúdo, não apenas o seu próprio. Capabilities são grosseiras — elas dizem “este plugin pode escrever conteúdo”, não “este plugin pode escrever apenas o conteúdo que criou”. A revisão no momento da auditoria é a única verificação do que um plugin realmente faz dentro de sua concessão. - Confiança do operador no Node.js. Quando o executor de sandbox configurado relata indisponível (sem Cloudflare Worker Loader, sem executor do lado do Node instalado, etc.), plugins
sandboxed: []são pulados na inicialização. Você pode movê-los paraplugins: []para executá-los 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 de nível nativo. - Canais laterais. Temporização, saída de log e dados armazenados são todos visíveis para qualquer pessoa com acesso apropriado ao ambiente host. Não use o sandbox como um limite de confidencialidade contra o operador que o executa.
Consentimento de capabilities
Quando um operador instala um plugin em sandbox do marketplace, EmDash mostra um diálogo de consentimento listando as capabilities declaradas. Atualizações que adicionam capabilities — por exemplo, um plugin que anteriormente apenas lia conteúdo agora quer fazer solicitações de rede — surgem como um diff de capabilities e exigem nova aprovação antes que a nova versão entre em vigor.
É por isso que declarar capabilities extras importa mesmo se você “possa usá-las mais tarde”. Elas aparecem como atrito 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 capabilities em uma versão real quando o plugin realmente começar a usá-las.
Validação no momento do empacotamento
emdash plugin bundle e emdash plugin publish realizam verificações adicionais:
- Cada capability declarada deve estar no conjunto conhecido (erros de digitação falham a construção).
- Um plugin que declara
network:requestsem preencherallowedHostsaciona um aviso — declare hosts ou mude paranetwork:request:unrestrictede documente o porquê. - Nomes de capabilities depreciados acionam avisos durante
bundle/validatee uma falha rígida empublish. - O
backend.jsempacotado não pode importar módulos internos do Node.js (fs,path,child_process, etc.) — runtimes de sandbox não os fornecem.
Veja Empacotamento e publicação para a lista completa de verificações.