Sandboxed Plugins sind standardmäßig isoliert. Um etwas über das Lesen und Schreiben ihres eigenen KV und Storage hinaus zu tun, muss ein Plugin eine Capability deklarieren in seinem Manifest. Die Sandbox-Bridge steuert 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 in emdash-plugin.jsonc, zusammen mit slug und dem Rest des Trust Contract:
{
"slug": "plugin-hello",
// ...identity + profile...
"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 zeigt — zusätzliche Capabilities sind Reibung bei der Installation und ein Sicherheits-Flag bei Audits.
Capability-Referenz
| Capability | Gewährt Zugriff auf |
|---|---|
content:read | ctx.content.get(), ctx.content.list() |
content:write | ctx.content.create(), ctx.content.update(), ctx.content.delete() (impliziert content:read) |
media:read | ctx.media.get(), ctx.media.list() |
media:write | ctx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (impliziert media:read) |
network:request | ctx.http.fetch() — beschränkt auf allowedHosts |
network:request:unrestricted | ctx.http.fetch() ohne Host-Beschränkung (nur für benutzerkonfigurierte URLs) |
users:read | ctx.users.get(), ctx.users.getByEmail(), ctx.users.list() |
email:send | ctx.email.send() (erfordert ein konfiguriertes E-Mail-Provider-Plugin) |
hooks.email-transport:register | Erlaubt die Registrierung des exklusiven email:deliver Hook (Transport-Provider) |
hooks.email-events:register | Erlaubt die Registrierung der email:beforeSend / email:afterSend Hooks |
hooks.page-fragments:register | Erlaubt die Registrierung des page:fragments Hook (nur native Plugins) |
Ein paar wissenswerte Dinge:
- Implikationen.
content:writeimpliziert automatischcontent:read;media:writeimpliziertmedia:read;network:request:unrestrictedimpliziertnetwork:request. Sie müssen nicht beide auflisten. network:request:unrestrictedexistiert für benutzerkonfigurierte URLs. Ein Webhook-Plugin, bei dem der Betreiber die Ziel-URL eingibt, muss Hosts erreichen können, die nicht im Manifest stehen. Plugins, die immer bekannte APIs aufrufen, solltennetwork:request+allowedHostsverwenden.email:sendist durch Konfiguration gesteuert, nicht nur durch die Capability. Ein Plugin kannemail:senddeklarieren, aberctx.emailwird nur gefüllt, wenn ein anderes Plugin einenemail:deliverTransport registriert hat.
Netzwerk-Host-Allowlists
Plugins mit network:request können nur Hosts abrufen, die in allowedHosts aufgeführt 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 einen Fehler, 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 Folgendes durch:
-
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. -
Storage- und KV-Scoping. Jede Storage- und KV-Operation ist auf den Slug des Plugins beschränkt. Ein Plugin kann nicht das KV oder die Storage-Collections eines anderen Plugins lesen, und es kann nur auf Storage-Collections zugreifen, die es im Manifest deklariert hat.
-
Netzwerk-Isolation. Direkte
fetch()und andere Netzwerk-Primitive werden vom Runner blockiert. Der einzige Weg, das Netzwerk zu erreichen, istctx.http.fetch(), das durch die Host-Validierung der Bridge geht. -
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.
-
Ressourcen-Limits. Der Runner kann CPU-, Subrequest-, Wall-Clock- und Speicherlimits pro Aufruf durchsetzen. Die genauen Limits hängen davon ab, welchen Runner Sie verwenden; der Cloudflare-Runner verwendet die Limits des Plattform-Worker-Loaders (50ms CPU pro Aufruf, 10 Subrequests, 30 Sekunden Wall-Clock, ~128MB Speicher). Hooks, die die Limits des Runners überschreiten, werden abgebrochen; das EmDash-Hook-Timeout (
timeoutin der Hook-Konfiguration) setzt eine strengere Obergrenze darüber.
Was die Sandbox nicht durchsetzt
Ein paar Dinge, die das Capability-System nicht und nicht kann abdecken:
- Verhalten innerhalb einer gewährten Capability. Ein Plugin mit
content:writekann jeden Content bearbeiten, nicht nur seinen eigenen. Capabilities sind grob — sie sagen “dieses Plugin kann Content schreiben”, nicht “dieses Plugin kann nur den Content schreiben, den es erstellt hat”. Die Audit-Zeit-Überprüfung ist die einzige Kontrolle darüber, was ein Plugin tatsächlich innerhalb seiner Berechtigung tut. - Betreiber-Vertrauen auf Node.js. Wenn der konfigurierte Sandbox-Runner als nicht verfügbar gemeldet wird (kein Cloudflare Worker Loader, kein Node-seitiger Runner installiert usw.), werden
sandboxed: []Plugins beim Start übersprungen. Sie können sie inplugins: []verschieben, um sie In-Process auszuführen — aber dann gibt es kein V8-Isolat, keine Ressourcen-Limits, und das Plugin kannfetch()direkt aufrufen oder Umgebungsvariablen lesen. Behandeln Sie das als Native-Level-Vertrauen. - Seitenkanäle. Timing, Log-Output 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 mit den deklarierten Capabilities. Updates, die Capabilities hinzufügen — zum Beispiel ein Plugin, das zuvor nur Content gelesen hat und jetzt Netzwerk-Requests machen möchte — erscheinen als Capability-Diff und erfordern eine neue Genehmigung, bevor die neue Version in Kraft tritt.
Deshalb ist es wichtig, zusätzliche Capabilities zu deklarieren, auch wenn Sie sie “vielleicht später verwenden”. Sie erscheinen als Reibung bei jeder Installation und jedem Update, und Sicherheits-Audits markieren Plugins, die nach mehr fragen, als sie offensichtlich benötigen. Listen Sie genau 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 anerkannten Set sein (Tippfehler lassen den Build fehlschlagen).
network:requesterfordert ein nicht-leeresallowedHosts;network:request:unrestrictederfordert, dass es leer ist. Siehe die Manifest-Referenz.- Das gebündelte
backend.jskann keine Node.js-Built-ins importieren (fs,path,child_processusw.) — Sandbox-Runtimes stellen sie nicht bereit.
Siehe Bundling und Publishing für die vollständige Liste der Prüfungen.