Capacità e sicurezza

In questa pagina

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:readctx.content.get(), ctx.content.list()
content:writectx.content.create(), ctx.content.update(), ctx.content.delete() (implica content:read)
taxonomies:readctx.taxonomies.getAll(), ctx.taxonomies.getTerms(), ctx.taxonomies.getEntryTerms()
media:readctx.media.get(), ctx.media.list()
media:writectx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (implica media:read)
network:requestctx.http.fetch() — limitato a allowedHosts
network:request:unrestrictedctx.http.fetch() senza restrizione di host (solo per URL configurati dall’utente)
users:readctx.users.get(), ctx.users.getByEmail(), ctx.users.list()
email:sendctx.email.send() (richiede un plugin provider email configurato)
hooks.email-transport:registerPermette la registrazione dell’hook esclusivo email:deliver (provider di trasporto)
hooks.email-events:registerPermette la registrazione degli hook email:beforeSend / email:afterSend
hooks.page-fragments:registerPermette la registrazione dell’hook page:fragments (solo plugin nativi)

Alcune cose da sapere:

  • Implicazioni. content:write implica automaticamente content:read; media:write implica media:read; network:request:unrestricted implica network:request. Non è necessario elencare entrambi.
  • Le tassonomie sono una superficie separata di sola lettura. taxonomies:read concede accesso alle definizioni di tassonomia, ai loro termini e ai termini assegnati a una voce tramite ctx.taxonomies. È indipendente da content:read — dichiara entrambi se il plugin legge contenuti e la loro classificazione. Non c’è accesso in scrittura alle tassonomie dai plugin.
  • network:request:unrestricted esiste 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 usare network:request + allowedHosts.
  • email:send è controllato dalla configurazione, non solo dalla capacità. Un plugin può dichiarare email:send, ma ctx.email viene popolato solo se un altro plugin ha registrato un trasporto email: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:

  1. Controllo per capacità. La factory PluginContext popola ctx.content, ctx.taxonomies, ctx.media, ctx.http, ctx.users, ctx.email solo se la capacità corrispondente è dichiarata. Chiamare un metodo su una capacità non dichiarata non è possibile — non c’è oggetto lì.

  2. 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.

  3. 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.

  4. 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.

  5. 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 tramite Promise.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 (timeout nella 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:write può 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 in plugins: [] per eseguirli nel processo — ma allora non c’è isolato V8, nessun limite di risorse, e il plugin può chiamare fetch() 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:request richiede un allowedHosts non vuoto; network:request:unrestricted richiede che sia vuoto. Vedi il riferimento del manifesto.
  • Il backend.js impacchettato 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.