Capabilities und Sicherheit

Auf dieser Seite

Sandboxed Plugins sind standardmäßig isoliert. Um etwas über das Lesen und Schreiben ihres eigenen KV und Speichers hinaus zu tun, muss ein Plugin eine Capability deklarieren auf seinem Descriptor. Die Sandbox-Bridge sperrt jede vom Host bereitgestellte API basierend auf diesen Deklarationen – ein Plugin, das content:read nicht deklariert hat, erhält kein ctx.content, und eines, das network:request nicht deklariert hat, erhält kein ctx.http.

Diese Seite behandelt, was jede Capability gewährt, wie die Sandbox sie durchsetzt und was nicht durchsetzbar ist.

Capabilities deklarieren

Capabilities befinden sich auf dem Descriptor (der Datei, die von astro.config.mjs importiert wird), neben id, version und 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"],
	};
}

Deklarieren Sie nur, was das Plugin tatsächlich benötigt. Capability-Deklarationen sind auch das, was der Marketplace Site-Betreibern im Zustimmungsdialog anzeigt – zusätzliche Capabilities sind Reibung zum Installationszeitpunkt und ein Sicherheitsflag bei Audits.

Capability-Referenz

CapabilityGewährt Zugriff auf
content:readctx.content.get(), ctx.content.list()
content:writectx.content.create(), ctx.content.update(), ctx.content.delete() (impliziert content:read)
media:readctx.media.get(), ctx.media.list()
media:writectx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (impliziert media:read)
network:requestctx.http.fetch() — beschränkt auf allowedHosts
network:request:unrestrictedctx.http.fetch() ohne Host-Beschränkung (nur für benutzerkonfigurierte URLs)
users:readctx.users.get(), ctx.users.getByEmail(), ctx.users.list()
email:sendctx.email.send() (erfordert ein konfiguriertes E-Mail-Provider-Plugin)
hooks.email-transport:registerErlaubt das Registrieren des exklusiven email:deliver Hooks (Transport-Provider)
hooks.email-events:registerErlaubt das Registrieren von email:beforeSend / email:afterSend Hooks
hooks.page-fragments:registerErlaubt das Registrieren des page:fragments Hooks (nur native Plugins)

Ein paar wissenswerte Dinge:

  • Implikationen. content:write impliziert automatisch content:read; media:write impliziert media:read; network:request:unrestricted impliziert network:request. Sie müssen nicht beide auflisten.
  • network:request:unrestricted existiert für benutzerkonfigurierte URLs. Ein Webhook-Plugin, bei dem der Betreiber die Ziel-URL eingibt, muss Hosts erreichen, die nicht im Manifest stehen. Plugins, die immer bekannte APIs aufrufen, sollten network:request + allowedHosts verwenden.
  • email:send wird durch Konfiguration gesperrt, nicht nur durch die Capability. Ein Plugin kann email:send deklarieren, aber ctx.email wird nur gefüllt, wenn ein anderes Plugin einen email:deliver Transport registriert hat.

Network Host Allowlists

Plugins mit network:request können nur Hosts abrufen, die in allowedHosts aufgelistet sind. Wildcards werden für Subdomains unterstützt:

capabilities: ["network:request"],
allowedHosts: [
	"api.example.com",          // exakter Host
	"*.cdn.example.com",        // jede Subdomain von cdn.example.com
],

Die Bridge überprüft den Host der Request-URL gegen die Allowlist, bevor sie die Anfrage weiterleitet. Eine Anfrage an einen Host, der nicht deklariert wurde, wirft innerhalb des Plugins, ohne jemals die Sandbox zu verlassen.

network:request:unrestricted überspringt die Allowlist-Prüfung vollständig. Es ist für Plugins gedacht, bei denen der Betreiber die Ziel-URL zur Laufzeit konfiguriert (Webhook-Sender, generische HTTP-Forwarder). Vermeiden Sie es für Plugins, bei denen das Ziel Teil des Plugin-Designs ist – deklarieren Sie stattdessen network:request mit expliziten Hosts, damit der Zustimmungsdialog den Betreibern genau sagt, wohin das Plugin aufrufen wird.

Was die Sandbox durchsetzt

Wenn ein Sandbox-Runner aktiv ist, setzt die Runtime durch:

  1. Capability-Gating. Die PluginContext-Factory füllt nur ctx.content, ctx.media, ctx.http, ctx.users, ctx.email, wenn die entsprechende Capability deklariert ist. Das Aufrufen einer Methode auf einer nicht deklarierten Capability ist nicht möglich – es gibt dort kein Objekt.

  2. Storage- und KV-Scoping. Jede Storage- und KV-Operation ist auf die ID des Plugins beschränkt. Ein Plugin kann nicht das KV eines anderen Plugins oder dessen Storage-Collections lesen, und es kann nur auf Storage-Collections zugreifen, die es auf dem Descriptor deklariert hat.

  3. Network-Isolation. Direktes fetch() und andere Netzwerk-Primitiven werden vom Runner blockiert. Der einzige Weg, das Netzwerk zu erreichen, ist ctx.http.fetch(), das durch die Host-Validierung der Bridge geht.

  4. Keine Host-Bindings. Sandboxed Plugins sehen keine Umgebungsvariablen, das Dateisystem oder irgendwelche Plattform-Bindings – selbst wenn Ihr Host-Worker sie hat. Die Plugin-Runtime ist ein sauberes Isolat mit nur der Bridge und den deklarierten Capabilities.

  5. Ressourcenlimits. Der Runner kann CPU-, Subrequest-, Wall-Clock- und Memory-Limits pro Aufruf durchsetzen. Die genauen Limits hängen davon ab, welchen Runner Sie verwenden; der Cloudflare-Runner verwendet die Worker Loader-Limits der Plattform (50ms CPU pro Aufruf, 10 Subrequests, 30 Sekunden Wall-Clock, ~128MB Memory). Hooks, die die Limits des Runners überschreiten, werden abgebrochen; das EmDash-Hook-Timeout (timeout in der Hook-Konfiguration) setzt eine strengere Obergrenze darauf.

Was die Sandbox nicht durchsetzt

Ein paar Dinge, die das Capability-System nicht abdecken kann:

  • Verhalten innerhalb einer gewährten Capability. Ein Plugin mit content:write kann jeden Inhalt bearbeiten, nicht nur seinen eigenen. Capabilities sind grob – sie sagen “dieses Plugin kann Inhalte schreiben”, nicht “dieses Plugin kann nur die Inhalte schreiben, die es erstellt hat”. Audit-Zeit-Review ist die einzige Überprüfung dessen, was ein Plugin tatsächlich innerhalb seiner Berechtigung tut.
  • Betreibervertrauen auf Node.js. Wenn der konfigurierte Sandbox-Runner als nicht verfügbar meldet (kein Cloudflare Worker Loader, kein Node-seitiger Runner installiert usw.), werden sandboxed: [] Plugins beim Start übersprungen. Sie können sie in plugins: [] verschieben, um sie in-process auszuführen – aber dann gibt es kein V8-Isolat, keine Ressourcenlimits, und das Plugin kann fetch() direkt aufrufen oder Umgebungsvariablen lesen. Behandeln Sie das als native-level Vertrauen.
  • Side Channels. Timing, Log-Ausgabe und gespeicherte Daten sind alle für jeden mit entsprechendem Zugriff auf die Host-Umgebung sichtbar. Verwenden Sie die Sandbox nicht als Vertraulichkeitsgrenze gegen den Betreiber, der sie ausführt.

Capability-Zustimmung

Wenn ein Betreiber ein sandboxed Plugin aus dem Marketplace installiert, zeigt EmDash einen Zustimmungsdialog an, der die deklarierten Capabilities auflistet. Updates, die Capabilities hinzufügen – zum Beispiel ein Plugin, das zuvor nur Inhalte gelesen hat, jetzt aber Netzwerkanfragen stellen möchte – erscheinen als Capability-Diff und erfordern eine neue Genehmigung, bevor die neue Version wirksam wird.

Deshalb ist es wichtig, zusätzliche Capabilities zu deklarieren, auch wenn Sie sie “später vielleicht verwenden”. Sie tauchen bei jeder Installation und jedem Update als Reibung auf, und Sicherheitsaudits flaggen Plugins, die mehr verlangen, als sie offensichtlich benötigen. Listen Sie genau das auf, was das Plugin verwendet, und fügen Sie neue Capabilities in einer echten Version hinzu, wenn das Plugin sie tatsächlich zu verwenden beginnt.

Bundle-Zeit-Validierung

emdash plugin bundle und emdash plugin publish führen zusätzliche Prüfungen durch:

  • Jede deklarierte Capability muss im bekannten Set sein (Tippfehler schlagen den Build fehl).
  • Ein Plugin, das network:request deklariert, ohne allowedHosts zu füllen, löst eine Warnung aus – deklarieren Sie Hosts oder wechseln Sie zu network:request:unrestricted und dokumentieren Sie warum.
  • Veraltete Capability-Namen lösen Warnungen während bundle/validate aus und ein hartes Fail bei publish.
  • Das gebündelte backend.js kann keine Node.js-Built-ins importieren (fs, path, child_process usw.) – Sandbox-Runtimes stellen sie nicht bereit.

Siehe Bundling und Publishing für die vollständige Liste der Prüfungen.