Files
Arcrun/registry/components/code/DESIGN.md
T

7.0 KiB
Raw Blame History

code 零件 —— 沙箱設計小結(Arcrun#10)

狀態:Workers 就緒(裁定 A,可部署)Node/vitest 12/12 綠燈,未部署 leo21c。live 前需總管與 leo 過寫入/部署閘。封裝=Aquickjs-emscripten singlefile variant);B(自建 QuickJS+wasi-sdk)列後續技術債。

0. arcrun 零件 runtime 真相(先摸清再設計)

  • 每個 logic 零件 = 一顆 Worker{name}.arcrun.dev),POST JSON → 內部跑 wasm → JSON 回傳。
  • 零件實作 = Go//go:build tinygo)→ TinyGo 編成 WASI-preview1 wasmbuild 時靜態 bundle 進 Workerwrangler.toml [[wasm_modules]])。
  • hostcomponent-worker-template/src/index.ts用 TS 自己實作一份 WASI-preview1 shimfd_read 餵 stdin= POST body 的 JSON)、fd_write 收 stdout= 回傳 JSON)。
  • no_network / no_filesystem 不是靠 wasm 自律,是 host 根本不提供那些 importsock_* / path_* 全回 ENOSYS(76)u6u.http_request no-op。→ 零件對外「無 ambient 能力」是由 host 建構保證的。
  • runtime 是 workerd(=cf-workerscontract 的 wazero 只是相容標記(本機/CLI 測試路徑),生產不經 wazero。

關鍵結論host 只認 WASI-preview1 + stdin/stdout JSON與 guest 語言無關。所以載入「QuickJS 編成的 wasm」與載入 TinyGo wasm 在 runtime 層是同一件事 —— QuickJS-wasm 可行,且完全合現有零件模型。差別只在 guest 從「TinyGo」換成「QuickJS」,且 user 的 JS 是「跑時餵進去的資料」而非「build 時編進去的程式」。

1. 沙箱機制

user JS 跑在 QuickJS context 裡,該 context 三層封裝、逐層無逃逸:

Cloudflare Worker isolateV8
  └─ QuickJS wasm module(線性記憶體沙箱;無 WASI 網路/檔案 import
       └─ QuickJS JS contextglobal 只有純 ECMAScript 內建)
            └─ user code:讀 input、return 值
  • 無 ambient 能力(by constructionQuickJS context 起始 global 只有 Object/Array/JSON/Math/Date/String/RegExp…沒有 fetch/process/require/WebAssembly/XMLHttpRequest/globalThis.env。要給的能力必須 host 明確、逐一注入。
  • 唯一注入的 curated builtinsha256(str)(純、決定性、零能力 —— 不能碰網路/檔案/secret)。card→envelope 的 content_hash 需要它。任何新 builtin 都必須維持「純函式、無 ambient 能力」這條線。
  • input 穿越邊界:以 JSON 字串 marshal,沙箱內 JSON.parse —— host 與 guest 不共享物件圖,杜絕 prototype/引用逃逸。
  • user code 形狀:當「函式體」跑(可含 const/function 宣告、以 return 回值),綁定唯一入參 input。回值 JSON.stringify 後交回 host。

2. 生產路徑(裁定 A,已就緒)

sandbox.mjsruntime-agnostic 核心Node 測試與 CF Worker 共用同一份

  • 封裝@jitl/quickjs-singlefile-mjs-release-sync variantwasm 內嵌 base64、同步載入) —— CF Workers 相容 loading 路徑(不靠 fetch/fs 取 .wasm),bundler 友善、無需 [[wasm_modules]]
  • sha256 curated builtin:以純 JS SHA-256 字串 prelude 注入沙箱(不呼叫 host、不用 async Web Crypto、不需 nodejs_compat),Node/Worker 皆決定性;與 Node crypto sha256 逐字等價 (測試 ⑤ 對 card 全文比對 content_hash 相同)。「curated builtin = 純演算法字串」定為往後標準。
  • Worker hostindex.tsHonoPOST /→runCode),自足 Worker,不走 TinyGo 模板流程。
  • 測試sandbox.mjs + test/Node/vitest 12/12 全綠(含 card→envelope 全等)。
  • 與 B 的差B(自建 QuickJS+wasi-sdk→preview1 wasm,跑現有 WASI host shim)最同構,但需 wasi-sdk 工具鏈(本環境無 sysroot)+維護 C harness,列後續技術債。

3. 資源限制(防跑飛)

限制 機制 預設
執行 timeout QuickJS runtime setInterruptHandler(逐指令檢查 wall-clock deadline 1000 ms
記憶體 setMemoryLimitQuickJS runtime 硬上限) 16 MiB
堆疊 setMaxStackSize(防深遞迴) 512 KiB
輸出大小 host 量測 stdout JSON bytes,超限即 ResourceError 1 MiB
code 大小 host 量測 user code bytes,超限即 ResourceError 256 KiB

節點 config 可覆蓋(但不得放寬過契約 sandbox_limits 硬上限 —— 由零件在讀 config 時 clamp)。

4. 錯誤處理(Worker 絕不掛)

一律回結構化 envelopeerror_type 分類:

  • UserCodeErroruser code 拋錯 / 語法錯誤。
  • TimeoutError:超時被 interrupt。
  • ResourceError:記憶體 / 輸出 / code 超限。
  • ContractError:輸入形狀不合(如 code 非字串)。
  • SandboxError:其餘沙箱層例外。

5. 安全性質(總結)

  1. user code 無網路:沒有 fetch/XHRQuickJS wasm 也沒有 WASI socket import。
  2. user code 無檔案:沒有 fsWASI path_* 一律 ENOSYS。
  3. user code 無 env/secret:沒有 processWorker 的 envbindings/secret)不進 QuickJS context。
  4. user code 碰不到 Worker 物件圖:獨立 wasm 線性記憶體 + 獨立 QuickJS heap + JSON 邊界。
  5. 可終止:timeout/記憶體/輸出上限,跑飛也不拖垮 Worker。
  6. 決定性(除 Date/Math.random):curated builtin 全是純函式。

6. 設計岔路(給總管/leo 裁)

沙箱主機制 = QuickJS-wasm(本 PoC 已證可行且合模型)。封裝方式有兩條,取捨如下:

  • A. QuickJS-emscripten 直接在零件 Worker 用(PoC 走這條,推薦先行) 優點:本環境即可跑、npm 現成、限制 API 齊(timeout/mem/stack)、已在 Workers 驗證過可用。 缺點:不經現有 TinyGo-wasm 的 [[wasm_modules]] + 自建 WASI shim 路徑,是零件家族裡的「特例封裝」。
  • B. 自建 QuickJS+C-harness → wasi-sdk 編成 preview1 wasm,跑在現有 host shim(最「同構」) 優點:與其他零件同一條 runtime(同 WASI shim),wasi_target: preview1 名副其實,「無 ambient 能力」最純。 缺點:需要 wasi-sdk 工具鏈(本環境無 sysroot,無法即刻 build)、要維護一段 C harness。

建議:先以 A live(快、已驗證),把 B 列為後續「收斂到同構 runtime」的技術債。若總管要求所有零件單一 runtime,則走 B,但需先補 wasi-sdk build 基礎設施。此為安全敏感原語,機制選定請總管拍板再 live。

7. 首個消費者(Arcrun#8

km_wiki_ingest_drain 的 card→envelope 解析,把 card-to-envelope.mjsparseCard/planEnvelopes/planCard 當作 code 節點的 inline JS(去掉 import 'node:crypto'export,改用注入的 sha256)。PoC 測試 ⑤ 已證:沙箱輸出與原始模組 planCard 逐欄全等(含 content_hash)。→ 可丟掉 domain 零件 km_wiki_card_parse