Divine

Security Model

View as Markdown

Divine lets agents do real work on a real WordPress theme without handing them direct, unbounded access to the live site. It achieves this not through a single gate but through a sequence of independent layers, each of which assumes the others might fail. A change must pass through all of them, in order, before it can affect the live theme, and the most dangerous capabilities are simply never exposed. The rest of this document walks through those layers in turn.

Normal WordPress stays on the live theme

The first and most important boundary is that WordPress behaves normally unless a request is a signed Divine preview context. The public frontend, wp-admin, REST, cron, feeds, search, sitemaps, login, and ordinary WP_Query all resolve the active theme and the live database. Divine does not globally swap the theme, intercept admin, or mutate query results. A request only enters a preview context when it carries valid Divine preview signalling, and even then the swap lasts for that one request. This means the live site keeps serving its active theme and its real data no matter how much work is in flight inside worktrees.

Preview tokens are purpose-scoped and time-limited

Inside a preview context, the theme is swapped on the strength of a signed preview token, and that token is deliberately narrow. Divine stores only a hash of each token, issues it for a single purpose (session for the in-app browser, share for shareable and capturable URLs), and binds it to a specific stylesheet, app slug, and blog_id. Validation checks the hash, the purpose, the expiry, and the target on every request, rejects a session token used by another user, and falls back to the live theme on any mismatch. Tokens default to a one-hour lifetime, can be revoked, and are invalidated when their worktree is deployed or destroyed. A token is a viewing credential only: it grants no editing, no deploy, no admin, and no REST workspace access.

The check does not bootstrap WordPress

Divine validates a theme with dv check, and its default fast mode is built to be safe to run anywhere, including untrusted CI. Fast mode does not bootstrap WordPress, open network connections, or shell out. It parses and analyses the theme’s own files in PHP and reports findings, with exit code 0 for no findings, 1 for findings, and 2 for usage or internal errors. WordPress is only loaded when you explicitly pass --full, which adds the render dry-run lane and is intended for CI or wp-env. Even then, Divine first looks for an in-process WordPress, then an explicit bootstrap path from the DIVINE_WORDPRESS_BOOTSTRAP environment variable, then a bounded walk up the directory tree to find wp-load.php; if none is found, it refuses rather than guessing. The practical guarantee is that the check you run on every change has no side effects on a database or the network.

The check enforces forbidden-pattern rules

The check is also where Divine encodes what theme code is not allowed to do. The rules below run as part of the native analysis, so a change that reaches for a dangerous pattern is reported before it is ever previewed or deployed.

Rule What it rejects
Forbidden eval() Any call to eval() in theme code; the scoped runtime APIs are the supported path.
Forbidden process functions exec, system, shell_exec, passthru, proc_open, and popen, so theme code cannot shell out.
No direct post-type registration register_post_type() in a theme, because Divine themes are file and block based.
Raw $wpdb writes $wpdb->insert(), ->update(), ->delete(), and ->query(); themes must use WordPress data APIs.
Output escaping Dynamic block attributes echoed or printed without an escaping function such as esc_html, esc_attr, esc_url, or wp_kses_post.

These rules are scoped to theme project files, so they target the code a worktree actually ships. Taken together they keep theme code away from arbitrary execution, away from raw database writes, and on the WordPress-native path for output and data, which is what makes a file-based theme safe to render against real site data.

REST is capability-gated

Everything the workspace does over the network is gated by a native WordPress capability. The worktree, file, deploy, destroy, and export routes all require the Divine management capability, which is manage_options on single-site and manage_network on multisite. The REST permission callbacks deny anyone without it, and the runtime services assert the same capability again before acting, so a caller that bypasses a permission callback is still rejected. Because the gate is a single native capability, an agent is exactly as privileged as the WordPress user it authenticates as, and no more. See Access And Capabilities for the full authorization model.

Deploy promotes files only

Deploy is the one step that changes the live theme, and it is built to be narrow and explicit. It mirrors the worktree’s theme files into the app theme directory and then destroys the worktree. It never copies database state: no posts, options, uploads, plugin state, or database sandbox travels with a deploy. On multisite it targets only the selected site’s active theme. Deploy also refuses to run while the worktree has unresolved merge conflicts, and it preserves the app theme’s own .git directory rather than overwriting history. The result is that the live site advances only when a sufficiently privileged user deploys, and only by file changes that were reviewable in the worktree first. See Deploy for the file-level behavior.

Bundled dependencies are scoped

Divine bundles Blockstudio so that theme rendering works without a separate plugin, and it loads that dependency under a scoped namespace, Divine\Lib, rather than at the global Blockstudio namespace. Scoping the bundled code keeps it from colliding with a host site’s own copy of Blockstudio or with other plugins that might ship the same library. The check confirms the scoped class is present when it bootstraps WordPress for the render lane, so the runtime uses Divine’s own scoped copy rather than whatever else happens to be installed. The same discipline carries into exported themes, which bundle the scoped runtime and record its provenance.

External side effects still leave WordPress

Divine’s isolation covers the theme swap, the preview context, and the file-only deploy boundary; it cannot retroactively sandbox the wider world that theme code might reach. A preview renders proposed theme files against the real site, so if that code triggers a genuine external effect, the effect happens for real. Remote API calls, email delivery, third-party requests, and outbound webhooks leave WordPress entirely and are outside what a preview or a deploy can roll back. When a theme touches external systems, review it with that in mind, and prefer test credentials or disabled integrations while the work is still being prepared in a worktree.