Los plugins en sandbox están aislados por defecto. Para hacer cualquier cosa más allá de leer y escribir su propio KV y storage, un plugin debe declarar una capability en su manifest. El puente de sandbox controla cada API proporcionada por el host según esas declaraciones — un plugin que no declaró content:read no obtiene un ctx.content, y uno que no declaró network:request no obtiene ctx.http.
Esta página cubre qué otorga cada capability, cómo la sandbox las hace cumplir y qué no es aplicable.
Declarar capabilities
Las capabilities viven en emdash-plugin.jsonc, junto con slug y el resto del contrato de confianza:
{
"slug": "plugin-hello",
// ...identity + profile...
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
Declara solo lo que el plugin realmente necesita. Las declaraciones de capabilities también son lo que el marketplace muestra a los operadores del sitio en el diálogo de consentimiento — las capabilities adicionales son fricción en el momento de la instalación y una señal de seguridad en las auditorías.
Referencia de capabilities
| Capability | Otorga acceso 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() — restringido a allowedHosts |
network:request:unrestricted | ctx.http.fetch() sin restricción de host (solo para URLs configuradas por el usuario) |
users:read | ctx.users.get(), ctx.users.getByEmail(), ctx.users.list() |
email:send | ctx.email.send() (requiere un plugin proveedor de email configurado) |
hooks.email-transport:register | Permite registrar el hook exclusivo email:deliver (proveedores de transporte) |
hooks.email-events:register | Permite registrar hooks email:beforeSend / email:afterSend |
hooks.page-fragments:register | Permite registrar el hook page:fragments (solo plugins nativos) |
Algunas cosas que vale la pena saber:
- Implicaciones.
content:writeimplica automáticamentecontent:read;media:writeimplicamedia:read;network:request:unrestrictedimplicanetwork:request. No necesitas listar ambas. network:request:unrestrictedexiste para URLs configuradas por el usuario. Un plugin de webhook donde el operador escribe la URL de destino necesita alcanzar hosts que no están en el manifest. Los plugins que siempre llaman a APIs conocidas deben usarnetwork:request+allowedHosts.email:sendestá controlado por configuración, no solo por la capability. Un plugin puede declararemail:send, peroctx.emailsolo se poblará si algún otro plugin ha registrado un transporteemail:deliver.
Listas de hosts de red permitidos
Los plugins con network:request solo pueden obtener hosts listados en allowedHosts. Se admiten comodines para subdominios:
"capabilities": ["network:request"],
"allowedHosts": [
"api.example.com", // host exacto
"*.cdn.example.com" // cualquier subdominio de cdn.example.com
]
El puente verifica el host de la URL de la solicitud contra la lista permitida antes de reenviar la solicitud. Una solicitud a un host que no fue declarado lanza un error dentro del plugin sin salir nunca de la sandbox.
network:request:unrestricted omite completamente la verificación de lista permitida. Está destinado a plugins donde el operador configura la URL de destino en tiempo de ejecución (enviadores de webhook, reenviadores HTTP genéricos). Evítalo para plugins donde el destino es parte del diseño del plugin — declara network:request con hosts explícitos en su lugar, para que el diálogo de consentimiento indique a los operadores exactamente a dónde va a llamar el plugin.
Lo que la sandbox hace cumplir
Cuando un ejecutor de sandbox está activo, el runtime hace cumplir:
-
Restricción de capabilities. La fábrica PluginContext solo llena
ctx.content,ctx.media,ctx.http,ctx.users,ctx.emailcuando se declara la capability correspondiente. Llamar a un método en una capability no declarada no es posible — no hay objeto allí. -
Ámbito de storage y KV. Cada operación de storage y KV está limitada al slug del plugin. Un plugin no puede leer el KV o las colecciones de storage de otro plugin, y solo puede acceder a las colecciones de storage que declaró en el manifest.
-
Aislamiento de red. El
fetch()directo y otros primitivos de red son bloqueados por el ejecutor. La única forma de alcanzar la red esctx.http.fetch(), que pasa por la validación de host del puente. -
Sin enlaces de host. Los plugins en sandbox no ven variables de entorno, el sistema de archivos ni ningún enlace de plataforma — incluso si tu worker host los tiene. El runtime del plugin es un aislado limpio con solo el puente y las capabilities declaradas.
-
Límites de recursos. El ejecutor puede hacer cumplir límites de CPU, subrequest, wall-clock y memoria por invocación. Los límites exactos dependen del ejecutor que estés usando; el ejecutor de Cloudflare usa los límites del Worker Loader de la plataforma (50ms CPU por invocación, 10 subrequests, 30 segundos wall-clock, ~128MB memoria). Los hooks que exceden los límites del ejecutor se abortan; el timeout de hook de EmDash (
timeouten la configuración del hook) impone un techo más estricto además de eso.
Lo que la sandbox no hace cumplir
Algunas cosas que el sistema de capabilities no cubre y no puede cubrir:
- Comportamiento dentro de una capability otorgada. Un plugin con
content:writepuede editar cualquier contenido, no solo el suyo. Las capabilities son gruesas — dicen “este plugin puede escribir contenido”, no “este plugin solo puede escribir el contenido que creó”. La revisión en tiempo de auditoría es la única verificación sobre lo que un plugin realmente hace dentro de su concesión. - Confianza del operador en Node.js. Cuando el ejecutor de sandbox configurado informa que no está disponible (sin Cloudflare Worker Loader, sin ejecutor del lado de Node instalado, etc.), los plugins
sandboxed: []se omiten al inicio. Puedes moverlos aplugins: []para ejecutarlos en proceso — pero entonces no hay aislado V8, sin límites de recursos, y el plugin puede llamar afetch()directamente o leer variables de entorno. Trata eso como confianza a nivel nativo. - Canales laterales. El timing, la salida de log y los datos almacenados son todos visibles para cualquier persona con acceso apropiado al entorno host. No uses la sandbox como un límite de confidencialidad contra el operador que la ejecuta.
Consentimiento de capabilities
Cuando un operador instala un plugin en sandbox desde el marketplace, EmDash muestra un diálogo de consentimiento enumerando las capabilities declaradas. Las actualizaciones que agregan capabilities — por ejemplo, un plugin que anteriormente solo leía contenido ahora quiere hacer solicitudes de red — aparecen como una diferencia de capabilities y requieren aprobación nueva antes de que la nueva versión surta efecto.
Por eso declarar capabilities adicionales importa incluso si “podrías usarlas más tarde”. Aparecen como fricción en cada instalación y actualización, y las auditorías de seguridad marcan plugins que piden más de lo que obviamente necesitan. Lista exactamente lo que el plugin usa, y agrega nuevas capabilities en una versión real cuando el plugin realmente comienza a usarlas.
Validación en tiempo de bundle
emdash-plugin bundle y emdash-plugin publish realizan verificaciones adicionales:
- Cada capability declarada debe estar en el conjunto reconocido (los errores tipográficos fallan la construcción).
network:requestrequiere unallowedHostsno vacío;network:request:unrestrictedrequiere que esté vacío. Ver la referencia del manifest.- El
backend.jsempaquetado no puede importar built-ins de Node.js (fs,path,child_process, etc.) — los runtimes de sandbox no los proporcionan.
Ver Bundling and publishing para la lista completa de verificaciones.