Suche

↑↓ auswählenEnter öffnenErweiterte Suche

Hintergrund

Das Plugin-Problem: Was die EmDash-Sandbox schützt und was nicht

EmDash verspricht sichere Plugins dank Sandbox. Ich habe ein Mess-Plugin lokal und auf Cloudflare laufen lassen: was die Sandbox wirklich sperrt, wo ihre Limits greifen und warum gerade Agentur-Plugins meist ausserhalb laufen.

Von Aktualisiert am 9 Min. Lesezeitgeprüft mit 1.1 · aktuell 1.2

Geprüft mit EmDash 1.1.0 auf Cloudflare Workers (Paid) und lokal mit `astro dev` am 5. Oktober 2026. Überarbeitet für EmDash 1.2.0 am 7. Oktober 2026. Was sich geändert hat, steht am Ende des Artikels.

Wer WordPress-Sites betreut, kennt die Meldungen über Lücken in verbreiteten Plugins und die Updates, die danach über alle Kundenseiten müssen. Als Cloudflare EmDash im April vorgestellt hat, hat mich deshalb ein Satz sofort hellhörig gemacht: EmDash sei der Nachfolger von WordPress, „der das Plugin-Sicherheitsproblem löst“ (Ankündigung). Jedes Plugin läuft in einer eigenen Sandbox und darf nur, was es vorher angemeldet hat.

Im Leitfaden für Agenturen habe ich geschrieben, dass das für Agenturen weniger bringt, als es zuerst klingt. Das wollte ich genauer wissen, und als Maintainer bei EmDash reicht mir die Doku dafür nicht. Also habe ich ein kleines Plugin gebaut, das sich absichtlich danebenbenimmt: Es macht zu viele Aufrufe, rechnet zu lange und versucht, an Netzwerk und Umgebungsvariablen vorbeizukommen. Laufen lassen habe ich es zuerst lokal und dann auf einer Wegwerf-Site bei Cloudflare.

Das Ergebnis hat zwei Seiten. Die Sandbox hält dicht, und zwar genau so streng, wie die Doku es beschreibt. Nur laufen ausgerechnet die Plugins, die Agenturen am häufigsten selbst bauen, gar nicht in ihr.

Zwei Arten von Plugins

EmDash kennt zwei Formate (Doku). Plugins in der Sandbox laufen isoliert, lassen sich mit einem Klick aus der Registry installieren und melden ihre Rechte in einem Manifest an. Native Plugins laufen im selben Prozess wie die Website, kommen über npm und brauchen für jede Installation und jedes Update ein neues Deployment.

Sandbox

Nativ

Läuft

isoliert

im selben Prozess wie die Site

Rechte

von der Sandbox erzwungen

angemeldet, aber nicht abgesichert

Limits pro Aufruf

Rechenzeit, Aufrufe, Laufzeit

keine

Installation

ein Klick in der Registry

npm, Konfiguration, neues Deployment

Admin-Oberfläche

Block Kit (JSON)

React oder Block Kit

Eigene Blöcke im Editor

nein

ja

HTML oder Skripte auf der Site

nein

ja

Die Doku empfiehlt die Sandbox, solange ein Plugin nichts braucht, das nur nativ geht. Über native Plugins schreibt sie selbst sehr deutlich: „Native Plugins haben denselben Zugriff wie die Website.“ Ein Fehler darin kann den ganzen Prozess lahmlegen.

Was nur ohne Sandbox geht

Nativ laufen muss ein Plugin, sobald es eigene React-Seiten oder Widgets im Admin braucht, eigene Blocktypen im Editor mitbringt oder HTML, Skripte und Stylesheets auf den öffentlichen Seiten ausgibt.

Schau dir die Liste mit den Plugins im Kopf an, die Kunden typischerweise bei einer Agentur bestellen: ein eigenes Feld im Editor, ein Call-to-Action-Block, Tracking, ein Chat-Widget, ein Cookie-Banner. Das landet fast alles in dieser Liste. Selbst EmDashs eigene Plugins für Formulare und Embeds sind nativ. Für diesen Teil gilt also dasselbe Vertrauensmodell wie bei WordPress: Du musst dem Code vertrauen, weil ihn nichts einschränkt.

Die Sandbox muss man einschalten

Die Sandbox ist nicht von selbst aktiv. Auf Cloudflare braucht sie den bezahlten Workers-Plan, ein Binding namens LOADER und einen Eintrag in der Astro-Konfiguration (Doku). Der Plan kostet ab 5 Dollar im Monat pro Cloudflare-Konto, nicht pro Site, und enthält 1000 Sandbox-Worker im Monat (Preise). Für die Sicherheit, die die Sandbox bringt, ist das wenig Geld. In der offiziellen Blog-Vorlage ist das Binding auskommentiert, und den Eintrag in der Astro-Konfiguration gibt es gar nicht. Für meinen Test habe ich beides ergänzt:

// wrangler.jsonc
"worker_loaders": [{ "binding": "LOADER" }],
// astro.config.mjs
import { d1, r2, sandbox } from "@emdash-cms/cloudflare";
import limitProbe from "limit-probe";

emdash({
	database: d1({ binding: "DB", session: "auto" }),
	storage: r2({ binding: "MEDIA" }),
	sandboxRunner: sandbox(),
	sandboxed: [limitProbe],
});

Beim Anlegen eines neuen Projekts gibt es dafür die Option --sandboxed-plugins. In Version 1.1.0 schaltet sie aber nur das Binding frei, und daran ändert auch 1.2.0 nichts. Den Eintrag in der Astro-Konfiguration musst du trotzdem selbst ergänzen. Sonst bleibt die Sandbox aus, und im Admin fehlt das Plugin-Verzeichnis, obwohl der Assistent „Enabled sandboxed plugins“ meldet.

Fehlt eines davon, lädt EmDash Plugins aus der Sandbox-Liste gar nicht, und eine Installation aus der Registry schlägt fehl. Die Doku nennt einen Ausweg: Du kannst so ein Plugin in die normale Plugin-Liste verschieben, dann läuft es im selben Prozess. Damit fällt aber jede Isolation weg, und die Doku sagt selbst, du sollst es dann behandeln wie ein natives Plugin.

Auf einem normalen Node.js-Server läuft die Sandbox über workerd. Das zugehörige Paket steht bei Version 0.9.3, und dort begrenzt die Sandbox laut Doku nur die Laufzeit auf 30 Sekunden, nicht die Rechenzeit oder die Anzahl Aufrufe.

Mein Test-Plugin

Ausgangspunkt war die Blog-Vorlage von EmDash 1.1.0 mit eingeschalteter Sandbox. Mit dem offiziellen Werkzeug habe ich ein Plugin namens limit-probe angelegt. Es meldet zwei Rechte an, content:read und network:request, und als einzigen erlaubten Host example.com. Jede Route prüft eine Grenze, zum Beispiel die Anzahl Speicheraufrufe:

kv: {
	public: true,
	handler: async (routeCtx, ctx) => {
		const n = num(routeCtx.input, "n", 20);
		for (let i = 1; i <= n; i++) {
			try {
				await ctx.kv.set(`probe:${i}`, i);
			} catch (e) {
				return { calls: n, okCalls: i - 1, failedAt: i, error: errorText(e) };
			}
		}
		return { calls: n, okCalls: n };
	},
},

Zuerst lief alles lokal mit astro dev, danach auf einer Wegwerf-Site in meinem Cloudflare-Konto mit Workers Paid. Die habe ich nach dem Test samt Datenbank und Speicher wieder gelöscht.

Test

Lokal (astro dev)

Cloudflare

10 Speicheraufrufe

ok

ok

11 Speicheraufrufe

ok

Aufruf scheitert

50 Speicheraufrufe

ok

Aufruf scheitert

11 Inhaltsabfragen

ok

Aufruf scheitert

Rechenarbeit von rund 10 ms

ok

ok

Rechenarbeit von rund 25 ms

ok

meist ok, einmal von fünf Abbruch

Rechenarbeit von rund 50 ms und mehr

ok

Abbruch

Angemeldeter Host example.com

HTTP 200

HTTP 200

Nicht angemeldeter Host

gesperrt

gesperrt

Normales fetch()

gesperrt

gesperrt

Umgebungsvariablen

nicht sichtbar

nicht sichtbar

So sahen die Antworten der Test-Site auf Cloudflare aus. Die Site gibt es nicht mehr, darum habe ich das Terminal mit den Antworten aus meinem Testprotokoll nachgestellt.

Ein Terminal mit vier Aufrufen an das Test-Plugin auf Cloudflare. Zehn Speicheraufrufe gehen durch. Bei elf scheitert der elfte mit „Too many subrequests by single Worker invocation“. Der Netzwerktest liefert für example.com HTTP 200, für www.cloudflare.com „Host not allowed“ und für das normale fetch() eine Sperre. Die Rechenarbeit von rund 50 Millisekunden bricht mit „Worker exceeded CPU time limit“ ab.
Nachgestellt aus meinem Testprotokoll vom 5. Oktober 2026, die Adresse der Test-Site ist gekürzt.

Beim elften Aufruf ist Schluss

Auf Cloudflare gingen genau zehn Aufrufe durch. Der elfte brach mit „Too many subrequests by single Worker invocation“ ab, egal ob das Plugin in seinen eigenen Speicher schrieb oder über ctx.content Inhalte abfragte. Die Fehlermeldung verweist auf eine Einstellung in Wrangler, aber für Plugins in der Sandbox ist das Limit laut Doku fest.

Zehn Aufrufe klingen nach wenig, und für manche Plugins sind sie das auch. Ein Entwickler hat dasselbe unabhängig von mir gemessen und dokumentiert (Discussion #3869). Sein Shop-Plugin braucht pro Checkout zwischen 21 und 84 Speicheraufrufe und passt damit nicht in die Sandbox. Er hat auch herausgefunden, dass ein Sammelaufruf wie getMany nur als ein Aufruf zählt, was beim Umbau hilft.

Rechenzeit, und wie ich mich selbst ausgetrickst habe

Bei der Rechenzeit bin ich zuerst über meinen eigenen Test gestolpert. Meine erste Version hat einfach so lange gerechnet, bis eine bestimmte Zeit vergangen war. Auf Cloudflare lief sie endlos. Workers hält Date.now() während laufendem Code an, als Schutz gegen Timing-Angriffe, und für meine Schleife verging deshalb nie Zeit. Danach habe ich eine feste Menge Rechenarbeit genommen und lokal gemessen, wie lange sie dauert.

Mit ein paar Sekunden Pause zwischen den Versuchen ging Arbeit von rund 10 Millisekunden immer durch. Bei rund 25 Millisekunden brach einer von fünf Versuchen ab, und ab rund 50 Millisekunden scheiterte jeder Versuch mit „Worker exceeded CPU time limit“. Schickte ich die Anfragen direkt hintereinander, streuten die Ergebnisse deutlich stärker. Ich würde deshalb mit klar weniger als den 50 Millisekunden aus der Doku planen. Bilder bearbeiten, grosse Dateien parsen oder lange Texte umwandeln gehört nicht in ein Plugin in der Sandbox.

Lokal merkst du nichts davon

Das ist für mich der heikelste Punkt. Lokal liefen 50 Aufrufe und eine halbe Sekunde Rechenzeit ohne jede Warnung durch. Die Doku sagt das zwar, aber eher versteckt im Kapitel übers Testen (Doku): Lokal werden die Limits von Cloudflare nicht nachgebildet. Ein Plugin, das in der Entwicklung einwandfrei läuft, kann also erst nach dem Deployment scheitern, beim Kunden. Alles, was viele Aufrufe macht oder rechnet, würde ich deshalb vorher auf einer Vorschau-Umgebung bei Cloudflare testen.

Die Isolation hält

Der Rest war erfreulich. Einen nicht angemeldeten Host lehnte die Sandbox mit „Host not allowed“ ab, das normale fetch() ist komplett gesperrt, und weder process noch Umgebungsvariablen sind sichtbar. Das galt lokal genauso wie auf Cloudflare. Ein Plugin aus der Registry, das heimlich Daten an einen Server schicken will, den es nicht angemeldet hat, kommt in der Sandbox nicht weit.

Was die Sandbox nicht abdeckt

Die Sandbox prüft, ob ein Plugin ein Recht hat, aber nicht, was es damit macht. Die Doku zählt die Grenzen offen auf (Doku). Die Rechte sind grob: Ein Plugin mit content:write darf alle Inhalte ändern, nicht nur seine eigenen. Freigeben kannst du die Rechte einer Version nur als Ganzes, eine Möglichkeit, nur einen Teil zu erlauben, habe ich in der Doku nicht gefunden. Schreibt ein Plugin in einen Eintrag, hält es die Bearbeitungssperre einer Redakteurin nicht auf. Und in der Sandbox werden Priorität und Reihenfolge von Hooks nicht beachtet.

Dazu kommt die Registry. Die Moderation prüft Name, Beschreibung, Links und Bilder eines Plugins, aber nicht den Code (Ankündigung zu 1.0). Signaturen beweisen, von wem ein Plugin stammt und dass es unterwegs nicht verändert wurde, aber nicht, dass es harmlos ist. Die Doku rät deshalb selbst, nur Code von Herausgebern zu installieren, denen du vertraust.

Was ich daraus für Agentur-Plugins mitnehme

Für Plugins von Drittanbietern ist die Sandbox ein echter Fortschritt gegenüber WordPress, wo jedes Plugin grundsätzlich alles darf. Ein Formular-Plugin aus der Registry kann keine Daten an einen Server schicken, den es nicht vorher angemeldet hat, und nicht an deine Schlüssel kommen. Bei WordPress gibt es nichts Vergleichbares.

Bei den eigenen Plugins sieht es anders aus. Was im Editor oder auf der Website etwas anzeigt, läuft nativ, und was in der Sandbox laufen soll, muss mit zehn Aufrufen und wenigen Millisekunden Rechenzeit auskommen. Grössere Aufgaben wie Importe, Newsletter-Versand oder Lagerbestände musst du in kleine Portionen teilen und über Cron verteilen, denn eine Warteschlange für Hintergrundjobs gibt es in EmDash noch nicht.

Wenn ich heute eine EmDash-Site für einen Kunden aufsetzen würde, würde ich die Sandbox von Anfang an einschalten. Die 5 Dollar für Workers Paid und die beiden Einträge in der Konfiguration sind ein kleiner Preis dafür. Plugins von fremden Herausgebern kämen bei mir nur in die Sandbox, denn ein natives Plugin von irgendwem ist dasselbe Risiko wie ein WordPress-Plugin. Bei eigenen Plugins würde ich vor der ersten Zeile Code entscheiden, wo sie laufen sollen, und bei der Sandbox die Aufrufe pro Anfrage zählen. Getestet wird auf einer Vorschau bei Cloudflare, nicht nur lokal. Und native Plugins bekommen dieselbe Sorgfalt wie bisher PHP-Code: Code-Review, Abhängigkeiten im Blick und Updates über alle Sites.

Fazit

Die Sandbox von EmDash funktioniert, und zwar genau so streng, wie die Doku es beschreibt. Für Plugins aus der Registry ist sie der wichtigste Grund, warum EmDash bei der Sicherheit besser dasteht als WordPress.

Den Slogan vom gelösten Plugin-Problem würde ich für Agenturen trotzdem nur zur Hälfte unterschreiben. Die Plugins, die wir am häufigsten selbst bauen, laufen ohne Sandbox, und die Sandbox selbst ist erst aktiv, wenn du sie bewusst einschaltest. Die paar Dollar im Monat dafür lohnen sich. Wer das weiss und seine eigenen Plugins entsprechend plant und prüft, hat trotzdem eine deutlich kleinere Angriffsfläche als mit Dutzenden WordPress-Plugins aus unterschiedlichsten Quellen.

Wie EmDash im Rest gegen WordPress abschneidet, steht im Leitfaden für Agenturen. Und wenn du selbst gemessen hast und auf andere Zahlen kommst, schreib es mir in die Kommentare.

Änderungen an diesem Artikel

  • : Für 1.2.0 nachgeprüft: --sandboxed-plugins ergänzt die Astro-Konfiguration weiterhin nicht, und das Paket für Node.js steht bei 0.9.3.

Über den Autor

Kevin Kyburz

Kevin Kyburz

Seit 20 Jahren im Netz und noch immer nicht fertig damit. Kevin Kyburz führt die Webagentur this:matters, baut Websites mit WordPress und EmDash und arbeitet als Maintainer an EmDash mit. Als Schweizer schreibt er „gross“ statt „groß“, und zwar mit Absicht.

Kommentare

No comments yet

Erstkommentare erscheinen nach unserer Freigabe. Was mit deinen Angaben passiert