Capabilities y seguridad

En esta página

Los plugins en sandbox están aislados por defecto. Para hacer algo más allá de leer y escribir su propio KV y almacenamiento, un plugin debe declarar una capability en su descriptor. El puente del sandbox controla cada API proporcionada por el host basándose en 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 el sandbox las aplica y qué no es aplicable.

Declarar capabilities

Las capabilities residen en el descriptor (el archivo importado por astro.config.mjs), junto con id, version y 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"],
	};
}

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

CapabilityOtorga acceso 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() — restringido a allowedHosts
network:request:unrestrictedctx.http.fetch() sin restricción de host (solo para URLs configuradas por el usuario)
users:readctx.users.get(), ctx.users.getByEmail(), ctx.users.list()
email:sendctx.email.send() (requiere un plugin proveedor de email configurado)
hooks.email-transport:registerPermite registrar el hook exclusivo email:deliver (proveedores de transporte)
hooks.email-events:registerPermite registrar hooks email:beforeSend / email:afterSend
hooks.page-fragments:registerPermite registrar el hook page:fragments (solo plugins nativos)

Algunas cosas que vale la pena saber:

  • Implicaciones. content:write implica automáticamente content:read; media:write implica media:read; network:request:unrestricted implica network:request. No necesitas listar ambos.
  • network:request:unrestricted existe 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 manifiesto. Los plugins que siempre llaman a APIs conocidas deben usar network:request + allowedHosts.
  • email:send está controlado por la configuración, no solo por la capability. Un plugin puede declarar email:send, pero ctx.email solo se poblará si algún otro plugin ha registrado un transporte email:deliver.

Listas de permitidos de hosts de red

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 solicitud contra la lista de permitidos antes de reenviar la solicitud. Una solicitud a un host que no se declaró arroja dentro del plugin sin salir nunca del sandbox.

network:request:unrestricted omite la verificación de la lista de permitidos por completo. Está destinado a plugins donde el operador configura la URL de destino en tiempo de ejecución (remitentes 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 diga a los operadores exactamente a dónde el plugin va a llamar.

Qué aplica el sandbox

Cuando un ejecutor de sandbox está activo, el runtime aplica:

  1. Control de capabilities. La fábrica PluginContext solo puebla ctx.content, ctx.media, ctx.http, ctx.users, ctx.email cuando se declara la capability correspondiente. Llamar a un método en una capability no declarada no es posible — no hay ningún objeto allí.

  2. Ámbito de almacenamiento y KV. Cada operación de almacenamiento y KV está limitada al id del plugin. Un plugin no puede leer el KV de otro plugin o sus colecciones de almacenamiento, y solo puede acceder a las colecciones de almacenamiento que declaró en el descriptor.

  3. Aislamiento de red. El fetch() directo y otras primitivas de red están bloqueadas por el ejecutor. La única forma de alcanzar la red es ctx.http.fetch(), que pasa por la validación de host del puente.

  4. Sin vinculaciones de host. Los plugins en sandbox no ven variables de entorno, el sistema de archivos o ninguna vinculación de plataforma — incluso si tu worker host las tiene. El runtime del plugin es un aislado limpio con solo el puente y las capabilities declaradas.

  5. Límites de recursos. El ejecutor puede aplicar límites de CPU, subpeticiones, reloj de pared y memoria por invocación. Los límites exactos dependen de qué ejecutor estés usando; el ejecutor de Cloudflare usa los límites del Worker Loader de la plataforma (50ms de CPU por invocación, 10 subpeticiones, 30 segundos de reloj de pared, ~128MB de memoria). Los hooks que exceden los límites del ejecutor se abortan; el timeout del hook de EmDash (timeout en la configuración del hook) aplica un techo más estricto encima de eso.

Qué no aplica el sandbox

Algunas cosas que el sistema de capabilities no puede cubrir:

  • Comportamiento dentro de una capability otorgada. Un plugin con content:write puede editar cualquier contenido, no solo el suyo propio. Las capabilities son gruesas — dicen “este plugin puede escribir contenido”, no “este plugin puede escribir solo el contenido que creó”. La revisión en tiempo de auditoría es la única verificación de lo que un plugin realmente hace dentro de su otorgamiento.
  • Confianza del operador en Node.js. Cuando el ejecutor de sandbox configurado reporta no disponible (sin Cloudflare Worker Loader, sin ejecutor del lado de Node instalado, etc.), los plugins sandboxed: [] se omiten en el inicio. Puedes moverlos a plugins: [] para ejecutarlos en proceso — pero entonces no hay aislado V8, sin límites de recursos, y el plugin puede llamar a fetch() directamente o leer variables de entorno. Trata eso como confianza de nivel nativo.
  • Canales laterales. El tiempo, la salida de registros y los datos almacenados son visibles para cualquiera con el acceso apropiado al entorno del host. No uses el sandbox como un límite de confidencialidad contra el operador que lo ejecuta.

Consentimiento de capabilities

Cuando un operador instala un plugin en sandbox desde el marketplace, EmDash muestra un diálogo de consentimiento que lista las capabilities declaradas. Las actualizaciones que agregan capabilities — por ejemplo, un plugin que anteriormente solo leía contenido ahora quiere hacer solicitudes de red — surgen como un diff de capabilities y requieren una aprobación nueva antes de que la nueva versión entre en vigor.

Por eso es importante declarar capabilities adicionales 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 comience a usarlas.

Validación en tiempo de empaquetado

emdash plugin bundle y emdash plugin publish realizan verificaciones adicionales:

  • Cada capability declarada debe estar en el conjunto conocido (los errores tipográficos fallan la construcción).
  • Un plugin que declara network:request sin poblar allowedHosts desencadena una advertencia — declara hosts o cambia a network:request:unrestricted y documenta por qué.
  • Los nombres de capabilities deprecados desencadenan advertencias durante bundle/validate y un fallo duro en publish.
  • El backend.js empaquetado no puede importar módulos integrados de Node.js (fs, path, child_process, etc.) — los runtimes de sandbox no los proporcionan.

Consulta Empaquetado y publicación para la lista completa de verificaciones.