Capabilities et sécurité

Sur cette page

Les plugins en sandbox sont isolés par défaut. Pour faire quoi que ce soit au-delà de la lecture et de l’écriture de leur propre KV et stockage, un plugin doit déclarer une capability sur son descripteur. Le pont du sandbox contrôle chaque API fournie par l’hôte en fonction de ces déclarations — un plugin qui n’a pas déclaré content:read n’obtient pas de ctx.content, et un qui n’a pas déclaré network:request n’obtient pas ctx.http.

Cette page couvre ce que chaque capability accorde, comment le sandbox les applique et ce qui n’est pas applicable.

Déclarer des capabilities

Les capabilities résident sur le descripteur (le fichier importé par astro.config.mjs), aux côtés de id, version et 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"],
	};
}

Déclarez uniquement ce dont le plugin a réellement besoin. Les déclarations de capabilities sont également ce que le marketplace montre aux opérateurs de site dans le dialogue de consentement — les capabilities supplémentaires sont des frictions au moment de l’installation et un signal de sécurité dans les audits.

Référence des capabilities

CapabilityAccorde l’accès à
content:readctx.content.get(), ctx.content.list()
content:writectx.content.create(), ctx.content.update(), ctx.content.delete() (implique content:read)
media:readctx.media.get(), ctx.media.list()
media:writectx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (implique media:read)
network:requestctx.http.fetch() — restreint à allowedHosts
network:request:unrestrictedctx.http.fetch() sans restriction d’hôte (uniquement pour les URLs configurées par l’utilisateur)
users:readctx.users.get(), ctx.users.getByEmail(), ctx.users.list()
email:sendctx.email.send() (nécessite un plugin fournisseur d’email configuré)
hooks.email-transport:registerPermet d’enregistrer le hook exclusif email:deliver (fournisseurs de transport)
hooks.email-events:registerPermet d’enregistrer les hooks email:beforeSend / email:afterSend
hooks.page-fragments:registerPermet d’enregistrer le hook page:fragments (plugins natifs uniquement)

Quelques points à connaître :

  • Implications. content:write implique automatiquement content:read ; media:write implique media:read ; network:request:unrestricted implique network:request. Vous n’avez pas besoin de lister les deux.
  • network:request:unrestricted existe pour les URLs configurées par l’utilisateur. Un plugin webhook où l’opérateur saisit l’URL de destination doit atteindre des hôtes qui ne sont pas dans le manifeste. Les plugins qui appellent toujours des API connues doivent utiliser network:request + allowedHosts.
  • email:send est contrôlé par la configuration, pas seulement par la capability. Un plugin peut déclarer email:send, mais ctx.email ne sera peuplé que si un autre plugin a enregistré un transport email:deliver.

Listes d’autorisation d’hôtes réseau

Les plugins avec network:request ne peuvent récupérer que les hôtes listés dans allowedHosts. Les jokers sont pris en charge pour les sous-domaines :

capabilities: ["network:request"],
allowedHosts: [
	"api.example.com",          // hôte exact
	"*.cdn.example.com",        // tout sous-domaine de cdn.example.com
],

Le pont vérifie l’hôte de l’URL de requête par rapport à la liste d’autorisation avant de transmettre la requête. Une requête vers un hôte qui n’a pas été déclaré lance à l’intérieur du plugin sans jamais quitter le sandbox.

network:request:unrestricted ignore complètement la vérification de la liste d’autorisation. Il est destiné aux plugins où l’opérateur configure l’URL de destination à l’exécution (expéditeurs de webhook, redirecteurs HTTP génériques). Évitez-le pour les plugins où la destination fait partie de la conception du plugin — déclarez plutôt network:request avec des hôtes explicites, afin que le dialogue de consentement indique aux opérateurs exactement où le plugin va appeler.

Ce que le sandbox applique

Lorsqu’un exécuteur de sandbox est actif, le runtime applique :

  1. Contrôle des capabilities. La factory PluginContext ne peuple ctx.content, ctx.media, ctx.http, ctx.users, ctx.email que lorsque la capability correspondante est déclarée. Appeler une méthode sur une capability non déclarée n’est pas possible — il n’y a pas d’objet là.

  2. Portée du stockage et KV. Chaque opération de stockage et KV est limitée à l’id du plugin. Un plugin ne peut pas lire le KV d’un autre plugin ou ses collections de stockage, et il ne peut accéder qu’aux collections de stockage qu’il a déclarées sur le descripteur.

  3. Isolation réseau. Le fetch() direct et d’autres primitives réseau sont bloqués par l’exécuteur. Le seul moyen d’atteindre le réseau est ctx.http.fetch(), qui passe par la validation d’hôte du pont.

  4. Pas de liaisons d’hôte. Les plugins en sandbox ne voient pas les variables d’environnement, le système de fichiers ou toute liaison de plateforme — même si votre worker hôte les a. Le runtime du plugin est un isolat propre avec seulement le pont et les capabilities déclarées.

  5. Limites de ressources. L’exécuteur peut appliquer des limites de CPU, sous-requêtes, temps réel et mémoire par invocation. Les limites exactes dépendent de l’exécuteur que vous utilisez ; l’exécuteur Cloudflare utilise les limites du Worker Loader de la plateforme (50ms de CPU par invocation, 10 sous-requêtes, 30 secondes de temps réel, ~128MB de mémoire). Les hooks qui dépassent les limites de l’exécuteur sont interrompus ; le timeout du hook EmDash (timeout dans la configuration du hook) applique un plafond plus strict en plus de cela.

Ce que le sandbox n’applique pas

Quelques éléments que le système de capabilities ne peut pas couvrir :

  • Comportement dans une capability accordée. Un plugin avec content:write peut modifier n’importe quel contenu, pas seulement le sien. Les capabilities sont grossières — elles disent “ce plugin peut écrire du contenu”, pas “ce plugin ne peut écrire que le contenu qu’il a créé”. L’examen au moment de l’audit est le seul contrôle sur ce qu’un plugin fait réellement dans son autorisation.
  • Confiance de l’opérateur sur Node.js. Lorsque l’exécuteur de sandbox configuré signale qu’il n’est pas disponible (pas de Cloudflare Worker Loader, pas d’exécuteur côté Node installé, etc.), les plugins sandboxed: [] sont ignorés au démarrage. Vous pouvez les déplacer dans plugins: [] pour les exécuter en processus — mais alors il n’y a pas d’isolat V8, pas de limites de ressources, et le plugin peut appeler fetch() directement ou lire les variables d’environnement. Traitez cela comme une confiance de niveau natif.
  • Canaux latéraux. Le timing, la sortie des logs et les données stockées sont tous visibles pour quiconque ayant un accès approprié à l’environnement hôte. N’utilisez pas le sandbox comme une limite de confidentialité contre l’opérateur qui l’exécute.

Consentement des capabilities

Lorsqu’un opérateur installe un plugin en sandbox depuis le marketplace, EmDash affiche un dialogue de consentement listant les capabilities déclarées. Les mises à jour qui ajoutent des capabilities — par exemple, un plugin qui ne lisait auparavant que du contenu veut maintenant faire des requêtes réseau — apparaissent comme un diff de capabilities et nécessitent une nouvelle approbation avant que la nouvelle version ne prenne effet.

C’est pourquoi déclarer des capabilities supplémentaires est important même si vous “pourriez les utiliser plus tard”. Elles apparaissent comme des frictions à chaque installation et mise à jour, et les audits de sécurité signalent les plugins qui demandent plus qu’ils n’ont manifestement besoin. Listez exactement ce que le plugin utilise, et ajoutez de nouvelles capabilities dans une vraie version lorsque le plugin commence réellement à les utiliser.

Validation au moment du packaging

emdash plugin bundle et emdash plugin publish effectuent des vérifications supplémentaires :

  • Chaque capability déclarée doit être dans l’ensemble connu (les fautes de frappe échouent la construction).
  • Un plugin qui déclare network:request sans remplir allowedHosts déclenche un avertissement — déclarez des hôtes ou passez à network:request:unrestricted et documentez pourquoi.
  • Les noms de capabilities dépréciés déclenchent des avertissements pendant bundle/validate et un échec dur sur publish.
  • Le backend.js packagé ne peut pas importer les modules intégrés de Node.js (fs, path, child_process, etc.) — les runtimes de sandbox ne les fournissent pas.

Consultez Packaging et publication pour la liste complète des vérifications.