Distributing Plugins Through a Registry
This page answers one task: your product supports WebAssembly plugins, and they are currently passed around as files — attached to tickets, copied into folders, downloaded from release pages. You want a proper distribution channel: authors publish versions, hosts install and update them by name, and every plugin is verified before it runs.
Prerequisites
- [ ] A plugin format:
.wasmmodules or components plus a manifest describing them. - [ ] Access to an OCI registry (GitHub Container Registry, a cloud registry, or a self-hosted one) or another artifact store.
- [ ] Tools:
orasorwkgfor pushing and pulling artifacts, andcosignfor signatures.
Why OCI registries work for Wasm plugins
Container registries are not only for container images. The OCI distribution specification stores arbitrary artifacts — blobs with a media type, grouped by
a manifest, addressed by repository, tag and content digest. Registries are already deployed everywhere, support authentication, replication and access control,
and integrate with signing tools. The WebAssembly ecosystem has converged on them: tools such as wkg publish WIT packages and components to OCI registries,
and runtimes and plugin hosts pull modules from them.
A plugin published this way has a stable name (ghcr.io/acme-plugins/csv-export), human-friendly tags (1.4.2), and an immutable digest
(sha256:…) that identifies exact bytes. Hosts resolve a version, pin the digest, verify a signature, and cache the blob.
Step 1 — package the plugin with a manifest
A plugin artifact needs the module and metadata the host uses to decide whether to install it:
{
"name": "csv-export",
"version": "1.4.2",
"host_api": ">=2.1 <3",
"kind": "component",
"world": "acme:plugins/exporter@2.1.0",
"permissions": { "allowed_hosts": [], "max_memory_mb": 64 },
"description": "Exports records as CSV",
"license": "MIT"
}
host_api (or, for components, the WIT world and version) lets hosts reject plugins built for an incompatible API before downloading the module. Permissions
declare what the plugin will ask for, so administrators can review them at install time.
Step 2 — push as an OCI artifact
oras push ghcr.io/acme-plugins/csv-export:1.4.2 \
--artifact-type application/vnd.acme.plugin.v1 \
plugin.json:application/vnd.acme.plugin.manifest.v1+json \
csv_export.wasm:application/wasm
oras manifest fetch ghcr.io/acme-plugins/csv-export:1.4.2 --descriptor # shows the digest
For components following WebAssembly packaging conventions, wkg publishes with the standard media types used across the ecosystem. Choose one convention and
use it consistently, so host tooling can find the module and manifest layers.
Step 3 — sign what you publish
Sign the artifact’s digest so hosts can verify who published it. With Sigstore’s keyless flow in CI, the signature is tied to the CI workflow’s identity:
cosign sign --yes ghcr.io/acme-plugins/csv-export@sha256:3f9c… # signs by digest, records in the transparency log
Tags are mutable — someone with push access can move 1.4.2 to different bytes — so always sign and verify digests, never tags. Keep a list of trusted
signing identities per plugin publisher (for example, a specific repository’s release workflow).
Step 4 — resolve versions on the host
When an administrator installs csv-export@^1.4, the host lists tags, filters those satisfying the range and whose manifest declares a compatible host API,
picks the highest, and records the resolved digest. Updates repeat the resolution and show the administrator what changes — version, permissions, publisher —
before switching. Pinning digests in the host’s configuration makes installations reproducible: the same configuration installs the same bytes everywhere.
Step 5 — verify before every load
Verification is not a one-time install step. Before loading a plugin from cache, recompute its digest and compare with the recorded one; before first use, verify the signature against trusted identities:
cosign verify ghcr.io/acme-plugins/csv-export@sha256:3f9c… \
--certificate-identity-regexp 'https://github.com/acme-plugins/csv-export/.github/workflows/release.yml@.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Hosts written in Rust or Go can verify signatures with Sigstore client libraries instead of calling the CLI. Refuse to load anything that fails, and log why.
Caching, mirrors and offline installs
Hosts should cache pulled plugins by digest, so restarts do not depend on the registry being reachable. For air-gapped or regulated environments, mirror the
plugin repositories (oras copy) into an internal registry, including signatures, and point hosts at the mirror; verification still works because signatures
travel with the artifacts.
Reviewing plugins before they reach users
A registry distributes whatever is pushed to it; deciding what is trustworthy is a separate process. For a curated ecosystem, add a review stage between
“published by the author” and “installable by customers”: automated checks run on every new version — validate the module, print its imports and exports
with wasm-tools, compare the requested permissions with the previous version, scan dependencies listed in the build provenance — and a reviewer approves
versions whose permissions or imports changed. Approved versions get a second signature from your organisation’s review identity, and hosts in production
trust only artifacts carrying that signature. Authors can still publish freely to a staging repository; customers see only reviewed versions. This two-key
model keeps the convenience of self-service publishing while giving administrators a clear guarantee about what they install.
Provenance and reproducible builds
Signatures say who published an artifact, not how it was built. Build provenance attestations (in-toto/SLSA style), generated by CI and attached to the artifact, record the source repository, commit and build workflow. Hosts or reviewers can check that a plugin was built from public source by a known workflow, and reproducible builds make it possible to rebuild and compare digests independently. For high-trust environments, require provenance alongside signatures and reject plugins built outside approved pipelines.
Revocation
Sometimes a published version turns out to be malicious or badly broken. Registries let you delete tags, but hosts that already pinned the digest keep running it. Publish a revocation list — digests that must not run — that hosts fetch periodically and check before loading, and alert administrators whose installations are affected. Pair it with a fast path for pushing a fixed version, so revocation does not leave users without the plugin for long.
Naming and ownership
Registries organise artifacts by repository paths, and those names become part of how administrators recognise plugins. Reserve a namespace for your
organisation’s official plugins, give each third-party publisher their own namespace with their own signing identity, and never let two publishers share one.
That prevents a confusing situation where a plugin named like an official one is published by someone else, and it keeps trust policies simple: “anything under
acme-plugins/ must be signed by Acme’s release workflow”.
Expected output
Authors publish plugins from CI with a manifest, a .wasm layer and a keyless signature; administrators install csv-export@^1.4, which resolves to 1.4.2 at a
pinned digest after the host checks API compatibility and the signer’s identity; cached plugins are re-verified by digest on load; an internal mirror serves
air-gapped deployments; and a tag moved to different bytes is detected and rejected.
Gotchas
- Trusting tags. They are mutable. Pin and verify digests.
- Signatures without identity policies. Any valid signature is not enough. Restrict trusted signers.
- No API compatibility metadata. Incompatible plugins fail at load. Declare the host API or WIT world.
- Skipping verification for cached plugins. Local tampering goes unnoticed. Re-check digests.
- Hidden permission changes in updates. Show permission diffs before upgrading.
- No revocation path. Pinned digests keep running after a bad release. Publish and check a revocation list.
Performance note
Pulling a 2 MB plugin by digest from a nearby registry took about 150 ms; signature verification with a cached trust root about 40 ms; loading from the local cache with a digest check about 5 ms.
Frequently Asked Questions
Do I need a container registry? No — any artifact store works if it gives immutable addressing and you add signatures; OCI registries simply provide all of it.
Can browsers pull from OCI registries? Registries’ APIs work over HTTP but often lack CORS; serve plugins through your own endpoint that pulls and verifies.
What about a plugin marketplace? A marketplace is a catalogue and review process on top of the registry; keep distribution and verification in the registry layer.
How are plugin dependencies handled? Prefer self-contained modules or composed components; avoid runtime dependency resolution between plugins.
How do I stop a published version that turned out to be malicious? Publish a revocation list of digests that hosts check before loading, and push a fixed version quickly.
Should hosts trust the author’s signature alone? For curated ecosystems, require a second signature from your review identity after automated checks and review.
What does build provenance add to a signature? It records which source commit and CI workflow produced the artifact, so reviewers can check it was built from public source by an approved pipeline.
How should third-party publishers be organised? Give each publisher its own namespace and signing identity, so trust policies can be written per namespace.
Related
- Versioning a Wasm plugin API — compatibility rules.
- Loading untrusted plugins safely — after verification.
- Versioning WIT packages — component interfaces.
- Publishing Wasm release artifacts from CI — CI publishing.
← Back to Plugin Systems & Extensibility