EmDash può eseguire i plugin in due modalità: trusted e sandboxed. Questa pagina spiega come funzionano, quali protezioni offrono e le implicazioni di sicurezza per diversi target di deployment.
Modalità di esecuzione
| Trusted | Sandboxed | |
|---|---|---|
| Eseguito in | Processo principale | Isolate V8 isolato (Dynamic Worker Loader) |
| Capabilities | Indicative (non applicate) | Applicate a runtime |
| Limiti risorse | Nessuno | CPU, memoria, subrequest, tempo reale |
| Accesso di rete | Illimitato | Bloccato; solo via ctx.http con allowlist host |
| Accesso ai dati | Accesso completo al database | Limitato alle capabilities dichiarate tramite bridge RPC |
| Disponibile su | Tutte le piattaforme | Solo Cloudflare Workers |
Modalità trusted
I plugin trusted girano nello stesso processo del sito Astro. Sono caricati da pacchetti npm o file locali e configurati in astro.config.mjs:
import myPlugin from "@emdash-cms/plugin-analytics";
export default defineConfig({
integrations: [
emdash({
plugins: [myPlugin()],
}),
],
});
In modalità trusted:
- Le capabilities sono documentazione, non enforcement. Un plugin che dichiara
["read:content"]può comunque accedere a tutto nel processo. Il campocapabilitiesindica agli amministratori cosa il plugin intende usare. - Nessun limite di risorse. CPU, memoria e rete non sono limitati. Un plugin difettoso può bloccare l’intera richiesta.
- Accesso completo al processo. I plugin condividono il runtime Node.js/Workers con il sito Astro. Possono importare qualsiasi modulo, leggere le variabili d’ambiente e accedere al filesystem (su Node.js).
Modalità sandboxed (Cloudflare Workers)
I plugin sandboxed girano in isolate V8 isolati forniti dall’API Dynamic Worker Loader di Cloudflare. Ogni plugin ha il proprio isolate con limiti applicati.
Per abilitare il sandboxing, configura il sandbox runner nella config Astro:
export default defineConfig({
integrations: [
emdash({
sandboxRunner: "@emdash-cms/cloudflare/sandbox",
sandboxed: [
{
manifest: seoPluginManifest,
code: seoPluginCode,
},
],
}),
],
});
Cosa impone la sandbox
-
Enforcement delle capabilities
Se un plugin dichiara
capabilities: ["read:content"], può chiamare soloctx.content.get()ectx.content.list(). Tentarectx.content.create()genera un errore di permesso. È applicato dal bridge RPC — il plugin non può aggirarlo perché non ha accesso diretto al database. -
Limiti di risorse
Ogni invocazione (hook o route) gira con:
Risorsa Predefinito Applicato da Tempo CPU 50ms Worker Loader (isolate V8) Subrequest 10 per invocazione Worker Loader (isolate V8) Tempo reale 30 secondi Runner EmDash ( Promise.race)Memoria ~128MB Tetto piattaforma V8 (non configurabile per plugin) Superare i limiti CPU o subrequest fa abortire l’isolate nel Worker Loader e lancia un’eccezione. Superare il limite di tempo fa rifiutare la promise di invocazione da EmDash. La memoria è limitata dal tetto V8 ma non è configurabile per plugin.
Questi sono i default integrati. Limiti personalizzati si possono impostare fornendo una
SandboxRunnerFactoryche passa valori diversi viaSandboxOptions.limits. La configurazione per sito nell’integrazione EmDash non è ancora implementata. -
Isolamento di rete
I plugin sandboxed hanno
globalOutbound: null— le chiamatefetch()dirette sono bloccate a livello V8. Devono usarectx.http.fetch(), proxato dal bridge. Il bridge valida l’host di destinazione rispetto alla listaallowedHostsdel plugin. -
Scoping dello storage
Tutte le operazioni di storage (KV, collections) sono limitate all’ID del plugin. Un plugin non può leggere i dati di un altro. Accesso a content e media tramite bridge, che controlla le capabilities a ogni chiamata.
-
Restrizioni di funzionalità
Alcune funzioni sono disponibili solo in modalità trusted:
- API routes — Endpoint REST personalizzati (
routes) non disponibili. I plugin sandboxed interagiscono tramite pagine admin Block Kit e hook. - Tipi di blocco Portable Text — I blocchi PT richiedono componenti Astro per il rendering lato sito (
componentsEntry), caricati a build time da npm. I plugin sandboxed si installano a runtime e non possono includere componenti. - Pagine admin React personalizzate — I plugin sandboxed usano Block Kit per l’UI admin invece di componenti React.
Il comando
emdash plugin bundleavvisa se un plugin dichiara queste funzionalità. - API routes — Endpoint REST personalizzati (
Architettura
I plugin sandboxed comunicano con EmDash tramite un bridge RPC:
┌─────────────────────┐ 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 │
└──────────────┘
Il codice del plugin gira in un isolate V8. Riceve un oggetto ctx dove ogni metodo è un proxy verso il bridge. Il bridge gira nel worker principale EmDash ed esegue le operazioni reali su database/storage dopo la validazione delle capabilities.
Configurazione Wrangler
Il sandboxing richiede Dynamic Worker Loader. Aggiungi a wrangler.jsonc:
{
"worker_loaders": [{ "binding": "LOADER" }],
"r2_buckets": [{ "binding": "MEDIA", "bucket_name": "emdash-media" }],
"d1_databases": [{ "binding": "DB", "database_name": "emdash" }]
}
Deployment Node.js
In deployment su Node.js (o qualsiasi piattaforma non Cloudflare):
- Si usa
NoopSandboxRunner. RestituisceisAvailable() === false. - Tentare di caricare plugin sandboxed lancia
SandboxNotAvailableError. - Tutti i plugin devono essere registrati come trusted nell’array
plugins. - Le dichiarazioni di capabilities sono solo informative — non sono applicate.
Cosa significa per la sicurezza
| Minaccia | Cloudflare (Sandboxed) | Node.js (solo Trusted) |
|---|---|---|
| Il plugin legge dati che non dovrebbe | Bloccato dai controlli capabilities sul bridge | Non impedito — accesso DB completo |
| Il plugin effettua chiamate di rete non autorizzate | Bloccato da globalOutbound: null + allowlist host | Non impedito — può chiamare fetch() direttamente |
| Il plugin esaurisce la CPU | Isolate abortito dal Worker Loader | Non impedito — blocca l’event loop |
| Il plugin esaurisce la memoria | Isolate terminato dal Worker Loader | Non impedito — può far crashare il processo |
| Il plugin accede alle variabili d’ambiente | Nessun accesso (contesto V8 isolato) | Non impedito — condivide process.env |
| Il plugin accede al filesystem | Nessun filesystem in Workers | Non impedito — accesso completo a fs |
Raccomandazioni per deployment Node.js
- Installa plugin solo da fonti fidate. Revisiona il codice sorgente prima dell’installazione. Preferisci plugin di maintainer noti.
- Usa le capabilities come checklist di review. Anche senza enforcement documentano la portata prevista. Un plugin con
["network:fetch"]che non serve rete è sospetto. - Monitora l’uso delle risorse. Usa monitoraggio a livello processo (es.
--max-old-space-size, health check) per individuare plugin fuori controllo. - Considera Cloudflare per plugin non attendibili. Se devi eseguire plugin da origini sconosciute (es. marketplace), deploya su Cloudflare Workers dove il sandboxing è disponibile.
Stessa API, garanzie diverse
Il codice del plugin è identico in entrambe le modalità. L’API definePlugin(), la forma di ctx, hooks, routes e storage funzionano allo stesso modo. Ciò che cambia è l’enforcement:
// 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 }),
});
},
},
});
L’obiettivo è permettere agli autori di sviluppare in locale in modalità trusted (iterazione più veloce, debug più semplice) e deployare in produzione in modalità sandboxed senza modificare il codice.