Sandboxed Plugins sind standardmäßig isoliert. Um etwas über das Lesen und Schreiben des eigenen KV und Storage hinaus zu tun, muss ein Plugin in seinem Manifest eine Capability deklarieren. Die Sandbox-Bridge steuert jede vom Host bereitgestellte API anhand dieser Deklarationen — ein Plugin, das content:read nicht deklariert hat, erhält kein ctx.content, und eines, das network:request nicht deklariert hat, erhält kein ctx.http.
Diese Seite behandelt, was jede Capability gewährt, wie die Sandbox sie durchsetzt und was nicht durchsetzbar ist.
Capabilities deklarieren
Capabilities leben in emdash-plugin.jsonc, neben slug und dem Rest des Trust-Vertrags:
{
"slug": "plugin-hello",
// ...identity + profile...
"capabilities": ["content:read", "network:request"],
"allowedHosts": ["api.example.com"]
}
Deklarieren Sie nur, was das Plugin tatsächlich braucht. Die Registry zeigt diese Capabilities Site-Operatoren vor der Installation, daher bittet jede zusätzliche Deklaration sie, Zugriff zu genehmigen, den das Plugin nicht nutzt.
Capability-Referenz
| Capability | Gewährt Zugriff auf |
|---|---|
content:read | ctx.content.get(), ctx.content.list(), ctx.content.getTranslations(), ctx.content.getPublicUrl() |
content:revisions:read | ctx.content.listRevisions(), ctx.content.getRevision() (impliziert content:read) |
content:write | ctx.content.create(), ctx.content.update(), ctx.content.delete() (impliziert content:read) |
content:publish | Versionierte Publish-, Unpublish-, Schedule- und Unschedule-Operationen (impliziert content:read) |
content:restore | Gelöschten Inhalt lesen und wiederherstellen |
comments:read | ctx.comments.get(), ctx.comments.list(), ctx.comments.count() und persönliche Kommentar-Daten |
comments:moderate | ctx.comments.setStatus() mit Expected-Status-Concurrency-Kontrolle (impliziert comments:read) |
schema:read | ctx.schema.listCollections(), ctx.schema.getCollection() |
hooks.content-policy:register | Policy-Hooks content:beforePublish, content:beforeSchedule und content:beforeUnpublish |
taxonomies:read | ctx.taxonomies.getAll(), ctx.taxonomies.getTerms(), ctx.taxonomies.getEntryTerms() |
taxonomies:write | ctx.taxonomies.createTerm(), ctx.taxonomies.addEntryTerms(), ctx.taxonomies.removeEntryTerms() (impliziert taxonomies:read) |
redirects:read | ctx.redirects.list(), ctx.redirects.get() |
redirects:write | ctx.redirects.create(), ctx.redirects.update(), ctx.redirects.delete() (impliziert redirects:read) |
media:read | ctx.media.get(), ctx.media.list() |
media:bytes:read | ctx.media.readBytes() für bereite Medien, mit begrenzter gepufferter Antwort |
media:metadata:write | ctx.media.updateMetadata() für Alt-Text, Captions und Fokuspunkte |
media:write | ctx.media.getUploadUrl(), ctx.media.upload(), ctx.media.delete() (impliziert media:read) |
network:request | ctx.http.fetch() — beschränkt auf allowedHosts |
network:request:unrestricted | ctx.http.fetch() ohne Host-Beschränkung (nur für benutzerkonfigurierte URLs) |
users:read | ctx.users.get(), ctx.users.getByEmail(), ctx.users.list() |
email:send | ctx.email.send() (erfordert ein konfiguriertes E-Mail-Provider-Plugin) |
hooks.email-transport:register | Erlaubt die Registrierung des exklusiven Hooks email:deliver (Transport-Provider) |
hooks.email-events:register | Erlaubt die Registrierung der Hooks email:beforeSend / email:afterSend |
hooks.page-fragments:register | Erlaubt die Registrierung des Hooks page:fragments (nur Native Plugins) |
Die folgenden Regeln beeinflussen, welche Capabilities ein Plugin braucht:
- Implikationen.
content:write,content:revisions:readundcontent:publishimplizieren automatischcontent:read;comments:moderateimpliziertcomments:read;taxonomies:writeimplizierttaxonomies:read;media:writeimpliziertmedia:read;redirects:writeimpliziertredirects:read;network:request:unrestrictedimpliziertnetwork:request. Sie müssen nicht beide auflisten. - Medien-Autoritäten sind getrennt.
media:read,media:bytes:readundmedia:metadata:writeimplizieren einander nicht. Deklarieren Sie jede Operation, die das Plugin nutzt. Die bestehende Capabilitymedia:writeimpliziert weiterhinmedia:readaus Kompatibilitätsgründen. - Taxonomien sind von Inhalt getrennt. Taxonomie-Capabilities gewähren weder
content:readnochcontent:write. Deklarieren Sie die passende Content-Capability, wenn das Plugin auch Eintragsfelder liest oder bearbeitet. - Publication Policy ist von Content-Zugriff getrennt.
hooks.content-policy:registerlässt ein Plugin Publication-Zustandsänderungen über Policy-Hook-Events prüfen und ablehnen. Es stellt keinctx.contentbereit und gewährt keine Content-Bearbeitungs- oder Publication-Aktionen. network:request:unrestrictedexistiert für benutzerkonfigurierte URLs. Ein Webhook-Plugin, bei dem der Operator die Ziel-URL eingibt, muss Hosts erreichen, die nicht im Manifest stehen. Plugins, die immer bekannte APIs aufrufen, solltennetwork:request+allowedHostsnutzen.email:sendwird durch Konfiguration gesteuert, nicht nur durch die Capability. Ein Plugin kannemail:senddeklarieren, aberctx.emailwird nur befüllt, wenn ein anderes Plugin einenemail:deliver-Transport registriert hat.
content:read gibt sichere Eintragsidentität zurück, einschließlich Autor-ID, Übersetzungsgruppe, Revisionszeigern und Zeilenversion. Nutzen Sie getTranslations(), um Locale-Geschwister zu entdecken, und getPublicUrl(), um eine veröffentlichte Route mit den Locale- und Trailing-Slash-Regeln der Site aufzulösen. getPublicUrl() gibt null für Drafts, nicht routbare Collections, fehlende Slugs und Locales zurück, die die Site nicht bedient. Es gibt niemals eine Preview-URL zurück.
Revisionssnapshots können Feldwerte enthalten, die ein Administrator später entfernt hat. Deklarieren Sie content:revisions:read nur, wenn das Plugin die zurückbehaltene Historie braucht. Revisionsergebnisse lassen die Identität des Revisionsautors weg.
schema:read legt Collection- und Felddefinitionen ohne Datenbank-IDs, Zeitstempel, Migrationsmetadaten oder SQL-Spaltentypen offen. Versteckte Collections bleiben sichtbar, weil hidden die Admin-Navigation steuert und nicht den Datenzugriff.
Inhalt erstellen und übersetzen
ctx.content.create() akzeptiert ein optionales drittes Argument für die Locale des neuen Eintrags:
const post = await ctx.content.create(
"posts",
{ title: "繁體中文" },
{ locale: "zh-tw" },
);
Locale-Matching ist case-insensitive und speichert die Schreibweise aus der Locale-Konfiguration der Site, sodass zh-tw zu zh-TW wird, wenn das die konfigurierte Form ist. Eine fehlerhafte explizite Locale wirft immer; wenn i18n konfiguriert ist, wirft auch eine explizite Locale außerhalb der konfigurierten Locale-Liste. Wenn die Option weggelassen wird, nutzt EmDash die konfigurierte Standard-Locale der Site; Sites ohne i18n-Konfiguration behalten den Standard en.
Um eine Locale zu einem bestehenden Eintrag hinzuzufügen, übergeben Sie seine Datenbank-ID als translationOf:
const translatedPost = await ctx.content.create(
"posts",
{ title: "Bienvenue", sku: "ignored-for-shared-fields" },
{ locale: "fr", translationOf: sourcePost.id },
);
Die Quelle muss ein aktiver Eintrag in derselben Collection sein. Der neue Eintrag tritt seiner Übersetzungsgruppe bei, erbt seine Byline-Credits und Taxonomie-Zuweisungen und startet mit den Quellwerten für als nicht übersetzbar markierte Felder. Ein für ein nicht übersetzbares Feld gelieferter Wert ersetzt während der Übersetzungserstellung nicht den Quellwert. Content-Validierung und Save-Hooks laufen über denselben Runtime-Pfad wie andere Content-Creates. EmDash tritt nicht erneut in den eigenen content:afterSave-Hook des erstellenden Plugins ein, und Inhalt, der aus einem Save-Hook heraus erstellt wird, führt Save-Hooks nicht erneut aus.
Jede Übersetzungsgruppe kann einen aktiven Eintrag pro Locale enthalten. Das Erstellen eines zweiten Eintrags für dieselbe Gruppe und Locale wirft einen CONFLICT-Fehler. Eine fehlende Quelle wirft NOT_FOUND, eine ungültige oder nicht konfigurierte Locale wirft VALIDATION_ERROR, und ein Save-Hook kann das Create mit SAVE_REJECTED stoppen.
Publication-Zustand ändern
Deklarieren Sie content:publish, um einen Eintrag zu veröffentlichen, zurückzuziehen, zu planen oder die Planung aufzuheben. Jede Aktion erfordert das opake _rev, das von getVersioned() oder der vorherigen Aktion zurückgegeben wird. EmDash leitet diese Methoden über dieselben Policy-Hooks, Revisionsförderung, Locale-Synchronisation, Redirects, Media-Usage-Updates, Cache-Invalidierung und After-Hooks wie REST- und MCP-Aktionen.
Die folgende Route veröffentlicht den aktuellen Draft nur, wenn sich der Eintrag seit dem Lesen nicht geändert hat:
const current = await ctx.content!.getVersioned!("posts", postId);
if (!current) return { ok: false, error: "NOT_FOUND" };
try {
const published = await ctx.content!.publish!("posts", postId, {
_rev: current._rev,
});
return { ok: true, content: published.item, _rev: published._rev };
} catch (error) {
return { ok: false, error: "PUBLISH_FAILED" };
}
schedule() akzeptiert { scheduledAt, _rev }; die anderen Publication-Methoden akzeptieren { _rev }. Diese Methoden akzeptieren keinen publishedAt-Override.
Deklarieren Sie content:restore separat, um gelöschte Einträge zu lesen und wiederherzustellen. getTrashedVersioned() gibt null für einen live oder fehlenden Eintrag zurück. Übergeben Sie sein _rev an restore(), damit eine parallele Änderung einen Konflikt zurückgibt, statt veralteten Zustand wiederherzustellen.
Taxonomie-Terms erstellen und zuweisen
taxonomies:write lässt ein Plugin Terms erstellen und Zuweisungs-Deltas anwenden. Übergeben Sie Term-Zeilen-IDs oder Übersetzungsgruppen-IDs. Term-Slugs werden nicht akzeptiert, weil sie nach Taxonomie und Locale scoped sind.
Das folgende Beispiel erstellt eine Kindkategorie und weist sie zu, ohne die anderen Kategorien des Eintrags zu ersetzen:
const releaseNotes = await ctx.taxonomies!.createTerm!("category", {
label: "Release notes",
parentId: productUpdatesId,
locale: "en",
});
await ctx.taxonomies!.addEntryTerms!("posts", postId, "category", [releaseNotes.id]);
addEntryTerms() und removeEntryTerms() sind idempotente Mengen-Deltas. Parallele Hinzufügungen erhalten jede Zuweisung. EmDash prüft, dass die Taxonomie an die Collection angehängt ist, der Eintrag existiert und jeder Term zur genannten Taxonomie gehört. createTerm() lehnt parentId ab, wenn die Taxonomie nicht hierarchisch ist, statt es zu ignorieren. Das Erstellen eines übersetzten Terms mit translationOf tritt der Übersetzungsgruppe des Quellterms bei; die Quelle muss zur selben Taxonomie gehören, und die Gruppe kann nur einen Term pro Locale enthalten.
Taxonomie-Definitionserstellung, Collection-Anhängen, Ersetzung, Term-Updates und Term-Löschung sind über taxonomies:write nicht verfügbar.
Medien-Metadaten und Bytes lesen
media:read gibt bereite Medieneinträge mit Dimensionen, Alt-Text, Caption, Fokuspunkt, Blurhash, Dominantfarbe, Ordner-ID und einer authentifizierten ID-basierten Asset-URL zurück. Authentifizierte Aufrufer mit der Berechtigung media:read können der URL folgen; abgemeldete Anfragen werden abgelehnt, bevor die Route den Medieneintrag liest. Die Metadaten geben weder den Storage-Schlüssel noch Autorenidentität, Content-Hash oder Dateibytes zurück. Der Content-Hash ist nur von readBytes() verfügbar, weil er verraten kann, ob die Site eine bekannte Datei speichert.
Innerhalb eines Hooks oder Route-Handlers liest der folgende Aufruf höchstens 2 MiB von einem bereiten Medienelement:
const file = await ctx.media!.readBytes!(mediaId, {
maxBytes: 2 * 1024 * 1024,
});
const digest = file.contentHash;
const bytes = file.bytes;
readBytes() puffert das Ergebnis. Es defaultet auf 10 MiB, wenn maxBytes weggelassen wird, und lehnt Werte über dem Host-Maximum von 16 MiB ab. EmDash zählt Bytes beim Konsumieren des Storage-Streams, sodass eine falsche gespeicherte Größe das angeforderte Limit nicht umgehen kann. Fehlende, ausstehende und fehlgeschlagene Medien werden abgelehnt, ohne ihren Storage-Standort preiszugeben.
Das folgende Update ändert den Barrierefreiheitstext und den Fokuspunkt, ohne Upload-, Ersetzungs- oder Löschberechtigung zu gewähren:
const updated = await ctx.media!.updateMetadata!(mediaId, {
alt: "Two people reviewing a printed proof",
focalX: 0.42,
focalY: 0.36,
});
Geben Sie beide Fokuskoordinaten als Zahlen von 0 bis 1 an, oder setzen Sie beide auf null. Parallele Patches an unterschiedliche Metadatenfelder ersetzen einander nicht.
Medien hochladen
ctx.media.upload() akzeptiert Bild-, Video-, Audio- und PDF-Inhalt und wirft bei jedem anderen Content-Typ. In einem Trusted Plugin setzen upload() und getUploadUrl() die Standard-Media-Upload-Allowlist durch: PNG-, JPEG-, GIF-, WebP- und AVIF-Bilder, jeder video/*- oder audio/*-Typ und application/pdf. Andere Typen werfen einen PluginRouteError mit Status 415, und ein fehlerhafter Content-Typ wirft einen mit Status 400; ein Route-Handler kann beide als Antwort propagieren lassen. In einem Trusted Plugin nimmt eine von upload() gespeicherte oder von getUploadUrl() reservierte Datei auch eine zum Content-Typ passende Erweiterung an, unabhängig von der Erweiterung des Dateinamens; wenn der Content-Typ keine bekannte Erweiterung hat, wird die Erweiterung des Dateinamens nur behalten, wenn sie zu einem erlaubten Medientyp gehört. Ein Sandboxed Plugin behält die Erweiterung des Dateinamens, wenn sie 1 bis 10 Buchstaben oder Ziffern hat.
Redirects sicher verwalten
redirects:read bietet cursor-paginierte Regelauflistung und versionierte Einzelregel-Reads. Fügen Sie redirects:write hinzu, wenn das Plugin Regeln erstellt, aktualisiert oder löscht. Schreibzugriff kann ändern, wohin Besucher gesendet werden.
Übergeben Sie das von get(), create() oder update() zurückgegebene _rev unverändert zurück, wenn Sie eine Regel aktualisieren oder löschen. EmDash lehnt eine veraltete Revision ab, damit das Plugin die Regel neu lesen und seine Änderung neu berechnen kann, statt parallele Arbeit zu überschreiben.
Die Revision verfolgt die Redirect-Konfiguration. Besuchertrefferzählung macht eine Revision nicht veraltet.
Das folgende Beispiel aktualisiert einen Redirect nur, wenn er sich seit dem Lesen nicht geändert hat:
const current = await ctx.redirects!.get(redirectId);
if (current) {
await ctx.redirects!.update!(redirectId, {
destination: "/guides/current",
_rev: current._rev,
});
}
Create-Operationen validieren Pfadmuster, terminale 410- und 451-Regeln, doppelte Quellen, Self-Loops und Multi-Hop-Loops mit denselben Regeln wie die EmDash-Redirect-API. Updates wenden Loop-Validierung an, wenn sich Quelle oder Ziel ändert. Ein nur-Enabled-Update kann einen bereits bestehenden Loop reaktivieren, den die Redirects-Seite meldet. Der Marker auto gehört zu Redirects, die aus Host-Inhaltsänderungen erstellt wurden; Plugin-Eingaben können ihn nicht setzen.
Kommentare lesen und moderieren
comments:read gewährt Zugriff auf nicht gelöschte Kommentare. Ergebnisse enthalten Autorname und E-Mail-Adresse, Kommentarkörper, pseudonymen IP-Hash, User-Agent, Moderationsmetadaten, Status, Ziel-Content-IDs und Zeitstempel. Sie schließen die verknüpfte EmDash-Benutzerkonten-ID aus. Deklarieren Sie users:read separat, wenn ein Plugin auch Benutzerkonten nachschlagen muss.
list() gibt die neuesten Kommentare zuerst zurück. Es akzeptiert Filter für status, collection und contentId, einen Cursor und ein Limit von 1 bis 100. Das Standardlimit ist 50. count() akzeptiert dieselben Filter ohne Paginierung.
Die folgende Route genehmigt einen Kommentar nur, wenn er noch aussteht:
const comment = await ctx.comments!.setStatus!(commentId, "approved", {
expectedStatus: "pending",
});
Wenn ein anderer Moderator den Status geändert hat, nachdem das Plugin ihn gelesen hat, lehnt setStatus() mit COMMENT_STATUS_CONFLICT ab. Lesen Sie den Kommentar erneut und berechnen Sie die Entscheidung neu, bevor Sie es erneut versuchen. Eine Anfrage, die mit einem früheren Übergang überlappt, bevor dessen Status sichtbar ist, lehnt mit COMMENT_MODERATION_IN_PROGRESS ab; warten Sie, bis dieser Übergang abgeschlossen ist, und lesen Sie dann den aktuellen Kommentar, bevor Sie es erneut versuchen. Ein erfolgreicher Übergang führt comment:afterModerate einmal mit origin: { source: "plugin", pluginId } aus. Die Genehmigung sendet dieselbe Core-Autor-Benachrichtigung wie eine Administratorgenehmigung. Einen Kommentar auf seinen aktuellen Status zu setzen ist ein No-Op und führt den Hook nicht aus und sendet keine weitere Benachrichtigung.
Network-Host-Allowlists
Plugins mit network:request können nur Hosts abrufen, die in allowedHosts gelistet sind. Ein führendes *. matcht sowohl die genannte Domain als auch ihre Subdomains:
"capabilities": ["network:request"],
"allowedHosts": [
"api.example.com", // exact host
"*.cdn.example.com" // cdn.example.com and any subdomain
]
Die Bridge prüft den Host der Request-URL gegen die Allowlist, bevor sie die Anfrage weiterleitet. Eine Anfrage an einen nicht deklarierten Host wirft im Plugin, ohne jemals die Sandbox zu verlassen.
network:request:unrestricted überspringt die Manifest-Host-Allowlist. Die Sandbox-Bridge akzeptiert weiterhin nur HTTP und HTTPS, blockiert bekannte interne Hosts und private Literaladressen, prüft jede Redirect erneut und entfernt Credential-Header, wenn ein Redirect Origins überschreitet. Nutzen Sie uneingeschränkten Zugriff nur, wenn ein Operator das Ziel zur Laufzeit liefert. Für feste Ziele deklarieren Sie network:request mit expliziten Hosts, damit der Consent-Dialog sie nennt.
ctx.http.fetch() puffert Request- und Response-Bodies und begrenzt jeden dekodierten Body auf 8 MiB. Die zurückgegebene WHATWG-Response bewahrt Binärbytes, Statustext, Header, finale URL, Redirect-Zustand und clone()-Verhalten in beiden Sandbox-Runnern. Lesen Sie Binärdaten mit arrayBuffer() oder blob().
Was die Sandbox durchsetzt
Wenn ein Sandbox-Runner aktiv ist, setzt die Runtime Folgendes durch:
-
Capability-Gating. Die PluginContext-Fabrik befüllt
ctx.content,ctx.comments,ctx.schema,ctx.taxonomies,ctx.redirects,ctx.media,ctx.http,ctx.users,ctx.emailnur, wenn die entsprechende Capability deklariert ist. Eine Methode auf einer nicht deklarierten Capability aufzurufen ist nicht möglich — dort gibt es kein Objekt. -
Storage- und KV-Scoping. Jede Storage- und KV-Operation ist auf die Runtime-Plugin-ID scoped. Ein Plugin kann weder KV noch Storage-Collections eines anderen Plugins lesen und kann nur Collections zugreifen, die in seinem Manifest deklariert sind.
-
Netzwerkisolation. Direktes
fetch()und andere Netzwerkprimitive werden vom Runner blockiert. Der einzige Weg zum Netzwerk istctx.http.fetch(), das durch die Host-Validierung der Bridge geht. -
Keine Host-Bindings. Sandboxed Plugins sehen weder Umgebungsvariablen noch das Dateisystem noch Platform Bindings — auch wenn Ihr Host-Worker sie hat. Die Plugin-Runtime ist ein sauberes Isolate nur mit der Bridge und den deklarierten Capabilities.
-
Ressourcenlimits. Der Cloudflare-Runner defaultet auf 50 ms CPU, 10 Subrequests und 30 Sekunden Wall-Time pro Aufruf. Worker Loader setzt CPU und Subrequests durch; der Runner setzt Wall-Time durch. Worker Loader hat eine Platform-Memory-Obergrenze, aber seine per-Plugin-Option
memoryMbist derzeit nicht durchsetzbar. Der Node.js-workerd-Runner setzt nur den 30-Sekunden-Wall-Time-Default durch; er warnt, wenn eine Site CPU-, Memory- oder Subrequest-Limits konfiguriert, die standalone workerd nicht durchsetzen kann. Ein per-Hook-timeoutgilt nur, wenn das Sandboxed-Format-Plugin in-process läuft.
Was die Sandbox nicht durchsetzt
Einige Dinge, die das Capability-System nicht abdeckt und nicht abdecken kann:
- Verhalten innerhalb einer gewährten Capability. Ein Plugin mit
content:writekann jeden Inhalt bearbeiten, nicht nur den eigenen. Capabilities sind grob — sie sagen „dieses Plugin kann Inhalt schreiben“, nicht „dieses Plugin kann nur den Inhalt schreiben, den es erstellt hat.“ Ein Operator muss Code und Publisher des Plugins bewerten, bevor er diesen Zugriff gewährt. - Eintrags-Edit-Locks.
ctx.content.update()undctx.content.delete()sind programmatische Schreibvorgänge. Ein Editor, der den beratenden Edit-Lock des Eintrags hält, blockiert sie nicht. Koordinieren Sie Plugin-Schreibvorgänge mit Editoren, wenn beide denselben Eintrag aktualisieren können. - Operator-Trust unter Node.js. Wenn der konfigurierte Sandbox-Runner als unavailable meldet (kein Cloudflare Worker Loader, kein Node-seitiger Runner installiert usw.), werden
sandboxed: []-Plugins beim Start übersprungen. Sie können sie inplugins: []verschieben, um sie in-process auszuführen — aber dann gibt es kein V8-Isolate, keine Ressourcenlimits, und das Plugin kannfetch()direkt aufrufen oder Umgebungsvariablen lesen. Behandeln Sie das als Native-Level-Trust. - Seitenkanäle. Timing, Log-Ausgabe und gespeicherte Daten sind für jeden mit entsprechendem Zugriff auf die Host-Umgebung sichtbar. Nutzen Sie die Sandbox nicht als Vertraulichkeitsgrenze gegen den Operator, der sie betreibt.
Capability-Consent
Wenn ein Operator ein Sandboxed Plugin aus der Registry installiert, zeigt EmDash einen Consent-Dialog mit den deklarierten Capabilities. Updates, die Capabilities hinzufügen — zum Beispiel ein Plugin, das zuvor nur Inhalt gelesen hat und jetzt Netzwerk-Anfragen stellen will — erscheinen als Capability-Diff und erfordern neue Zustimmung, bevor die neue Version wirksam wird.
Capabilities für mögliche zukünftige Nutzung zu deklarieren lässt jede Installation oder jedes Update unnötigen Zugriff verlangen. Listen Sie auf, was die aktuelle Version nutzt, und fügen Sie dann eine Capability in der Version hinzu, die sie zu nutzen beginnt.
Bundle-Zeit-Validierung
emdash-plugin bundle und emdash-plugin publish führen zusätzliche Prüfungen durch:
- Jede deklarierte Capability muss im erkannten Satz sein (Tippfehler lassen den Build scheitern).
network:requesterfordert eine nicht leereallowedHosts;network:request:unrestrictederfordert, dass sie leer ist. Siehe Capabilities and hosts.- Die gebündelte
backend.jskann keine Node.js-Built-ins importieren (fs,path,child_processusw.) — Sandbox-Runtimes stellen sie nicht bereit.
Siehe the manifest reference für die Authoring-Felder und Bundling and publishing für Bundle-Checks.