Capabilities e segurança

Nesta página

Plugins em sandbox são isolados por padrão. Para fazer qualquer coisa além de ler e escrever seu próprio KV e storage, um plugin deve declarar uma capability em seu manifest. A ponte de 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 a sandbox as impõe e o que não é aplicável.

Declarar capabilities

Capabilities ficam em emdash-plugin.jsonc, junto com slug e o resto do contrato de confiança:

{
	"slug": "plugin-hello",
	// ...identity + profile...

	"capabilities": ["content:read", "network:request"],
	"allowedHosts": ["api.example.com"]
}

Declare apenas o que o plugin realmente precisa. 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

CapabilityConcede acesso a
content:readctx.content.get(), ctx.content.list()
content:writectx.content.create(), ctx.content.update(), ctx.content.delete() (implica content:read)
media:readctx.media.get(), ctx.media.list()
media:writectx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (implica media:read)
network:requestctx.http.fetch() — restrito a allowedHosts
network:request:unrestrictedctx.http.fetch() sem restrição de host (apenas para URLs configuradas pelo usuário)
users:readctx.users.get(), ctx.users.getByEmail(), ctx.users.list()
email:sendctx.email.send() (requer um plugin provedor de email configurado)
hooks.email-transport:registerPermite registrar o hook exclusivo email:deliver (provedores de transporte)
hooks.email-events:registerPermite registrar hooks email:beforeSend / email:afterSend
hooks.page-fragments:registerPermite registrar o hook page:fragments (apenas plugins nativos)

Algumas coisas que vale a pena saber:

  • Implicações. content:write implica automaticamente content:read; media:write implica media:read; network:request:unrestricted implica network:request. Você não precisa listar ambos.
  • network:request:unrestricted existe 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 manifest. Plugins que sempre chamam APIs conhecidas devem usar network:request + allowedHosts.
  • email:send é controlado por configuração, não apenas pela capability. Um plugin pode declarar email:send, mas ctx.email só será populado se algum outro plugin tiver registrado um transporte email:deliver.

Listas de hosts de rede permitidos

Plugins com network:request só podem buscar hosts listados em allowedHosts. Wildcards 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 permitida antes de encaminhar a requisição. Uma requisição para um host que não foi declarado lança um erro dentro do plugin sem nunca sair da sandbox.

network:request:unrestricted pula completamente a verificação da lista permitida. É 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 a sandbox impõe

Quando um executor de sandbox está ativo, o runtime impõe:

  1. Controle de capabilities. A fábrica PluginContext só preenche ctx.content, ctx.media, ctx.http, ctx.users, ctx.email quando a capability correspondente é declarada. Chamar um método em uma capability não declarada não é possível — não há objeto ali.

  2. Escopo de storage e KV. Cada operação de storage e KV é limitada ao slug do plugin. Um plugin não pode ler o KV ou coleções de storage de outro plugin, e só pode acessar coleções de storage que declarou no manifest.

  3. Isolamento de rede. O fetch() direto e outros primitivos de rede são bloqueados pelo executor. A única maneira de alcançar a rede é ctx.http.fetch(), que passa pela validação de host da ponte.

  4. Sem bindings de host. Plugins em sandbox não veem variáveis de ambiente, o sistema de arquivos ou quaisquer bindings de plataforma — mesmo se seu worker host os tiver. O runtime do plugin é um isolado limpo com apenas a ponte e as capabilities declaradas.

  5. Limites de recursos. O executor pode impor limites de CPU, subrequest, wall-clock e memória por invocação. Os limites exatos dependem do executor que você está usando; o executor Cloudflare usa os limites do Worker Loader da plataforma (50ms CPU por invocação, 10 subrequests, 30 segundos wall-clock, ~128MB memória). Hooks que excedem os limites do executor são abortados; o timeout de hook do EmDash (timeout na configuração do hook) impõe um teto mais rigoroso além disso.

O que a sandbox não impõe

Algumas coisas que o sistema de capabilities não cobre e não pode cobrir:

  • Comportamento dentro de uma capability concedida. Um plugin com content:write pode 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 só pode escrever o conteúdo que criou”. A revisão no momento da auditoria é a única verificação sobre o que um plugin realmente faz dentro de sua concessão.
  • Confiança do operador em Node.js. Quando o executor de sandbox configurado reporta indisponível (sem Cloudflare Worker Loader, sem executor do lado Node instalado, etc.), plugins sandboxed: [] são pulados na inicialização. Você pode movê-los para plugins: [] para executá-los em processo — mas então não há isolado V8, sem limites de recursos, e o plugin pode chamar fetch() diretamente ou ler variáveis de ambiente. Trate isso como confiança de nível nativo.
  • Canais laterais. Timing, saída de log e dados armazenados são todos visíveis para qualquer pessoa com acesso apropriado ao ambiente host. Não use a sandbox como um limite de confidencialidade contra o operador que a executa.

Consentimento de capabilities

Quando um operador instala um plugin em sandbox do marketplace, o 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 requisições de rede — aparecem como uma diferença de capabilities e requerem aprovação nova antes que a nova versão entre em vigor.

É por isso que declarar capabilities extras importa mesmo se você “pode 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 em tempo de bundle

emdash-plugin bundle e emdash-plugin publish realizam verificações adicionais:

  • Cada capability declarada deve estar no conjunto reconhecido (erros de digitação falham a construção).
  • network:request requer um allowedHosts não vazio; network:request:unrestricted requer que esteja vazio. Veja a referência do manifest.
  • O backend.js empacotado não pode importar built-ins do Node.js (fs, path, child_process, etc.) — runtimes de sandbox não os fornecem.

Veja Bundling and publishing para a lista completa de verificações.