Plugins
Plugins extend a defined framework surface; they do not alter arbitrary HTML or receive ambient infrastructure credentials. Sandboxed plugin packaging follows EmDash so authors can participate in one ecosystem rather than maintain a WiseWig-only marketplace format. WiseWig’s AI/MCP declarations are an optional sidecar capability; see EmDash plugin compatibility and WiseWig AI.
Sandboxed plugins by default
Third-party and marketplace plugins should run in an isolated sandbox by default. The sandbox exposes a capability-gated bridge: undeclared content, media, forms, settings, network, and storage APIs are absent rather than merely rejected at a later call. Plugins never receive environment variables, filesystem access, D1/R2/DO bindings, or direct network primitives.
Every sandboxed plugin keeps the canonical EmDash publisher/slug identity, semantic version, capabilities, allowed hosts, storage requests, hooks, bundle, and registry provenance. The optional wisewig-plugin.jsonc sidecar contributes MCP tools, resources, and prompts but cannot broaden that authority. Network access requires the EmDash network capability and hostname allowlist. Runtime enforcement must reject an undeclared capability even for an installed plugin. A public plugin MCP primitive is restricted to the public_content data scope; it cannot carry a permission. Private primitives must declare a WiseWig permission and data scope. Form submissions, engagement aggregates, and personal data are private-only, with personal data requiring the elevated engagement.export permission.
EmDash artifact downloads are unauthenticated HTTPS with redirects disabled, obvious local/private targets denied, and strict declared/streamed size limits. The SDK verifies the required base32 sha2-256 multihash before returning bytes. The Node operator adapter establishes identity first by verifying the exact release’s AT Protocol CAR proof against the publisher’s current DID signing key and repository MST. It validates the EmDash lexicon and requested package/version before downloading anything. Hostname checks are not a substitute for outbound network policy because of DNS rebinding. Verified bytes still cannot execute until the compatible sandbox adapter lands.
Verified tarballs are parsed in memory rather than extracted to the operator filesystem. Tar checksums, entry count, EmDash’s 256 KiB package and 128 KiB backend limits, UTF-8, required files, and release/manifest identity are enforced; traversal, links, duplicate entries, and unsupported entry types are rejected. backend.js executes in the Dynamic Workers runner with ambient networking disabled. Normal after-publish lifecycle dispatch uses the tenant capability bridge.
The initial executable enforcement is documented in plugin runtime boundaries.
Native plugins are exceptional
Native plugins run in the same process as a site’s deployed theme and can ship Astro/React code where a developer genuinely needs that power. They are for developer-controlled code, versioned and deployed with the site—not one-click marketplace installation. Capability declarations remain useful policy, but native code is not an isolation boundary: it can access the runtime directly. The native PluginRuntime explicitly refuses to execute a sandboxed manifest.
The Cloudflare reference accepts only signed ECDSA P-256 releases from configured registry keys. The signature covers the normalized manifest, provenance, artifact, and forward-only declarative migrations; the declared SHA-256 artifact digest is recomputed before installation and again at the sandbox boundary. Native packages cannot use this route. The management UI summarizes capabilities and MCP tools before confirmation, and install/upgrade/enable/disable/rollback/uninstall operations use plugin.manage, durable idempotency, and audit.
Alpha sandbox artifacts are bounded declarative hook programs (require-field, max-string, and deny), not arbitrary JavaScript. They execute in the separately deployed wise-wig-plugin-sandbox Worker, whose only current storage binding is its own artifact Durable Object. It has no site D1, R2, session, tenant workspace, or outbound-network bridge. Artifact state is immutable and addressed by verified digest: reinstalling the same release is idempotent, while a different plugin or payload cannot claim that digest. Disable, rollback, and uninstall change tenant-local selection/storage but never delete the globally addressable artifact because other tenants may still reference it.
The beta executable path follows EmDash: a pluggable sandbox runner uses Dynamic Workers on Cloudflare, blocks direct network access, and supplies request-scoped capability bridge bindings. Verified code storage, signed immutable executable identity, declared-hook checks, fresh Worker Loader instances, CPU/subrequest limits, host wall timeout, a runtime adapter pinned to the EmDash 0.31 wrapper contract, blocked ambient fetch, hashed short-lived tenant grants, storage collection enforcement, and lifecycle after-publish dispatch are implemented and tested. The bridge supports published core-page reads, namespaced structured plugin-content CRUD, ready-media reads, bounded 5 MiB media upload/delete, and allowlisted HTTP. Core-page writes, taxonomy, users, and email remain denied. If no compatible runner is available, sandboxed plugins are skipped and reported unavailable. They are never silently treated as native. The existing declarative runner remains useful for code-free policy plugins.
Tenant workspace SQLite stores the active and previous release, migration ledger, enabled state, declared collections, and quota-metered plugin records. Upgrades retain one rollback release. Alpha migrations only create declared collections; destructive migrations are rejected so manifest rollback does not pretend it can restore deleted data. A rollback selects the previous signed manifest/artifact while forward-created collection tables remain inaccessible unless that manifest declares them. Uninstall removes that tenant/site plugin’s namespaced records and migration ledger.
Plugin MCP surfaces
Tools, resources, and prompts are first-class signed manifest declarations. Publishing copies only enabled public declarations whose sole data scope is public_content; the anonymous MCP Worker reads only that sanitized published index. Private declarations require both an OAuth scope and the exact current WiseWig role permission on every request. A private resource explicitly lists the plugin collections it may read. An omitted list returns no plugin records, an undeclared collection fails manifest validation, and public resources may not name private collections.
Hook policy
Hooks validate or react to lifecycle events. They cannot mutate published state directly, block indefinitely, or make the editor wait for nonessential external work. beforeSave and beforePublish hooks are bounded validation points; delivery, indexing, and integrations run after the committed command.