Cloudflare’s EmDash 1.0 Turns the WordPress Plugin Problem Into a Permissions Decision
EmDash 1.0 sandboxes plugins and builds its registry on signed AT Protocol records, but a launch-day look at 13 listings shows the protection still depends on where the sandbox runs and what site...
When Cloudflare introduced EmDash on April 1 as the “spiritual successor to WordPress,” it acknowledged that some in the industry wondered whether it was an April Fools’ joke. On September 28 the company answered with EmDash 1.0: a free, MIT-licensed CMS built on Astro, launched alongside a decentralized plugin registry. The registry promises two things the WordPress plugin model does not offer. Plugins run in a sandbox, and no marketplace owner controls a publisher’s identity or release history.
Table Of Content
- The Problem EmDash Is Aiming At
- What EmDash 1.0 Actually Changes
- Plugins That Must Declare What They Need
- A Registry That Does Not Own Its Publishers
- Where the Boundary Is Weaker Than the Headline
- The Same Manifest Gets Different Guarantees on Different Runtimes
- Coarse Capabilities Hand the Judgment Back to the Operator
- Provenance Is a Publisher’s Choice
- A Launch-Day Look at the Registry
- What Site Owners and Platforms Should Take From It
That design is a real step beyond how WordPress treats third-party code, and it targets the part of the platform that produces most of its reported vulnerabilities. But EmDash’s own documentation, the warnings that ship with Cloudflare’s workerd runtime, and a query of the live registry on launch day all point to the same caveat. “Secure” here is a set of conditions, not a property. It depends on where the sandbox runs, on what a site owner approves in a consent dialog, and on whether each publisher chooses to prove how a release was built. The plugin problem has been moved into a permissions decision that a person still has to make.
The Problem EmDash Is Aiming At
Cloudflare describes the WordPress model plainly in the 1.0 announcement: “plugins run inside the same PHP process as the rest of the application, with direct access to its database, filesystem, and network.” It adds: “A contact-form plugin can technically read unpublished posts, modify another plugin, or send data anywhere.” That is the setting for a steady run of incidents this site has covered, from an Elementor feature, on by default, that let one link forge an administrator account to a calendar plugin on 600,000 sites where an anonymous comment could lead to full server takeover and a self-rebuilding must-use plugin backdoor that reached its command servers through an Ethereum smart contract.
The scale is not in dispute. Patchstack’s State of WordPress Security in 2026 report counts 11,334 new vulnerabilities in the WordPress ecosystem in 2025, a 42 percent increase over 2024. Of those, 91 percent were in plugins and 9 percent in themes; only six were in WordPress core, all low priority. Patchstack also found that approximately half of high-impact vulnerabilities are exploited within 24 hours of disclosure. Cloudflare’s April announcement put the plugin share of WordPress security issues at 96 percent, so the exact figure moves between reports, but the direction does not.
What EmDash 1.0 Actually Changes
Plugins That Must Declare What They Need
A sandboxed EmDash plugin starts with almost nothing. Cloudflare says each one “runs in an isolated runtime with access to its own private storage, but not to the site’s content, media, users, secrets, environment, filesystem, or network,” and “gains additional abilities only when they are declared by the plugin and approved by the site administrator.” Those declarations are named capabilities in a manifest. EmDash’s capabilities documentation says the sandbox bridge only hands a plugin the host APIs it declared, so a plugin that never declared content:read gets no ctx.content object at all, and one without network:request gets no ctx.http.
{
"slug": "plugin-hello",
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
A minimal manifest from EmDash’s capabilities documentation, with identity fields omitted.
Two rules make the model more than a permissions label. First, it fails closed. When no sandbox runner is available, the plugin sandbox documentation says plugins configured as sandboxed “are not loaded, installed marketplace and registry plugins do not run,” and a new install fails with a SANDBOX_NOT_AVAILABLE error, while the rest of the site keeps working. Second, updates cannot quietly widen a plugin’s reach. Registry updates repeat every check, and the registry documentation puts the confirmation rule this way: “An update waits for confirmation when it adds permissions or MCP tools, or changes a route from authenticated to public.” WordPress has no capability declarations to compare, so a directory update simply carries whatever new behavior its author ships.
A Registry That Does Not Own Its Publishers
Traditional plugin registries, Cloudflare argues, combine three roles: the publisher’s account, the authoritative package record, and the catalog where users discover it. EmDash separates them using AT Protocol, the network behind Bluesky. “The package and release records are signed by the publisher and stored in the publisher’s own account,” the announcement says, so other catalogs can index the same publications under their own policies. The AT Protocol repository specification confirms the building blocks: a repository’s records are stored in a Merkle Search Tree, and each commit carries a required cryptographic signature. Per the announcement, that lets EmDash verify a release record independently instead of trusting the catalog’s copy.
Decentralized publishing, the announcement stresses, “does not mean accepting unverified code.” Before it shows the consent dialog, the registry documentation says, “EmDash checks the downloaded bundle’s checksum, name, version, and permissions. If the publisher requires build provenance, EmDash verifies that evidence too. Installation stops if a check fails.” Public names are built from the publisher’s handle, for example @example.com/my-gallery, and EmDash resolves the handle to the publisher’s stable account identifier before loading the package. If a handle conclusively stops resolving back to the publisher, new installs are blocked, but existing installations stay as they are “so an identity lookup cannot disable a running site.”
The trade is that no one has to approve a plugin’s code before it appears. The catalog applies default moderation to the names, descriptions, links, and images it displays, and Cloudflare says that moderation “does not rewrite a release, take ownership of the plugin, or erase the underlying publication.” Publishers, in Cloudflare’s words, can “publish useful software without asking permission from EmDash.” The WordPress.org directory works differently: a submitted plugin “will be manually reviewed for any common errors as well as ensuring it complies with all the guidelines,” and the developer FAQ says of going live: “As soon as you push code to the SVN folders, your plugin will be live.” EmDash swaps the review queue for a runtime boundary and a consent screen.
Where the Boundary Is Weaker Than the Headline
The Same Manifest Gets Different Guarantees on Different Runtimes
Cloudflare stresses that the isolation “is not limited to Cloudflare deployments” and that plugins “use the same manifests and capability-gated APIs on either platform.” The manifests are identical. The enforcement is not. On Cloudflare, each plugin runs as a Dynamic Worker created through the Worker Loader binding. On Node.js, EmDash starts workerd as a child process and runs each plugin as a service inside it. The sandbox and capabilities documentation spell out what each runner enforces.
| Control | Cloudflare Workers runner | Node.js workerd runner |
|---|---|---|
| Isolation | Dynamic Worker created through the Worker Loader binding | workerd child process running each plugin as a service |
| CPU time (50 ms default) | Enforced by Worker Loader | Not enforced |
| Subrequests (10 default) | Enforced by Worker Loader | Not enforced |
| Memory (128 MB default) | Not enforced per plugin; platform ceiling applies | Not enforced |
| Wall time (30 s default) | Enforced by the runner | Enforced by the runner |
| Requirement | Workers Paid plan and a worker_loaders binding | The workerd package |
Source: EmDash plugin sandbox and capabilities documentation, as read on September 28, 2026.
Of the four limits, only the 30-second wall-time limit is enforced on both runners. The docs say the Node.js runner “enforces only the 30-second wall-time default” and warns when a site configures limits that “standalone workerd cannot enforce.” EmDash does add some hygiene of its own: the workerd child process receives only a short list of environment variables, so secrets in the server’s environment stay out of the sandbox.
There is a more basic caveat. The workerd README carries a warning that “workerd is not a hardened sandbox” and explains: “workerd on its own does not contain suitable defense-in-depth against the possibility of implementation bugs. When using workerd to run possibly-malicious code, you must run it inside an appropriate secure sandbox, such as a virtual machine.” The README adds that the Cloudflare Workers hosting service “uses many additional layers of defense-in-depth.” Cloudflare’s own Dynamic Workers documentation, by contrast, presents that product as a way of “securely sandboxing code you don’t trust.” A self-hosted Node.js site that installs a registry plugin is therefore leaning on an isolate whose maintainers say to run it inside a secure sandbox such as a virtual machine when the code might be hostile.
Reaching the stronger Cloudflare path has conditions of its own. The sandbox documentation says “Dynamic Workers are available on the Workers Paid plan.” The Cloudflare templates leave the Worker Loader binding commented out, so new projects deploy on the free plan unless sandboxed plugins are enabled during scaffolding. And the same page states: “Sandboxed plugins are not available on a Hyperdrive deployment.” That limit stands out because Cloudflare credits its own migration of the Cloudflare Blog to EmDash with producing the Hyperdrive database adapter, along with KV object caching and Workers Cache compatibility. For now, the database adapter that came out of that project cannot be combined with sandboxed plugins.
Coarse Capabilities Hand the Judgment Back to the Operator
Even where the runtime holds, a capability is a broad grant. The capabilities documentation is direct about what the sandbox does not enforce: “A plugin with content:write can edit any content, not only its own.” Capabilities are coarse, it says, and “An operator must evaluate the plugin’s code and publisher before granting that access.” Cloudflare compares installing a sandboxed plugin to installing a mobile app. Anyone who has tapped through an app permission prompt knows both the strength and the weakness of that comparison: the prompt is far better than nothing, and it works only if the person reading it knows which requests are unusual.
Provenance Is a Publisher’s Choice
Build provenance is the check that would tell a site owner a release came from the source repository its publisher claims. In EmDash it is conditional. The registry documentation says: “If the publisher requires build provenance, EmDash verifies that evidence too.” The publishing documentation adds that “Profiles without repository metadata also permit releases without provenance,” and the automated-release workflow is limited for now to public GitHub repositories because, in the docs’ words, “the verifier trusts GitHub’s public Sigstore root.” What every install is guaranteed, then, is integrity: the bundle matches the signed record. That says nothing on its own about whether the publisher’s build was clean or whether the publisher’s account is still in the right hands, and a signed record from a compromised account is still a signed record.
A Launch-Day Look at the Registry
To see what the trust model looks like in practice, we queried the registry’s public discovery API at 19:28 UTC on launch day, the same API that EmDash’s registry client wraps, and read each package’s latest release record. We also asked the WordPress.org directory API for its total, for scale; the -g flag keeps curl from treating the square brackets as a glob. The docs describe the registry API as experimental, so its endpoint may change.
curl -s "https://registry.emdashcms.com/xrpc/com.emdashcms.experimental.aggregator.searchPackages?q=&limit=100"
curl -sg "https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[per_page]=1"
The hosted registry returned 13 packages from 10 publisher handles, with no further page of results. The WordPress.org directory API, queried at about the same time, reported 69,743 plugins in its info.results field, or roughly 5,400 directory listings for every registry listing. That is a comparison of scale, not of quality: the EmDash repository was created on April 1 and already shows more than 12,800 stars. Two of the 13 entries are named marketplace-test, and the one from the project’s own plugins.emdashcms.com account describes itself as a “Maximal sandboxed plugin fixture for registry, consent, runtime, and admin testing.” Four of the 13 come from that project account.
| Plugin | Publisher handle | Network access | Other declared access | Requires provenance |
|---|---|---|---|---|
| Webhook Notifier | plugins.emdashcms.com | Unrestricted | media read, content read | No |
| AT Protocol Syndication | plugins.emdashcms.com | Unrestricted | content read | No |
| marketplace-test (fixture) | plugins.emdashcms.com | Unrestricted | 23 capability entries | No |
| audit-log | plugins.emdashcms.com | None | media read, content read, content write | No |
| LinguaDash | swiss.ky | 4 named hosts | schema read, content read, content write, content revisions read | Yes |
| Eventual | vermeulen.solutions | None | media read, media bytes read | No |
| ai-search | shahriar-robbani.bsky.social | 1 named host | content read | No |
| Forward Email | numoteq.com | 1 named host | email transport | No |
| EmDash to Buffer | justy.dev | 2 named hosts | content read | No |
| Freeform | solspace.com | Unrestricted | email send | No |
| contact-form | harsh-pranshtech.bsky.social | None | None declared | No |
| Cloudflare Email Sending | cfreear.bsky.social | 1 named host | email events, email transport | No |
| marketplace-test | mk.gg | None | content read, content write | No |
Latest release record for each package returned by the registry discovery API at 19:28 UTC on September 28, 2026. The provenance column reflects each package profile’s release policy: one profile requires provenance, five set the policy to false, and seven leave it unset.
Nine of the 13 declare outbound network access. Five pin it to named hosts. Four declare it with no host list, which the registry’s lexicon defines plainly: “an empty object grants unrestricted requests.” The capabilities documentation says that form “exists for user-configured URLs,” such as a webhook plugin where the operator types in the destination, but it is also the broadest network grant on the list. Two mail plugins, Forward Email and Cloudflare Email Sending, declare the email transport capability, which the same lexicon describes this way: “Plugin becomes the host’s mail transport; every message the site sends is delivered through it.” LinguaDash, which translates content with DeepL, OpenAI, or Cloudflare AI, combines content:write with network access to those providers’ API hosts. That is exactly what such a plugin needs, and exactly the kind of grant the documentation says an operator must judge by the plugin’s code and publisher.
Only one of the 13 requires build provenance. That is LinguaDash, whose repository sits under the GitHub account the announcement thanks among the project’s most dedicated contributors. On identity, the Cloudflare Email Sending plugin, which the announcement names as one of the registry’s plugins, appears as @cfreear.bsky.social/emdash-cf-email-sending: a bsky.social handle, not a cloudflare.com one. Nothing in the record suggests anything is wrong. It shows that in a handle-based model the person clicking Install has to confirm that a name means what it appears to mean. All 13 listings also carry a listing-passed label from labels.emdashcms.com, which appears to be the labeler service the announcement says uses Workers AI to moderate package descriptions. Per the announcement, that moderation applies to the names, descriptions, links, and images the catalog displays; it describes no review of plugin code.
None of this is an indictment of a young registry with 13 listings. It is a map of where the trust decisions moved.
What Site Owners and Platforms Should Take From It
- Read the consent dialog as a threat model. Email transport, unrestricted network access, and content write are the grants with the widest reach, so they deserve the most scrutiny.
- On self-hosted Node.js, treat the workerd isolate as one layer, not the whole boundary. The README says possibly-malicious code belongs inside a secure sandbox such as a virtual machine, and EmDash’s docs say only wall time is enforced on that runner.
- Prefer plugins that pin hosts, require provenance, and come from publishers you can identify. Named entries in
allowedHostsnarrow the network grant, and a provenance requirement is what makes EmDash verify build evidence at install time. - Platforms can curate. Cloudflare says hosting platforms built on Workers for Platforms can run their own plugin marketplaces with access to the full registry, or restrict customers to a selection of pre-approved plugins.
EmDash 1.0 does two things the WordPress plugin model does not. It makes a plugin’s reach a declared, runtime-enforced property, which matters because controls that run only at install time cannot see what code does later: the npm campaign this site covered earlier put its loader in runtime code rather than install scripts. And it makes the registry unable to silently rewrite a publisher’s history. That is a genuine relocation of trust. The launch-day data shows what the trust now rests on: a runtime whose guarantees differ by platform, a consent screen that asks people to judge coarse grants, and a provenance check that publishers must opt into. With 13 listings and one provenance requirement, the ecosystem has not yet had to test any of the three.








No Comment! Be the first one.