Plugin marketplace

kanros ships a small, opinionated plugin host (kanros-plugin) that runs sandboxed WASM components and JavaScript bundles to extend the built-in assertions, providers, and report formats. The plugin marketplace is the discovery + install surface on top of that host: a published index of community plugins that users can search, inspect, and install from the kanros plugin CLI subcommand.

This page documents:

  • The published manifest JSON schema.
  • The registry HTTP API.
  • How to author and submit a plugin.
  • The security model — what we verify today, what is deferred to v0.2.

Status. v0.1.0 ships only the client (kanros-plugin-registry) and the kanros plugin CLI subcommand. The canonical hosting service at https://registry.kanros.dev is a follow-up workstream. The client is designed so users can point it at any registry that implements the API documented below — for example a local mock, a corporate mirror, or a GitHub Pages-hosted static index.

Manifest JSON schema

Every published plugin is described by a PluginManifest document. This is the single source of truth: the registry's index endpoints return arrays of manifests, the per-plugin endpoint returns one manifest, and the same shape is stashed in the local cache directory next to every installed artefact so kanros plugin list can recover metadata without re-contacting the network.

{
  "name": "regex-plus",
  "version": "0.1.0",
  "description": "Extra regex assertions with named-capture support.",
  "authors": ["Alice Example <alice@example.com>"],
  "license": "Apache-2.0",
  "kanros_min_version": "0.1.0",
  "kind": "wasm",
  "source_url": "https://registry.kanros.dev/api/v1/plugins/regex-plus/versions/0.1.0/download",
  "sha256": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
  "tags": ["assertion", "regex"]
}
FieldTypeRequiredNotes
namestring (kebab-case)yesGlobally unique.
versionstring (semver)yesMust match ^v?\d+\.\d+\.\d+(-[\w.+-]+)?$.
descriptionstringyesOne sentence, < 200 chars.
authorsstring[]yesMay be empty.
licensestring (SPDX)yesValidated against the SPDX list at submission time.
kanros_min_versionstring (semver)yesMinimum runtime version that can load this plugin.
kindenum: wasm | javascriptyesDetermines on-disk filename (plugin.wasm vs plugin.js).
source_urlURLyesDirect download URL — may point at the registry or any HTTPS host.
sha256hex string (64 chars)yesSHA-256 of the payload — verified by the client.
tagsstring[]noFree-form, used by the search endpoint. Default [].

The schema lives in kanros-plugin-registry::PluginManifest and round-trips through serde JSON. A sample manifest is checked into the repo at examples/plugin-registry/sample-manifest.json and is used as a fixture when running the crate's test suite manually.

Registry HTTP API

The registry is read-only over HTTP. All endpoints return JSON encoded as UTF-8.

GET /api/v1/plugins?q={query}

Full-text search. query may be empty — the registry returns its curated "all plugins" list in that case.

Response (200 OK) — JSON array of PluginManifest documents (newest version per plugin):

[
  { "name": "regex-plus", "version": "0.1.0", "kind": "wasm", ... },
  { "name": "boa-jsonpath", "version": "0.2.1", "kind": "javascript", ... }
]

Errors. A 404 is treated by the client as "no results". Any 5xx response surfaces as RegistryError::Server { status }.

GET /api/v1/plugins/{name}

Single-manifest lookup. Returns the manifest of the canonical version (typically the latest stable release). Older versions are accessible via the per-version download endpoint below.

Response (200 OK) — a single PluginManifest:

{
  "name": "regex-plus",
  "version": "0.1.0",
  ...
}

Errors. 404 → RegistryError::NotFound { name }. 5xx → Server.

GET /api/v1/plugins/{name}/versions/{version}/download

Binary download endpoint. The body is the raw .wasm or .js payload — no JSON wrapping, no multipart, no encoding.

Response (200 OK)application/octet-stream. Content-Length is required and must match the payload exactly.

Errors. 404 → RegistryError::NotFound { name: "<name>@<version>" }.

Verification. The client downloads the payload into a .partial sibling of the final destination, computes SHA-256, and only renames into the final location if the digest matches manifest.sha256. On mismatch, the partial file is removed and RegistryError::ChecksumMismatch is returned with both the expected and actual digests.

Optional headers

  • User-Agent: the client always sends kanros-plugin-registry/<crate-version>.
  • Accept: not required — every endpoint always emits JSON or octet-stream as appropriate.
  • Bearer auth is not part of v0.1. A private/corporate mirror that wants gating should sit behind an auth proxy.

Authoring and submitting a plugin

Authoring a plugin is unchanged from the WASM plugins and JavaScript plugins guides — the marketplace only adds a distribution layer on top. The workflow is:

  1. Build the plugin artefact (.wasm or .js).
  2. Compute its SHA-256 (kanros-plugin-registry::sha256_hex(&bytes) or sha256sum).
  3. Author a PluginManifest JSON file with the schema above. Lint it by loading it through serde_json::from_str::<PluginManifest> — kanros's own test suite uses the same code path.
  4. Publish the artefact somewhere addressable by an HTTPS URL.
  5. Open a pull request against the registry index repository (link TBD when the hosting service ships) with the manifest added under plugins/<name>/<version>.json.

The registry CI will, at submission time:

  • Validate the manifest against the schema.
  • Re-download the artefact from source_url and verify sha256.
  • Check that kanros_min_version is <= the current released kanros version.
  • Reject names that collide with existing first-party crates.

Local install layout

kanros plugin install <name> (with optional --version) writes the payload under:

~/.local/share/kanros/plugins/<name>/<version>/{plugin.wasm | plugin.js}
~/.local/share/kanros/plugins/<name>/<version>/manifest.json

The path is platform-aware — on macOS it lives under ~/Library/Application Support/kanros/plugins/, on Windows under %LOCALAPPDATA%\kanros\plugins\. Override with kanros plugin install --dir <PATH> if you want a project-local cache instead.

kanros plugin list walks the cache and prints every manifest.json it finds — there is no separate index file, so removing a plugin is as simple as rm -rf of its directory (or kanros plugin remove <name>).

Security model

v0.1.0:

  • SHA-256 integrity on every download. A mismatched payload never reaches the final install path.
  • HTTPS-only by convention — the client does not refuse http:// URLs (useful for local mocks) but the canonical registry only serves HTTPS.
  • No code execution at install time. kanros plugin install writes the artefact to disk and stops. The plugin only runs when the user explicitly opts in with kanros run --plugins.

v0.2 (deferred):

  • Signed manifests via Sigstore. The plan is to publish a Rekor entry for every manifest signed by the author's GitHub identity, and to require kanros plugin install to verify the signature against the certificate transparency log before writing to disk. The PluginManifest schema will gain optional signature_url and rekor_log_index fields; the client will pull a Sigstore bundle and call sigstore-rs to verify.
  • Provenance. SLSA build-provenance attestations attached to each release, also verified through Sigstore.
  • Per-plugin permission manifests. Today, a WASM plugin runs with the capability set declared in the user's kanros config; the marketplace manifest will gain a requested_permissions array so users can be warned before granting filesystem or network access to a third-party plugin.

Users who want stronger guarantees today should:

  1. Run a corporate mirror that re-hosts only audited plugins.
  2. Override --registry to point at that mirror.
  3. Keep the install dir under version control so unexpected changes surface as a diff.

Related reading

  • WASM plugins — how the host loads .wasm components.
  • JavaScript plugins — the Boa-backed runtime.
  • The kanros-plugin-registry crate docs — cargo doc -p kanros-plugin-registry --open.