EmDash ではプラグインを trusted と sandboxed の 2 つの実行モードで動かせます。本ページでは各モードの動作、保護内容、デプロイ先ごとのセキュリティ上の意味を説明します。
実行モード
| Trusted | Sandboxed | |
|---|---|---|
| 実行場所 | メインプロセス | 分離された V8 isolate(Dynamic Worker Loader) |
| Capabilities | 参考情報(強制されない) | 実行時に強制 |
| リソース制限 | なし | CPU、メモリ、サブリクエスト、経過時間 |
| ネットワーク | 制限なし | ブロック。ctx.http とホスト許可リストのみ |
| データアクセス | DB フルアクセス | 宣言した capabilities 経由の RPC ブリッジに限定 |
| 利用可能 | 全プラットフォーム | Cloudflare Workers のみ |
Trusted モード
Trusted プラグインは Astro サイトと同一プロセスで動きます。npm パッケージまたはローカルファイルから読み込み、astro.config.mjs で設定します。
import myPlugin from "@emdash-cms/plugin-analytics";
export default defineConfig({
integrations: [
emdash({
plugins: [myPlugin()],
}),
],
});
Trusted モードでは:
- Capabilities はドキュメントであり強制ではありません。
["read:content"]を宣言していても、プロセス内のあらゆるものにアクセスできます。capabilitiesフィールドは管理者向けに、プラグインが 意図して 使う範囲を示します。 - リソース上限がありません。 CPU・メモリ・ネットワークは無制限で、問題のあるプラグインがリクエスト全体を止める可能性があります。
- プロセスへのフルアクセス。 プラグインは Astro サイトと同じ Node.js/Workers ランタイムを共有します。任意のモジュールの import、環境変数の参照、(Node.js では)ファイルシステムの読み書きが可能です。
Sandboxed モード(Cloudflare Workers)
Sandboxed プラグインは Cloudflare の Dynamic Worker Loader API による分離 V8 isolate 上で動きます。各プラグインは制限付きの独自 isolate を持ちます。
サンドボックスを有効にするには、Astro 設定で sandbox runner を指定します。
export default defineConfig({
integrations: [
emdash({
sandboxRunner: "@emdash-cms/cloudflare/sandbox",
sandboxed: [
{
manifest: seoPluginManifest,
code: seoPluginCode,
},
],
}),
],
});
サンドボックスが強制すること
-
Capability の強制
プラグインが
capabilities: ["read:content"]と宣言した場合、ctx.content.get()とctx.content.list()のみ呼べます。ctx.content.create()を試みると権限エラーになります。RPC ブリッジが強制するため、DB に直接触れないプラグインは迂回できません。 -
リソース制限
各呼び出し(フックまたはルート)は次の制限下で実行されます。
リソース デフォルト 強制主体 CPU 時間 50ms Worker Loader(V8 isolate) サブリクエスト 呼び出しあたり 10 Worker Loader(V8 isolate) 経過時間 30 秒 EmDash ランナー( Promise.race)メモリ 約 128MB V8 プラットフォーム上限(プラグイン単位では設定不可) CPU またはサブリクエスト超過で Worker Loader が isolate を中止し例外を投げます。経過時間超過で EmDash が呼び出し Promise を拒否します。メモリは V8 上限で抑えられますが、プラグインごとの設定はできません。
これらは組み込みデフォルトです。
SandboxRunnerFactoryでSandboxOptions.limitsに別値を渡すとカスタム制限にできます。EmDash 統合設定によるサイト単位の設定は未実装です。 -
ネットワーク分離
Sandboxed プラグインは
globalOutbound: nullのため、直接のfetch()は V8 レベルでブロックされます。ctx.http.fetch()を使い、ブリッジ経由でプロキシされます。ブリッジは宛先ホストをプラグインのallowedHostsと照合します。 -
ストレージのスコープ
KV・コレクションなどのストレージ操作はプラグイン ID にスコープされます。他プラグインのデータは読めません。コンテンツ・メディアはブリッジ経由で、呼び出しごとに capability を検査します。
-
機能制限
次は Trusted モードのみで利用可能です。
- API routes — カスタム REST エンドポイント(
routes)は使えません。Sandboxed プラグインは Block Kit の管理画面とフックでやり取りします。 - Portable Text ブロック型 — PT ブロックはサイト側レンダリング用の Astro コンポーネント(
componentsEntry)が必要で、ビルド時に npm から読み込まれます。Sandboxed プラグインは実行時インストールのためコンポーネントを同梱できません。 - カスタム React 管理ページ — Sandboxed プラグインは React コンポーネントではなく Block Kit で管理 UI を構築します。
これらを宣言していると
emdash plugin bundleが警告します。 - API routes — カスタム REST エンドポイント(
アーキテクチャ
Sandboxed プラグインは RPC ブリッジで EmDash と通信します。
┌─────────────────────┐ RPC ┌──────────────────────┐
│ Plugin Isolate │ ◄──────────► │ PluginBridge │
│ (Worker Loader) │ (binding) │ (WorkerEntrypoint) │
│ │ │ │
│ ctx.kv.get(k) │──────────────│► kvGet(k) │
│ ctx.content.list() │──────────────│► contentList() │
│ ctx.http.fetch(u) │──────────────│► httpFetch(u) │
└─────────────────────┘ └──────────────────────┘
│
▼
┌──────────────┐
│ D1 / R2 │
└──────────────┘
プラグインコードは V8 isolate 上で動き、ctx の各メソッドはブリッジへのプロキシです。ブリッジはメインの EmDash worker で動き、capability 検証のうえで実際の DB/ストレージ操作を行います。
Wrangler 設定
サンドボックスには Dynamic Worker Loader が必要です。wrangler.jsonc に追加します。
{
"worker_loaders": [{ "binding": "LOADER" }],
"r2_buckets": [{ "binding": "MEDIA", "bucket_name": "emdash-media" }],
"d1_databases": [{ "binding": "DB", "database_name": "emdash" }]
}
Node.js デプロイ
Node.js(または Cloudflare 以外のプラットフォーム)にデプロイする場合:
NoopSandboxRunnerが使われ、isAvailable() === falseを返します。- Sandboxed プラグインの読み込みは
SandboxNotAvailableErrorになります。 - すべてのプラグインは
plugins配列に Trusted として登録する必要があります。 - capability 宣言は情報のみで強制されません。
セキュリティ上の意味
| 脅威 | Cloudflare(Sandboxed) | Node.js(Trusted のみ) |
|---|---|---|
| 許可外データの読取 | ブリッジの capability チェックでブロック | 防げない — DB フルアクセス |
| 不正なネットワーク呼び出し | globalOutbound: null とホスト許可でブロック | 防げない — 直接 fetch() 可能 |
| CPU 枯渇 | Worker Loader が isolate を中止 | 防げない — イベントループを塞ぐ |
| メモリ枯渇 | Worker Loader が isolate を終了 | 防げない — プロセスクラッシュの恐れ |
| 環境変数へのアクセス | なし(分離 V8 コンテキスト) | 防げない — process.env を共有 |
| ファイルシステムへのアクセス | Workers に FS なし | 防げない — fs フルアクセス |
Node.js デプロイの推奨
- 信頼できるソースのプラグインのみ。 インストール前にソースを確認し、既知のメンテナーのプラグインを優先してください。
- Capability をレビュー用チェックリストに。 強制されなくても意図した範囲の文書になります。ネットワーク不要なのに
["network:fetch"]があるのは不審です。 - リソース使用を監視。 プロセス単位の監視(例:
--max-old-space-size、ヘルスチェック)で暴走を検知してください。 - 信頼できないプラグインは Cloudflare を検討。 出所不明のプラグイン(マーケットプレイス等)を動かすなら、サンドボックスのある Cloudflare Workers へデプロイしてください。
API は同じ、保証は異なる
実行モードにかかわらずプラグインコードは同じです。definePlugin() API、ctx の形、hooks、routes、storage の使い方は同じで、変わるのは 強制の有無 です。
// This plugin works in both trusted and sandboxed mode
export default definePlugin({
id: "analytics",
version: "1.0.0",
capabilities: ["read:content", "network:fetch"],
allowedHosts: ["api.analytics.example.com"],
hooks: {
"content:afterSave": async (event, ctx) => {
// In trusted mode: ctx.http is always present (capabilities not enforced)
// In sandboxed mode: ctx.http is present because "network:fetch" is declared
await ctx.http.fetch("https://api.analytics.example.com/track", {
method: "POST",
body: JSON.stringify({ contentId: event.content.id }),
});
},
},
});
ローカルでは Trusted で高速に開発・デバッグし、本番ではコード変更なしで Sandboxed に載せられることが狙いです。