沙箱外掛預設是隔離的。要做超出讀寫自己的 KV 和儲存之外的任何事情,外掛必須在其描述符上宣告一個 capability。沙箱橋根據這些宣告控制每個主機提供的 API — 沒有宣告 content:read 的外掛不會獲得 ctx.content,沒有宣告 network:request 的外掛不會獲得 ctx.http。
本頁介紹每個 capability 授予什麼,沙箱如何強制執行它們,以及什麼是不可強制執行的。
宣告 capabilities
Capabilities 位於描述符(由 astro.config.mjs 匯入的檔案)上,與 id、version 和 entrypoint 一起:
export function helloPlugin(): PluginDescriptor {
return {
id: "plugin-hello",
version: "0.1.0",
format: "standard",
entrypoint: "@my-org/plugin-hello/sandbox",
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 鉤子(傳輸提供者) |
hooks.email-events:register | 允許註冊 email:beforeSend / email:afterSend 鉤子 |
hooks.page-fragments:register | 允許註冊 page:fragments 鉤子(僅原生外掛) |
一些值得了解的事情:
- 隱含關係。
content:write自動隱含content:read;media:write隱含media:read;network:request:unrestricted隱含network:request。你不需要同時列出兩者。 network:request:unrestricted存在於使用者設定的 URL。 營運者輸入目標 URL 的 webhook 外掛需要存取清單中沒有的主機。始終呼叫已知 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 上呼叫方法 — 那裡沒有物件。 -
儲存和 KV 作用域。 每個儲存和 KV 操作都限定為外掛的 id。外掛無法讀取另一個外掛的 KV 或其儲存集合,並且只能存取在描述符上宣告的儲存集合。
-
網路隔離。 直接
fetch()和其他網路原語被執行器阻止。存取網路的唯一方式是ctx.http.fetch(),它通過橋的主機驗證。 -
無主機繫結。 沙箱外掛看不到環境變數、檔案系統或任何平台繫結 — 即使你的主機工作器有它們。外掛執行階段是一個乾淨的隔離,只有橋和宣告的 capabilities。
-
資源限制。 執行器可以對每次呼叫強制執行 CPU、子請求、掛鐘時間和記憶體限制。確切的限制取決於你使用的執行器;Cloudflare 執行器使用平台的 Worker Loader 限制(每次呼叫 50ms CPU、10 個子請求、30 秒掛鐘時間、約 128MB 記憶體)。超出執行器限制的鉤子會被中止;EmDash 鉤子逾時(鉤子設定中的
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 必須在已知集合中(拼寫錯誤會導致建置失敗)。
- 在未填充
allowedHosts的情況下宣告network:request的外掛會觸發警告 — 宣告主機或切換到network:request:unrestricted並說明原因。 - 棄用的 capability 名稱在
bundle/validate期間觸發警告,在publish時硬失敗。 - 打包的
backend.js無法匯入 Node.js 內建模組(fs、path、child_process等) — 沙箱執行階段不提供它們。
有關檢查的完整清單,請參閱打包和發布。