沙箱插件預設是隔離的。要做超出讀寫自己的 KV 和 storage 之外的任何事情,插件必須在其 manifest 中宣告一個 capability。沙箱橋基於這些宣告控制每個主機提供的 API — 沒有宣告 content:read 的插件不會獲得 ctx.content,沒有宣告 network:request 的插件不會獲得 ctx.http。
本頁介紹每個 capability 授予什麼,沙箱如何強制執行它們,以及什麼是不可強制執行的。
宣告 capabilities
Capabilities 位於 emdash-plugin.jsonc 中,與 slug 和其餘的信任契約一起:
{
"slug": "plugin-hello",
// ...identity + profile...
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
只宣告插件實際需要的內容。Capability 宣告也是市場向網站運營者在同意對話框中顯示的內容 — 額外的 capabilities 在安裝時會造成摩擦,在稽核中是一個安全標記。
Capability 參考
| Capability | 授予存取權限 |
|---|---|
content:read | ctx.content.get(), ctx.content.list() |
content:write | ctx.content.create(), ctx.content.update(), ctx.content.delete() (包含 content:read) |
media:read | ctx.media.get(), ctx.media.list() |
media:write | ctx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (包含 media:read) |
network:request | ctx.http.fetch() — 限制在 allowedHosts |
network:request:unrestricted | ctx.http.fetch() 無主機限制(僅用於使用者設定的 URL) |
users:read | ctx.users.get(), ctx.users.getByEmail(), ctx.users.list() |
email:send | ctx.email.send() (需要已設定的電子郵件提供者插件) |
hooks.email-transport:register | 允許註冊獨占的 email:deliver hook(傳輸提供者) |
hooks.email-events:register | 允許註冊 email:beforeSend / email:afterSend hooks |
hooks.page-fragments:register | 允許註冊 page:fragments hook(僅限原生插件) |
一些值得了解的事情:
- 包含關係。
content:write自動包含content:read;media:write包含media:read;network:request:unrestricted包含network:request。你不需要同時列出兩者。 network:request:unrestricted存在是為了使用者設定的 URL。 一個 webhook 插件,其中運營者輸入目標 URL,需要存取 manifest 中沒有的主機。始終呼叫已知 API 的插件應使用network:request+allowedHosts。email:send由設定控制,而不僅僅是 capability。 插件可以宣告email:send,但只有在某個其他插件註冊了email:deliver傳輸時,ctx.email才會被填充。
網路主機允許清單
具有 network:request 的插件只能取得 allowedHosts 中列出的主機。支援子網域的萬用字元:
"capabilities": ["network:request"],
"allowedHosts": [
"api.example.com", // 精確主機
"*.cdn.example.com" // cdn.example.com 的任何子網域
]
橋在轉發請求之前檢查請求 URL 的主機是否在允許清單中。對未宣告的主機的請求會在插件內部拋出錯誤,而不會離開沙箱。
network:request:unrestricted 完全跳過允許清單檢查。它適用於運營者在執行時設定目標 URL 的插件(webhook 傳送器、通用 HTTP 轉發器)。對於目標是插件設計一部分的插件,請避免使用它 — 而是使用明確主機宣告 network:request,以便同意對話框準確告訴運營者插件將呼叫哪裡。
沙箱強制執行的內容
當沙箱執行器處於活動狀態時,執行階段強制執行:
-
Capability 閘控。 PluginContext 工廠僅在宣告了相應 capability 時才填充
ctx.content、ctx.media、ctx.http、ctx.users、ctx.email。在未宣告的 capability 上呼叫方法是不可能的 — 那裡沒有物件。 -
Storage 和 KV 作用域。 每個 storage 和 KV 操作都限定在插件的 slug 範圍內。插件無法讀取另一個插件的 KV 或其 storage 集合,它只能存取在 manifest 中宣告的 storage 集合。
-
網路隔離。 執行器阻止直接的
fetch()和其他網路原語。到達網路的唯一方法是ctx.http.fetch(),它通過橋的主機驗證。 -
無主機繫結。 沙箱插件看不到環境變數、檔案系統或任何平台繫結 — 即使你的主機 worker 有它們。插件執行階段是一個乾淨的隔離環境,只有橋和宣告的 capabilities。
-
資源限制。 執行器可以對每次呼叫強制執行 CPU、子請求、掛鐘時間和記憶體限制。確切的限制取決於你使用的執行器;Cloudflare 執行器使用平台的 Worker Loader 限制(每次呼叫 50ms CPU,10 個子請求,30 秒掛鐘時間,~128MB 記憶體)。超過執行器限制的 hooks 將被中止;EmDash hook 逾時(hook 設定中的
timeout)在此之上強制執行更嚴格的上限。
沙箱不強制執行的內容
capability 系統不涵蓋且無法涵蓋的一些內容:
- 授予的 capability 內的行為。 具有
content:write的插件可以編輯任何內容,而不僅僅是它自己的。Capabilities 是粗粒度的 — 它們說「這個插件可以寫內容」,而不是「這個插件只能寫它建立的內容」。稽核時的審查是對插件在其授權範圍內實際執行的操作的唯一檢查。 - Node.js 上的運營者信任。 當設定的沙箱執行器回報不可用時(沒有 Cloudflare Worker Loader,沒有安裝 Node 端執行器等),
sandboxed: []插件在啟動時被跳過。你可以將它們移到plugins: []中以在程序內執行它們 — 但那樣就沒有 V8 隔離,沒有資源限制,插件可以直接呼叫fetch()或讀取環境變數。將其視為原生層級的信任。 - 側通道。 時序、日誌輸出和儲存的資料對於任何有適當存取主機環境權限的人都是可見的。不要將沙箱用作對抗執行它的運營者的保密邊界。
Capability 同意
當運營者從市場安裝沙箱插件時,EmDash 顯示一個同意對話框,列出宣告的 capabilities。新增 capabilities 的更新 — 例如,以前只讀取內容的插件現在想要發出網路請求 — 顯示為 capability 差異,並在新版本生效之前需要新的核准。
這就是為什麼即使你「可能稍後使用它們」,宣告額外的 capabilities 也很重要。它們在每次安裝和更新時都會顯示為摩擦,安全稽核會標記要求超過其明顯需要的插件。準確列出插件使用的內容,並在插件實際開始使用它們時在真實版本中新增新的 capabilities。
打包時驗證
emdash-plugin bundle 和 emdash-plugin publish 執行額外的檢查:
- 每個宣告的 capability 必須在認可的集合中(拼寫錯誤會導致建置失敗)。
network:request需要非空的allowedHosts;network:request:unrestricted需要它為空。參見 manifest 參考。- 打包的
backend.js不能匯入 Node.js 內建模組(fs、path、child_process等)— 沙箱執行階段不提供它們。
有關檢查的完整清單,請參閱 Bundling and publishing。