I plugin sandboxed sono isolati per impostazione predefinita. Per fare qualsiasi cosa oltre alla lettura e scrittura del proprio KV e storage, un plugin deve dichiarare una capacità nel suo manifesto. Il ponte della sandbox controlla ogni API fornita dall’host in base a queste dichiarazioni — un plugin che non ha dichiarato content:read non ottiene ctx.content, e uno che non ha dichiarato network:request non ottiene ctx.http.
Questa pagina copre cosa concede ogni capacità, come la sandbox le applica e cosa non è applicabile.
Dichiarare le capacità
Le capacità si trovano in emdash-plugin.jsonc, insieme a slug e al resto del contratto di fiducia:
{
"slug": "plugin-hello",
// ...identità + profilo...
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
Dichiara solo ciò di cui il plugin ha effettivamente bisogno. Le dichiarazioni di capacità sono anche ciò che il Marketplace mostra agli operatori nel dialogo di consenso — capacità aggiuntive sono attrito nell’installazione e un segnale di sicurezza negli audit.
Riferimento capacità
| Capacità | Concede accesso a |
|---|---|
content:read | ctx.content.get(), ctx.content.list() |
content:write | ctx.content.create(), ctx.content.update(), ctx.content.delete() (implica content:read) |
taxonomies:read | ctx.taxonomies.getAll(), ctx.taxonomies.getTerms(), ctx.taxonomies.getEntryTerms() |
media:read | ctx.media.get(), ctx.media.list() |
media:write | ctx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (implica media:read) |
network:request | ctx.http.fetch() — limitato a allowedHosts |
network:request:unrestricted | ctx.http.fetch() senza restrizione di host (solo per URL configurati dall’utente) |
users:read | ctx.users.get(), ctx.users.getByEmail(), ctx.users.list() |
email:send | ctx.email.send() (richiede un plugin provider email configurato) |
hooks.email-transport:register | Permette la registrazione dell’hook esclusivo email:deliver (provider di trasporto) |
hooks.email-events:register | Permette la registrazione degli hook email:beforeSend / email:afterSend |
hooks.page-fragments:register | Permette la registrazione dell’hook page:fragments (solo plugin nativi) |
Alcune cose da sapere:
- Implicazioni.
content:writeimplica automaticamentecontent:read;media:writeimplicamedia:read;network:request:unrestrictedimplicanetwork:request. Non è necessario elencare entrambi. - Le tassonomie sono una superficie separata di sola lettura.
taxonomies:readconcede accesso alle definizioni di tassonomia, ai loro termini e ai termini assegnati a una voce tramitectx.taxonomies. È indipendente dacontent:read— dichiara entrambi se il plugin legge contenuti e la loro classificazione. Non c’è accesso in scrittura alle tassonomie dai plugin. network:request:unrestrictedesiste per URL configurati dall’utente. Un plugin webhook dove l’operatore inserisce l’URL di destinazione deve raggiungere host che non sono nel manifesto. I plugin che chiamano sempre API conosciute dovrebbero usarenetwork:request+allowedHosts.email:sendè controllato dalla configurazione, non solo dalla capacità. Un plugin può dichiarareemail:send, mactx.emailviene popolato solo se un altro plugin ha registrato un trasportoemail:deliver.
Liste di host consentiti per la rete
I plugin con network:request possono fare fetch solo verso host elencati in allowedHosts. I caratteri jolly sono supportati per i sottodomini:
"capabilities": ["network:request"],
"allowedHosts": [
"api.example.com", // host esatto
"*.cdn.example.com" // qualsiasi sottodominio di cdn.example.com
]
Il ponte verifica l’host dell’URL della richiesta rispetto alla lista consentita prima di inoltrare la richiesta. Una richiesta verso un host non dichiarato lancia un’eccezione all’interno del plugin senza mai uscire dalla sandbox.
network:request:unrestricted bypassa completamente il controllo della lista consentita. È destinato ai plugin dove l’operatore configura l’URL di destinazione a runtime (emittenti webhook, forwarder HTTP generici). Evitalo per plugin dove la destinazione è parte del design del plugin — dichiara invece network:request con host espliciti in modo che il dialogo di consenso dica agli operatori esattamente dove il plugin chiamerà.
Cosa applica la sandbox
Quando un runner sandbox è attivo, il runtime applica:
-
Controllo per capacità. La factory PluginContext popola
ctx.content,ctx.taxonomies,ctx.media,ctx.http,ctx.users,ctx.emailsolo se la capacità corrispondente è dichiarata. Chiamare un metodo su una capacità non dichiarata non è possibile — non c’è oggetto lì. -
Ambito di storage e KV. Ogni operazione di storage e KV è limitata allo slug del plugin. Un plugin non può leggere il KV o le collezioni di storage di un altro, e può accedere solo alle collezioni di storage dichiarate nel manifesto.
-
Isolamento di rete.
fetch()diretto e altre primitive di rete sono bloccati dal runner. L’unico percorso verso la rete èctx.http.fetch(), che passa attraverso la validazione degli host del ponte. -
Nessun binding dell’host. I plugin sandboxed non vedono variabili d’ambiente, filesystem o binding della piattaforma — anche se il tuo worker host li ha. Il runtime del plugin è un isolato pulito con solo il ponte e le capacità dichiarate.
-
Limiti di risorse. Il runner può applicare limiti di CPU, sotto-richieste, tempo wall-clock e memoria per invocazione. I limiti esatti dipendono dal runner in uso; il runner Cloudflare usa i limiti del Worker Loader della piattaforma (50ms CPU per invocazione, 10 sotto-richieste, 30 secondi wall-clock, ~128MB di memoria). Il runner workerd Node.js (
@emdash-cms/sandbox-workerd) applica il tempo wall-clock tramitePromise.race; i limiti di CPU e memoria sono caratteristiche della piattaforma Cloudflare e non sono applicati da workerd standalone. Gli hook che superano i limiti del runner vengono cancellati; il timeout degli hook di EmDash (timeoutnella configurazione dell’hook) applica inoltre un limite superiore più stretto.
Cosa la sandbox non applica
Alcune cose che il sistema di capacità non copre e non può coprire:
- Comportamento all’interno di una capacità concessa. Un plugin con
content:writepuò modificare qualsiasi contenuto, non solo il proprio. Le capacità sono a grana grossa — dicono “questo plugin può scrivere contenuti”, non “questo plugin può scrivere solo i contenuti che ha creato”. La revisione in fase di audit è l’unica verifica di ciò che un plugin fa effettivamente all’interno della sua concessione. - Fiducia dell’operatore su Node.js. Se il runner sandbox configurato segnala che non è disponibile (nessun Cloudflare Worker Loader, nessun runner lato Node installato, ecc.), i plugin
sandboxed: []vengono saltati all’avvio. Puoi spostarli inplugins: []per eseguirli nel processo — ma allora non c’è isolato V8, nessun limite di risorse, e il plugin può chiamarefetch()direttamente o leggere variabili d’ambiente. Tratta questo come fiducia a livello nativo. - Canali laterali. Temporizzazione, output dei log e dati memorizzati sono visibili a chiunque abbia accesso ragionevole all’ambiente host. Non usare la sandbox come confine di riservatezza contro l’operatore che la esegue.
Consenso alle capacità
Quando un operatore installa un plugin sandboxed dal Marketplace, EmDash mostra un dialogo di consenso con le capacità dichiarate. Gli aggiornamenti che aggiungono capacità — per esempio, un plugin che prima leggeva solo contenuti e ora vuole fare richieste di rete — vengono mostrati come un diff delle capacità e richiedono una nuova approvazione prima che la nuova versione abbia effetto.
Ecco perché è importante dichiarare capacità aggiuntive anche se “potresti averne bisogno in futuro”. Appaiono come attrito ad ogni installazione e aggiornamento, e gli audit di sicurezza segnalano plugin che chiedono più di quanto ovviamente necessitano. Elenca esattamente ciò che il plugin usa e aggiungi nuove capacità in una versione reale quando il plugin inizia effettivamente a usarle.
Validazione al momento del build
emdash-plugin bundle e emdash-plugin publish eseguono controlli aggiuntivi:
- Ogni capacità dichiarata deve essere nell’insieme riconosciuto (gli errori di battitura fanno fallire il build).
network:requestrichiede unallowedHostsnon vuoto;network:request:unrestrictedrichiede che sia vuoto. Vedi il riferimento del manifesto.- Il
backend.jsimpacchettato non può importare built-in di Node.js (fs,path,child_process, ecc.) — i runtime sandbox non li forniscono.
Vedi Impacchettare e pubblicare per la lista completa dei controlli.