Sandbox-Plugins sind standardmäßig isoliert. Um etwas über das Lesen und Schreiben des eigenen KV und Speichers hinaus zu tun, muss ein Plugin eine Fähigkeit 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, bekommt kein ctx.content, und eines, das network:request nicht deklariert hat, bekommt kein ctx.http.
Diese Seite behandelt, was jede Fähigkeit gewährt, wie die Sandbox sie durchsetzt und was nicht durchsetzbar ist.
Fähigkeiten deklarieren
Fähigkeiten befinden sich in emdash-plugin.jsonc, zusammen mit slug und dem Rest des Vertrauensvertrags:
{
"slug": "plugin-hello",
// ...Identität + Profil...
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
Deklariere nur, was das Plugin tatsächlich braucht. Fähigkeitsdeklarationen sind auch das, was der Marketplace den Betreibern im Einwilligungsdialog zeigt — zusätzliche Fähigkeiten sind Reibung bei der Installation und ein Sicherheitsflag bei Audits.
Fähigkeitsreferenz
| Fähigkeit | Gewährt Zugang zu |
|---|---|
content:read | ctx.content.get(), ctx.content.list() |
content:write | ctx.content.create(), ctx.content.update(), ctx.content.delete() (impliziert content:read) |
taxonomies:read | ctx.taxonomies.getAll(), ctx.taxonomies.getTerms(), ctx.taxonomies.getEntryTerms() |
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-Hooks (Transport-Provider) |
hooks.email-events:register | Erlaubt die Registrierung von email:beforeSend / email:afterSend-Hooks |
hooks.page-fragments:register | Erlaubt die Registrierung des page:fragments-Hooks (nur native Plugins) |
Einige wissenswerte Dinge:
- Implikationen.
content:writeimpliziert automatischcontent:read;media:writeimpliziertmedia:read;network:request:unrestrictedimpliziertnetwork:request. Du musst nicht beides auflisten. - Taxonomien sind eine separate, nur lesbare Oberfläche.
taxonomies:readgewährt Zugang zu Taxonomiedefinitionen, ihren Begriffen und den einem Eintrag zugewiesenen Begriffen überctx.taxonomies. Es ist unabhängig voncontent:read— deklariere beides, wenn das Plugin Inhalte und deren Klassifikation liest. Es gibt keinen Schreibzugriff auf Taxonomien von Plugins. network:request:unrestrictedexistiert 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, solltennetwork:request+allowedHostsverwenden.email:sendwird durch Konfiguration gesteuert, nicht nur durch die Fähigkeit. Ein Plugin kannemail:senddeklarieren, aberctx.emailwird nur befüllt, wenn ein anderes Plugin einenemail:deliver-Transport 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 prüft den Host der Request-URL gegen die Allowlist, bevor sie die Anfrage weiterleitet. Eine Anfrage an einen nicht deklarierten Host wirft innerhalb des Plugins eine Exception, 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). Vermeide es für Plugins, bei denen das Ziel Teil des Plugin-Designs ist — deklariere stattdessen network:request mit expliziten Hosts, damit der Einwilligungsdialog den Betreibern genau sagt, wohin das Plugin aufrufen wird.
Was die Sandbox durchsetzt
Wenn ein Sandbox-Runner aktiv ist, setzt die Laufzeit durch:
-
Fähigkeits-Gating. Die PluginContext-Factory befüllt
ctx.content,ctx.taxonomies,ctx.media,ctx.http,ctx.users,ctx.emailnur, wenn die entsprechende Fähigkeit deklariert ist. Eine Methode auf einer nicht deklarierten Fähigkeit aufzurufen 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 kein KV oder Storage-Collections eines anderen Plugins lesen, und es kann nur auf Storage-Collections zugreifen, die es im Manifest deklariert hat.
-
Netzwerkisolation. Direktes
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. Sandbox-Plugins sehen keine Umgebungsvariablen, kein Dateisystem und keine Plattform-Bindings — auch wenn dein Host-Worker sie hat. Die Plugin-Laufzeit ist ein sauberes Isolat mit nur der Bridge und den deklarierten Fähigkeiten.
-
Ressourcenlimits. Der Runner kann CPU-, Subrequest-, Wanduhr- und Speicherlimits pro Aufruf durchsetzen. Die genauen Limits hängen davon ab, welchen Runner du verwendest; der Cloudflare-Runner nutzt die Worker-Loader-Limits der Plattform (50ms CPU pro Aufruf, 10 Subrequests, 30 Sekunden Wanduhr, ~128MB Speicher). Der Node.js workerd-Runner (
@emdash-cms/sandbox-workerd) setzt Wanduhrzeit überPromise.racedurch; CPU- und Speicherlimits sind Cloudflare-Plattform-Features und werden von Standalone-workerd nicht durchgesetzt. Hooks, die die Limits des Runners überschreiten, werden abgebrochen; das EmDash-Hook-Timeout (timeoutin der Hook-Konfiguration) setzt darüber hinaus eine strengere Obergrenze durch.
Was die Sandbox nicht durchsetzt
Einige Dinge, die das Fähigkeitssystem nicht und nicht kann abdecken:
- Verhalten innerhalb einer gewährten Fähigkeit. Ein Plugin mit
content:writekann jeden Inhalt bearbeiten, nicht nur seinen eigenen. Fähigkeiten sind grobkörnig — sie sagen “dieses Plugin kann Inhalte schreiben”, nicht “dieses Plugin kann nur die Inhalte schreiben, die es erstellt hat.” Audit-Time-Review ist die einzige Überprüfung dessen, was ein Plugin tatsächlich innerhalb seiner Gewährung tut. - Betreibervertrauen auf Node.js. Wenn der konfigurierte Sandbox-Runner meldet, dass er nicht verfügbar ist (kein Cloudflare Worker Loader, kein Node-seitiger Runner installiert, usw.), werden
sandboxed: []-Plugins beim Start übersprungen. Du kannst sie inplugins: []verschieben, um sie im Prozess auszuführen — aber dann gibt es kein V8-Isolat, keine Ressourcenlimits, und das Plugin kannfetch()direkt aufrufen oder Umgebungsvariablen lesen. Behandle das als Vertrauen auf nativem Niveau. - Seitenkanäle. Timing, Log-Output und gespeicherte Daten sind für jeden sichtbar, der angemessenen Zugang zur Host-Umgebung hat. Verwende die Sandbox nicht als Vertraulichkeitsgrenze gegen den Betreiber, der sie ausführt.
Fähigkeitseinwilligung
Wenn ein Betreiber ein Sandbox-Plugin aus dem Marketplace installiert, zeigt EmDash einen Einwilligungsdialog mit den deklarierten Fähigkeiten. Updates, die Fähigkeiten hinzufügen — zum Beispiel ein Plugin, das vorher nur Inhalte gelesen hat und jetzt Netzwerkanfragen machen möchte — werden als Fähigkeits-Diff angezeigt und erfordern eine frische Genehmigung, bevor die neue Version wirksam wird.
Deshalb ist es wichtig, zusätzliche Fähigkeiten zu deklarieren, auch wenn du sie “vielleicht später brauchst”. Sie erscheinen als Reibung bei jeder Installation und jedem Update, und Sicherheitsaudits markieren Plugins, die mehr verlangen, als sie offensichtlich brauchen. Liste genau auf, was das Plugin verwendet, und füge neue Fähigkeiten in einer echten Version hinzu, wenn das Plugin sie tatsächlich zu nutzen beginnt.
Build-Time-Validierung
emdash-plugin bundle und emdash-plugin publish führen zusätzliche Prüfungen durch:
- Jede deklarierte Fähigkeit muss im erkannten Set sein (Tippfehler lassen den Build fehlschlagen).
network:requesterfordert eine nicht-leereallowedHosts;network:request:unrestrictederfordert, dass sie leer ist. Siehe die Manifest-Referenz.- Das gebündelte
backend.jskann keine Node.js-Built-ins importieren (fs,path,child_process, usw.) — Sandbox-Laufzeiten stellen sie nicht bereit.
Siehe Bündeln und veröffentlichen für die vollständige Liste der Prüfungen.