配置插件沙箱

本页内容

沙箱化插件除了插件声明之外还需要一个平台运行器。Marketplace 和 registry 安装始终使用该运行器,sandboxed: [] 下列出的插件也是如此。plugins: [] 下的原生插件在 EmDash 服务器进程中运行,不会获得沙箱隔离。

运行器取决于部署平台。在 Cloudflare Workers 上,每个插件作为通过 Worker Loader 绑定创建的 Dynamic Worker 运行。在 Node.js 上,服务器启动开源 Workers 运行时 workerd 作为子进程,并将每个插件作为其中的服务运行。emdash() 的 sandboxRunner 选项选择运行器并启用托管注册表目录。没有它,sandboxed: [] 下的插件不会被加载。显式配置的注册表仍可浏览,但安装或更新沙箱化插件会以 SANDBOX_NOT_AVAILABLE 失败。

下表总结了每个运行器需要和强制执行的内容。

Cloudflare WorkersNode.js
sandboxRunner@emdash-cms/cloudflare 的 sandbox()"@emdash-cms/sandbox-workerd/sandbox"
要求Workers 付费计划、worker_loaders 绑定、从 Worker 入口点导出 PluginBridgeworkerd 包
数据库访问DB D1 绑定(与配置的适配器无关)配置的数据库
强制限制CPU 时间、子请求、墙钟时间墙钟时间

Cloudflare Workers

Dynamic Workers 在 Workers 付费计划中可用。*-cloudflare 模板包含以下入口点导出,但将绑定注释掉了,因此新项目在 Workers 免费计划上部署,除非你在脚手架期间启用沙箱化插件。

  1. 在 wrangler.jsonc 中启用 Worker Loader 绑定。运行器以 LOADER 名称读取它,仅在此绑定存在时选择 Cloudflare 沙箱:

    {
    	"worker_loaders": [
    		{
    			"binding": "LOADER",
    		},
    	],
    }

    如果 Wrangler 配置使用命名环境,请在 Astro 构建期间设置 CLOUDFLARE_ENV。Cloudflare Vite 插件和 sandbox() 将读取相同的环境。绑定不会被继承,因此请将 LOADER 添加到运行沙箱化插件的每个命名环境中。

  2. 从 Worker 入口点导出 PluginBridge,并将 main 指向该文件。PluginBridge 是沙箱化插件访问内容、媒体、存储和电子邮件的入口点;运行器在入口模块的导出中查找它:

    import handler, { createScheduledHandler, PluginBridge } from "@emdash-cms/cloudflare/worker";
    
    export { PluginBridge };
    
    export default {
    	...handler,
    	scheduled: createScheduledHandler(),
    } satisfies ExportedHandler;
    {
    	"main": "./src/worker.ts",
    }
  3. 在 emdash() 集成中选择运行器:

    import { d1, r2, sandbox } from "@emdash-cms/cloudflare";
    
    emdash({
    	database: d1({ binding: "DB" }),
    	storage: r2({ binding: "MEDIA" }),
    	sandboxRunner: sandbox(),
    });

Node.js

  1. 将运行器与作为 peer 依赖的 workerd 一起安装:

    npm install @emdash-cms/sandbox-workerd workerd

    workerd 包通过可选依赖安装当前平台的二进制文件(x64 上的 Linux、macOS 和 Windows;arm64 上的 Linux 和 macOS)。在服务器运行的平台上启用可选依赖进行安装。在多阶段 Docker 构建中,在与运行时阶段相同平台的阶段中运行安装。

  2. 在 emdash() 集成中选择运行器:

    import { sqlite } from "emdash/db";
    
    emdash({
    	database: sqlite({ url: "file:./data/emdash.db" }),
    	sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
    });

运行器将 Miniflare 声明为可选依赖。包管理器默认安装它。当 NODE_ENV 为 development(astro dev 设置的值)时,运行器将插件交给 Miniflare,由其管理自己的 workerd 进程;下面的崩溃策略不适用。如果省略了可选依赖,运行器改用 workerd。astro preview 将 NODE_ENV 设为 production,node ./dist/server/entry.mjs 不设置它;两者都使用 workerd。

workerd 进程如何运行

EmDash 在沙箱化插件加载后、站点首次请求的初始化期间启动 workerd,并等待最多 10 秒让插件服务响应。从管理面板安装或更新插件会重启它。workerd 写入 stdout 或 stderr 的所有内容都以 [emdash:workerd] 前缀出现在服务器输出中。

插件服务在 127.0.0.1 上监听,返回服务器的通道是 Unix 域套接字(Windows 上为 127.0.0.1 TCP 端口)。不需要打开入站端口。

子进程仅从服务器环境接收 PATH、HOME、TMPDIR、TMP、TEMP、LANG 和 LC_ALL,因此服务器环境中的密钥不会进入沙箱。要传递更多变量,请将 EMDASH_WORKERD_PASSTHROUGH_ENV 设置为逗号分隔的变量名列表。

如果 workerd 意外退出,运行器会记录 [emdash:workerd] workerd exited with <reason> 并在下次调用时重启它,延迟从 1 秒开始,每次翻倍直到 30 秒。当 workerd 在 60 秒内崩溃超过五次时,运行器停止重启并记录 [emdash:workerd] workerd crashed 5 times in 60 seconds, giving up。此后,每个沙箱化插件钩子和路由都会以 Plugin sandbox unavailable for <plugin>: workerd crashed 5 times in 60 seconds and the runner stopped retrying; restart the server 失败。重启服务器会再次启动 workerd,从管理面板安装或更新插件也会如此。向服务器发送 SIGTERM 会同时终止 workerd。

资源限制

每个运行器对每次插件调用应用相同的限制集。限制是固定的;emdash() 集成没有相关选项。

限制值Cloudflare WorkersNode.js
CPU 时间50 ms由 Worker Loader 强制执行;插件达到限制时抛出异常不强制执行
子请求10由 Worker Loader 强制执行;插件达到限制时抛出异常不强制执行
内存128 MB不按插件强制执行;适用平台的 isolate 内存上限不强制执行
墙钟时间30 秒由运行器强制执行由运行器强制执行

当钩子或路由超过墙钟时间限制时,调用以 Plugin <id> exceeded wall-time limit of 30000ms during hook:<name>(或 route:<name>)失败。对于钩子,EmDash 以 EmDash: Sandboxed plugin <id> 前缀记录失败,并在没有该插件结果的情况下继续请求。超过限制的插件路由对其调用者失败。

运行器不可用时

在 Cloudflare Workers 上,sandbox() 在构建时检查 wrangler.jsonc。没有名为 LOADER 的 worker_loaders 绑定时,它将运行器留为未设置并记录以下警告:

[emdash] Sandboxed plugins are disabled because wrangler.jsonc has no LOADER Worker Loader binding. Worker Loader requires a Workers paid plan.

选定的运行器在运行时仍可能不可用:在 Cloudflare Workers 上,当部署的 LOADER 绑定或 PluginBridge 导出缺失时;在 Node.js 上,当 workerd 未安装或其二进制文件无法运行时。EmDash 会记录一条警告,其中包含运行器在冒号后报告的原因。以下警告在绑定缺失时在 Cloudflare Workers 上记录:

EmDash: Plugin sandbox is configured but not available on this platform: the worker has no worker_loaders binding named LOADER. Sandboxed plugins will not be loaded.

sandboxed: [] 下的插件不会被加载,已安装的 marketplace 和 registry 插件不会运行,管理面板的新安装会以错误代码 SANDBOX_NOT_AVAILABLE 失败。站点的其余部分不受影响。

在进程内运行沙箱化插件

在 emdash() 中设置 sandbox: false,以在没有隔离或限制的情况下在服务器进程中运行 sandboxed: [] 下的插件和已安装的 marketplace 插件。这是一个调试选项,用于区分插件中的错误和沙箱中的错误。以下配置在 Node.js 站点上关闭沙箱:

emdash({
	sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
	sandbox: false,
});

在 Cloudflare Workers 上,运行时会以 sandbox: false is not supported in Cloudflare Workers 拒绝启动。

故障排除

每个条目以服务器记录的消息或管理面板返回的错误代码为标题。

“[emdash] Sandboxed plugins are disabled because wrangler.jsonc has no LOADER Worker Loader binding”

Cloudflare 适配器未选择沙箱运行器,因为构建时的 Wrangler 配置没有名为 LOADER 的 worker_loaders 绑定。这是 Workers 免费计划上的预期配置。在 Workers 付费计划上,在 wrangler.jsonc 中启用绑定并重新构建站点。

“Plugin sandbox is configured but not available on this platform”

冒号后的文本指出了原因。在 Cloudflare Workers 上,the worker has no worker_loaders binding named LOADER 表示 wrangler.jsonc 需要一个名为 LOADER 的 worker_loaders 绑定,the worker entrypoint does not export PluginBridge 表示 main 指向的文件必须导出 PluginBridge。部署绑定需要 Workers 付费计划。

在 Node.js 上,workerd is missing or its binary does not run on this platform 表示运行器无法执行 workerd。直接运行已安装的二进制文件,以防止检查下载缺失的包:

./node_modules/.bin/workerd --version

在 Windows 上,运行 node_modules\\.bin\\workerd.cmd --version。如果命令失败,说明 workerd 在 node_modules 中缺失,或安装的二进制文件无法在此平台上运行。在目标平台上启用可选依赖重新安装。

“workerd failed to start within 10 seconds”

子进程已启动,但其插件服务未在 10 秒内响应。此消息前带有 [emdash:workerd] 前缀的行包含 workerd 本身的输出,包括配置和启动错误。运行器将在下次调用时重试。

“workerd crashed 5 times in 60 seconds, giving up”

运行器已停止重启 workerd。此消息前的 [emdash:workerd] workerd exited with <reason> 行指出了每次崩溃的退出代码或信号。修复原因后重启服务器。

安装插件时出现 SANDBOX_NOT_AVAILABLE

管理面板的安装请求被拒绝,因为运行器缺失或不可用。当运行器已配置时,错误消息以与上述启动警告相同的原因结尾。为平台配置运行器,或修复该原因,然后重新部署。