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: .wasm modules 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: oras or wkg for pushing and pulling artifacts, and cosign for 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.

Publishing and installing a plugin through a registry The author builds the plugin, pushes the .wasm and manifest to an OCI registry as an artifact with a version tag, and signs the digest with cosign. The host resolves a compatible version, pulls by digest, verifies the signature and digest against trusted identities, caches the blob, and only then loads the plugin. build plugin .wasm + manifest push OCI artifact tag 1.4.2 sign digest cosign host resolves + pulls by digest verify, cache, load trusted signer only

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

Installing by tag versus by verified digest Installing by tag fetches whatever the tag currently points to, which can change, with no proof of origin. Installing by verified digest resolves the tag once, pins the immutable digest, checks a signature from a trusted identity, and loads exactly the bytes that were signed. pull by tag tag may move no proof of publisher silent changes possible unsafe for plugins pin digest + verify signature immutable bytes trusted publisher identity auditable install record required

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.

Time to make a plugin available to the host Milliseconds to pull a 2 MB plugin from a registry, to verify its signature, and to load it from the local cache with a digest check. ms pull from registry 150 ms verify signature 40 ms load from cache + digest check 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.

← Back to Plugin Systems & Extensibility