使用 WorkerJavaScriptBackend 隔离运行 JavaScript 模块:@cloudflare/computer 隔离运行时实战指南
使用 WorkerJavaScriptBackend 隔离运行 JavaScript 模块cloudflare/computer 隔离运行时实战指南【免费下载链接】computerGive your agent a computer 项目地址: https://gitcode.com/GitHub_Trending/computer1/computer导读cloudflare/computer为 Durable Object 提供了一套开箱即用的虚拟文件系统与执行后端而 WorkerJavaScriptBackend 是其核心能力之一在每次执行时创建一个全新的 Cloudflare Dynamic Worker在隔离的 ECMAScript 模块运行时中运行用户代码。它支持静态导入、字面量动态导入、顶层 await、持久化相对导入、宿主安装的配置模块以及由 Workspace 提供的持久化node:fs/promises和受信任的ws:git/ws:artifacts能力模块。阅读本文后你将掌握该后端的完整配置项、执行模型、资源限制、取消语义与隔离边界能够为你的 Agent 搭建安全、可回放、受约束的 JavaScript 代码执行环境。前置说明cloudflare/computer目前处于PREVIEW预览阶段见 docs/README.mdAPI 不稳定、设计可能调整适合实验与原型暂不适合生产环境本文描述的是当前仓库spec 目录为前瞻性说明代码以 packages/computer 实际实现为准。一、整体架构一次执行 一个全新的 Dynamic WorkerWorkerJavaScriptBackend源码位于 packages/computer/src/backends/worker-javascript/worker-javascript.ts类型定义与导出见 packages/computer/src/backends/worker-javascript/index.ts的运行模型是调用方通过Workspace.runtime.exec()传入一段真实 ES 模块源码后端先用 acorn 解析模块图见 packages/computer/src/backends/worker-javascript/module-graph.ts收集所有静态导入、导出与字面量动态导入把源码与依赖打包成一个模块图通过loader.load()启动一个全新的 Dynamic WorkerWorker 加载宿主生成的workspace-runtime-runner.js运行器动态import入口模块并调用其default导出函数运行器通过WorkspaceRuntimeBridge见 packages/computer/src/runtime/bridge.ts以 RPC 方式访问宿主侧的 Workspace 文件系统与能力模块stdout/stderr 以帧的形式实时流回宿主追加到执行事件流结果与退出事件在输出流关闭后发布。最小可运行示例import { Workspace } from cloudflare/computer; import { WorkerJavaScriptBackend } from cloudflare/computer/backends/worker-javascript; const workspace new Workspace({ storage: ctx.storage, backends: [ new WorkerJavaScriptBackend({ loader: env.LOADER, // 动态 Worker 加载器Worker Loader binding root: /workspace, // 代码可见的文件系统根 access: read-write, // read | read-write defaultTimeoutMs: 60_000, // 默认超时 maxTimeoutMs: 180_000, // 单次执行允许的最大超时 globalOutbound: null, // 默认关闭出站网络 modules: { math-kit: export const double value value * 2;, // 配置模块bare import }, }), ], });执行一个模块const handle await workspace.runtime.exec( import { double } from math-kit; import fs from node:fs/promises; export default async function main(input) { const value double(input.value); await fs.writeFile(/workspace/result.txt, String(value)); return { value, persisted: await fs.readFile(/workspace/result.txt, utf8) }; } , { backend: worker-javascript, input: { value: 21 }, encoding: utf8, }, ); const result await handle.result(); // result.value { value: 42, persisted: 42 }这里的源码是真正的 ES 模块import { double } from math-kit命中后端构造时配置的modules映射import fs from node:fs/promises命中宿主安装的持久化文件系统。如果模块default导出一个函数Workspace 会用options.input调用它否则模块求值完成时返回一个null的结构化结果对应运行器源码中的typeof module.default function ? await module.default(input) : module.default ?? null见 worker-javascript.ts。调用返回时机与执行生命周期runtime.exec()在 Dynamic Worker完成之前就会返回运行会在其事件流被消费、且宿主对 Dynamic Worker 的调用仍处于 in-flight 状态时持续推进。这部分挂起的工作本身就能让 Durable Object 保持驻留resident。如果拿到了 handle 却从不读取事件流一旦对象转为空闲运行可能被驱逐因此持续排空事件流或调用result()来保持运行存活对于必须跨驱逐存活的持久化工作通过ctx.storage.setAlarm()调度 alarm而不是依赖挂起调用。执行事件流的类型定义见 packages/computer/src/runtime/types.ts包含stdout、stderr与携带code以及可选result的exit事件。二、持久化相对导入模块图在宿主侧解析相对导入从cwd出发通过持久化的 Workspace 文件系统解析。先在 Workspace 里写入一个任务模块再通过相对路径导入它await workspace.fs.writeFile( /workspace/task.js, import fs from node:fs/promises; export default input fs.writeFile(/workspace/value.txt, String(input.value)); , ); await workspace.runtime.exec( import task from ./task.js; export default task;, { backend: worker-javascript, cwd: /workspace, input: { value: 42 }, }, );模块图构建module-graph.ts在加载 Worker 之前就完成了全部静态分析加载前解析整个图用 acorn 把源码解析成 AST遍历ImportDeclaration、ExportNamedDeclaration、ExportAllDeclaration与ImportExpression约束每个持久化路径所有相对导入都先经WorkspaceRuntimeCapability.resolveConfined()归一化并约束在root之内见 capability.ts越界路径直接抛错拒绝符号链接穿越#assertSafeComponents会逐段lstat检查路径组件任何中间符号链接都会被拒绝见 capability.ts动态导入必须使用字符串字面量import(\./${name}.js) 这类模板字符串在解析阶段就会抛错见 module-graph.ts绝对导入不被支持import /abs/path.js会被拒绝必须改用相对 Workspace 导入。此外模块图受聚合源码大小maxSourceBytes、模块数量默认maxModules128见 module-graph.ts与导入深度默认maxDepth32约束加载到 Dynamic Worker 的完整模块图还被assertLoaderGraph限制为最多 256 个模块见 worker-javascript.ts。三、执行限额与记录保留并发执行上限后端默认同时接纳最多24 个执行maxConcurrentExecutions构造器默认值见 worker-javascript.ts。超过上限的并发启动会以EEXEC_BUSY错误失败而不是无限创建 Dynamic Worker。同一执行 id 已存在也会抛出EEXEC_BUSY/EEXEC_EXISTS见 worker-javascript.ts。在调整maxConcurrentExecutions之前请先实测部署环境的 Durable Object 与 Worker Loader 限额再据此上调。单次执行的字节与数量限额每次执行都会约束以下资源可分别下调公共负载场景建议收紧选项默认值作用maxSourceBytes1 MiB源码与模块图聚合大小maxInputBytes1 MiB结构化输入序列化后的字节数maxResultBytes1 MiB结构化结果字节数宿主侧assertResult校验maxStdinBytes256 KiB标准输入字节数maxEnvBytes1 MiBenv记录总字节数maxStdioBytes1 MiBstdout stderr 合计字节数maxCapabilityBytes1 MiB单次能力调用请求/响应载荷最小 256 字节maxHostCallMs默认maxTimeoutMs单次宿主能力调用的调用方可见期限maxConcurrentCapabilityCalls32并发的宿主能力调用数maxCapabilityCalls256累计能力调用次数maxCapabilityRequestBytes8 MiB能力调用累计请求字节maxCapabilityResponseBytes8 MiB能力调用累计响应字节maxDirectoryEntries1024单次目录读取返回的最大条目数maxExecutionSubscribers8单次执行的实时事件订阅者数以上默认值全部来自 worker-javascript.ts 的构造器解析逻辑。实现细节值得注意目录读取限额在 SQLite 层生效readdir会以maxDirectoryEntries 1作为 limit 请求超过即抛错避免物化超量行见 capability.ts请求在隔离内、Workers RPC 前检查一次宿主再检查一次隔离侧workspace-capabilities.js先校验序列化后的请求大小见 module-graph.ts宿主侧WorkspaceRuntimeBridge.call()再对载荷、并发、总数与累计字节做二次校验见 bridge.ts。已完成执行的保留与回放默认保留60 分钟retentionMs同一后端最多保留100 条完成记录maxRetainedExecutions见 worker-javascript.ts完成记录立即离开内存中的活跃集合回放时从 SQLite 读取后端在连接时创建workspace_runtime_executions与workspace_runtime_events两张表事件按(backend, execution_id, seq)持久化见 worker-javascript.ts内存清理与 SQLite 清理都由#prune()驱动按finished_at与条数双条件删除过期记录见 worker-javascript.ts。取消语义取消killExec/disposeExec/ Workspace 关闭会停止新的宿主能力调用cancelAndDrain()置#cancelled true并 abort 所有在途AbortController见 bridge.tsdispose 掉 Dynamic Worker等待已接受的宿主调用 settle 之后才发布 exit 130。正常完成同样遵循这条 drain 规则因此未 await 的能力调用不可能在 exit 0 之后继续改动 Workspace。宿主调用存在调用方可见的截止期限maxHostCallMs错过期限会让该能力调用失败并把执行标记为 failed——即使调用方代码 catch 了这个错误。但执行仍会等待已接受的宿主操作本身完成后再发布终态事件因为许多宿主 API 在派发后无法回滚外部副作用。受信任模块会收到可选的{ signal, deadline }上下文必须在 signal abort 时及时停止一个无视取消、永不 settle 的受信任模块会让执行停留在 finalizing 状态。运行期配置compatibilityDate与compatibilityFlags控制 Dynamic Worker 运行时默认分别为2026-05-23与[nodejs_compat]见 worker-javascript.ts即包内测试过的设置日期必须符合YYYY-MM-DD格式否则构造抛错。四、环境变量、标准输入与processshim每次执行都会安装一个极小的node:processshim让普通模块代码可以读取自己的环境与标准流。shim 只暴露调用方为该次执行提供的内容宿主环境完全不可见process.env是 exec options 中env记录的快照。未传入的变量不存在Durable Object 自身环境从不合并进去——模块无法通过process.env读取宿主 binding 或 secretsprocess.stdin一个非交互式async-iterable按for await消费调用方传入的stdin字节一次后即结束由于 evaluate-once 执行没有会话可等待不存在阻塞读取更多输入。isTTY恒为false。输入受maxStdinBytes约束超限会以明确错误失败process.stdout/process.stderr可写流写入会流向实时输出见下文隔离与生命周期。console.log/console.info路由到 stdoutconsole.warn/console.error路由到 stderr两者共享同一个maxStdioBytes上限process.argv、process.cwd()、process.platform返回惰性值——cwd()反映该次执行的cwd而argv固定为[workspace, entryName]与platform固定为linux是占位符不描述宿主进程。上述行为对应运行器源码worker-javascript.tsnextProcess只包含env、固定argv、cwd()、固定platform与一次性stdin迭代器且安装时会尽力覆盖globalThis.process不可覆盖时则只回填env/stdin/stdout/stderr。示例读取 stdin 与环境const handle await workspace.runtime.exec( export default async function main() { let piped ; for await (const chunk of process.stdin) piped new TextDecoder().decode(chunk); console.log(received, piped.length, bytes); return { who: process.env.WHO, piped }; } , { backend: worker-javascript, env: { WHO: demo }, stdin: hello, encoding: utf8, }, );env必须是 string-to-string 记录超出maxEnvBytes会抛错assertEnv见 worker-javascript.tsstdin只接受字符串或Uint8ArraynormalizeStdin。五、配置模块Configured modulesbare imports 在后端构造时安装而不是在单次执行时传递new WorkerJavaScriptBackend({ loader: env.LOADER, modules: { tar-stream: TAR_STREAM_BUNDLE, }, });未知的 bare import 会在创建 Worker 之前失败模块图构建时凡是既非相对路径、又非node:*/ws:*/ 内部模块名的 specifier都必须命中configuredModules否则抛Module X is not configured for the worker-javascript backend.见 module-graph.tsnode:fs与node:fs/promises是宿主安装的例外由持久化 Workspace 背书配置模块只是代码不是宿主权限它们不得使用保留的ws:命名空间也不得遮蔽node:fs、node:fs/promises两个文件系统 specifier配置模块名含/、ws:前缀或与内部模块名冲突都会在 module-graph.ts 中被拒绝。六、受信任的 Workspace 模块node:fs/node:fs/promises持久化文件系统文件系统访问使用熟悉的异步 Node API但底层是持久化的 Workspace而非 isolate 本地文件系统。两种写法都自动安装import fs from node:fs/promises; // or: import { promises as fs } from node:fs; const text await fs.readFile(/workspace/input.txt, utf8); await fs.writeFile(/workspace/output.txt, text.toUpperCase());受支持的 promise API 为readFile、writeFile、mkdir、rm、chmod、symlink、readlink、readdir、stat、lstat、access实现见 module-graph.ts。行为边界readFile省略 encoding 时返回字节Uint8Array仅支持utf8/utf-8文本编码其他编码如base64被拒绝writeFile支持默认w标志与独占wx其他 Node 标志被拒绝与 Node 一致父目录必须已存在writeFileNode只放行w/wx见 capability.tsreadlink保留相对符号链接目标通过符号链接的读写被 Workspace 约束边界拒绝同步与回调式 Node 文件系统 API刻意不可用因为每个操作都要跨越 isolate → Workspace 的能力边界隔离侧通过workspace-capabilities.js安装的全局分发器路由到宿主WorkspaceRuntimeBridge.call见 module-graph.ts。ws:命名空间宿主能力模块整个ws:命名空间为 Workspace 维护的宿主能力保留内置运行时安装ws:git与ws:artifacts。调用方模块与持久化文件都不能遮蔽node:fs、node:fs/promises或ws:*。未知的ws:导入在模块图构建时即失败Unknown trusted Workspace module见 module-graph.ts。ws:gitimport { clone, diff, status, log, cli } from ws:git;ws:git是显式宿主权限而非环境化的隔离区联网。clone、fetch、pull、push、ls-remote与 submodule 等命令即使 Dynamic Worker 配置了globalOutbound: null也能发起宿主侧请求因此默认一律拒绝只有在受信任的后端构造上显式设置allowGitNetwork: true才可用本地 Git 操作无需该权限。实现上bridge.#callGit对网络命令调用#requireGitNetwork对写操作调用#requireWrite并对cli参数做-C/--git-dir/--work-tree路径覆盖防护见 bridge.ts。ws:artifactsimport { create, get, list, importArtifact, deleteArtifact, } from ws:artifacts;这些模块是沙箱侧的 shim背后是宿主 RPC。Loader binding、凭据、Durable Object 存储与不受限的 Workspace 对象永不进入用户代码。宿主桥接层在每次变更上都校验后端固定的 read / read-write 权限#requireWrite未配置 Artifacts binding 时Artifacts 方法会明确失败Workspace Artifacts are not configured for this execution.见 bridge.ts。远程ws:artifacts.importArtifact()独立于 Git 网络权限默认也被拒绝需allowArtifactNetwork: true。路径约束的安全边界路径约束会拒绝词法逃逸..、NUL 字节等与操作前路径中的每一个符号链接组件见 capability.ts。但要注意这些检查不是原子化的 inode 式root 之下原语——不要把单个 isolate 能力当作针对另一个、可在同一可变 Workspace 中并发替换路径的更高权限主体的安全边界。需要对抗这种并发替换的部署应等待未来的事务性 DOFS 原语或使用独立的 Workspace 身份。七、隔离与生命周期每次执行都会获得一个全新的 Dynamic Worker并伴随显式的 Worker Loader CPU 限额limits: { cpuMs: timeoutMs }见 worker-javascript.ts宿主墙钟期限timeoutMs与maxTimeoutMs约束默认globalOutbound: null无出站网络也可通过egress策略或globalOutboundFetcher 配置网关有限、无环、JSON 兼容的结构化输入与结果校验assertRuntimeValue拒绝循环、非平凡原型与非有限数值见 capability.ts可配置的 source / module graph / input / result / stdin / stdio / 文件与能力调用请求 / 响应字节限额显式的 entrypoint 与 Worker disposedisposeQuietly见 worker-javascript.ts宿主持有的取消host-owned cancellation事件与结果行保留在 Workspace 数据库中。实时输出管道stdout / stderr实时流式输出隔离侧运行器把IdentityTransformStream的可读端通过host.attachOutput(readable)交给宿主见 worker-javascript.ts宿主在用户代码仍在运行时逐帧排空该流#pumpFrames见 worker-javascript.ts每帧到达即追加到执行事件流帧编解码见 packages/computer/src/backends/worker-javascript/frames.ts而不是缓存到运行结束再发布结构化结果与退出事件在输出流关闭后 settle因此终态事件总是跟随最后一行输出输出由maxStdioBytes在两条流上共同限定超过后隔离侧会以...[stdio truncated]标记截断并停止记录见 worker-javascript.ts。已完成写入立即可持久化失败或取消不会回滚已完成的文件系统效果。八、受信任的集成扩展Trusted integrations宿主可以通过WorkerJavaScriptBackend.trustedModules配置额外的保留能力模块new WorkerJavaScriptBackend({ loader: env.LOADER, trustedModules: { ws:my-service: { async call(method, args, context) { // 实现你的宿主能力 }, }, }, });这些模块在后端构造时固定调用方源码无法提供或替换类型定义WorkspaceTrustedModule见 packages/computer/src/runtime/types.ts。受信任模块名必须以ws:开头、使用唯一简单的保留名且不得与内置的node:fs、node:fs/promises、ws:git、ws:artifacts冲突校验见 module-graph.ts。宿主桥接层在分发trusted/specifier.call时同样会校验参数与返回值均为 JSON 兼容的 Workspace 值assertBridgeValues并为受信任模块传入{ signal, deadline }取消上下文见 bridge.ts。九、相关文档与源码索引运行时入口与后端路由05. Runtime Interface、16. Execution runtime architecture后端类型与选项定义packages/computer/src/runtime/types.ts、packages/computer/src/backends/worker-javascript/worker-javascript.ts模块图构建与能力 shimpackages/computer/src/backends/worker-javascript/module-graph.ts帧编解码packages/computer/src/backends/worker-javascript/frames.ts宿主能力桥接与限额执行packages/computer/src/runtime/bridge.ts路径约束与结构化值校验packages/computer/src/runtime/capability.ts后端行为测试packages/computer/src/backends/worker-javascript/worker-javascript.test.ts包级总览与安装方式docs/README.mdnpm install cloudflare/computer入口子路径cloudflare/computer/backends/worker-javascript综上所述WorkerJavaScriptBackend的价值在于把运行一段不可信模块代码与访问持久化 Workspace 能力之间建立了一条显式、可配限额、可审计、可回放的受控边界模块图在加载前静态解析并约束能力调用在隔离内与宿主双重校验输出实时回流且终态严格落库。它是构建 Agent 代码执行、插件沙箱、脚本化任务等场景的可靠基础件。【免费下载链接】computerGive your agent a computer 项目地址: https://gitcode.com/GitHub_Trending/computer1/computer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考