Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Maskura plugins

Plugins are WebAssembly components that transform object data in an ordered pipeline. A plugin can filter, redact, encrypt, validate, normalize, convert, or perform another deterministic byte transformation. The output of one plugin is the input of the next.

Contract ownership

The public maskura-plugin-sdk crate owns the canonical WIT contract and thin Rust bindings. Both the gateway host and every in-tree transformer consume that same source:

package maskura:plugin@0.1.0;
world transformer { /* begin, transform, finish */ }

Purpose is metadata, not ABI. A plugin under plugins/crypto/ and one under plugins/filters/ implement the same transformer world, so new capability families do not require new protocols.

FunctionCalledPurpose
begin(context)once per objectReceive format, operation, policy, bounded config, and explicitly granted sensitive inputs
transform(payload)once per recordReturn emit(bytes), drop, or reject(reason)
finish()once at the endFlush bounded trailing output

The sandbox provides bounded memory, tables, stack, time, and fuel. It does not inherit the host filesystem, environment, network, stdout, or stderr.

Write a Rust plugin

[lib]
crate-type = ["cdylib"]

[dependencies]
maskura-plugin-sdk = { git = "https://github.com/231self/maskura", tag = "v0.7.13" }
#![allow(unused)]
fn main() {
use maskura_plugin_sdk::{Context, Decision, Guest, export_plugin};

struct Normalize;

impl Guest for Normalize {
    fn begin(_context: Context) -> Result<(), String> {
        Ok(())
    }

    fn transform(payload: Vec<u8>) -> Result<Decision, String> {
        Ok(Decision::Emit(payload))
    }

    fn finish() -> Result<Vec<u8>, String> {
        Ok(Vec::new())
    }
}

export_plugin!(Normalize);
}

Build it as a WASI reactor and lift it to a component with the pinned adapter, as demonstrated by scripts/build-plugins.sh. The minimal in-tree example is plugins/transforms/noop.

Official plugin layout

Maskura keeps one contribution surface until external contribution volume justifies another repository:

plugins/
  filters/       content selection and redaction
  crypto/        encryption and deterministic protection
  transforms/    general-purpose byte transformations
  shared/        internal libraries, not loadable components

Each loadable plugin has a plugin.toml with its stable ID, category, status, world, version, and requested capabilities. official means maintained and released by Maskura; experimental means the API or behavior may still change. The label does not weaken sandboxing or validation.

Do not add an empty category to advertise a roadmap. Add a category when its first useful plugin lands. If outside contributions eventually need independent ownership or release cadence, the SDK keeps a future community repository from forking the ABI.

Load a component

Self-hosted gateways support runtime import without rebuilding the server:

maskura plugin upload my-plugin.component.wasm
maskura plugin list
maskura plugin reorder my-plugin pii-default
maskura plugin disable <id>
maskura plugin delete <id>

Or auto-load every .wasm component in a directory at startup:

MASKURA_PLUGINS_DIR=./components ./target/debug/maskura-gateway

A component may have an adjacent metadata sidecar, for example email-detect.component.wasm and email-detect.plugin.toml. The sidecar’s version and world fields become its signed-pipeline catalog identity. A legacy component without a sidecar defaults to version 0.1.0 and maskura:plugin/transformer@0.1.0. Name/version references rely on the component source not reusing a version for different bytes. Use the component’s 64-hex SHA-256 digest as the pipeline plugin name when the policy must remain byte-for-byte pinned across restarts.

MASKURA_DEFAULT_PLUGIN selects the initial component; local images use the official pii-default artifact. just proof plugin verifies runtime import against the published container and observes a transformation produced only by the imported component.

Hosted workspaces persist immutable plugin versions, installations, ordered pipeline revisions, assignments, grants, and validation results. Uploads use the same world identifier:

maskura hosted upload ./my-plugin.component.wasm \
  --slug my-plugin --display-name "My Plugin" --version 1.0.0 \
  --world maskura:plugin/transformer@0.1.0 --wit-version 0.1.0

Only workspace owners mutate hosted plugin state. Sensitive context is passed only after an explicit capability grant. Empty pipelines require explicit pass-through, and failed read transforms never disclose unprocessed fallback data.

Declarative pipelines (self-hosted)

Self-hosted gateways can define their processing chains in a signed TOML file instead of relying on catalog order. Configure [wasm] pipelines_file and pipeline_trust_roots (or their environment overrides); author and sign with:

maskura pipelines init-key --out maskura-pipeline.key
maskura pipelines sign    --key maskura-pipeline.key pipelines.toml
maskura pipelines verify  --trust-roots "acme=<hex-public-key>" pipelines.toml
maskura pipelines check   pipelines.toml

A file carries dedicated, ordered write and read chains per scope — default, buckets.<bucket>, workspaces.<id>, and workspaces.<id>.buckets.<bucket> — and the most specific assignment wins. See ADR 0021 and the configuration reference. All references are validated before startup. Components uploaded after startup require a restart before a signed file can reference them; hot reload is intentionally deferred.

Typed binary formats can additionally use the binary-reductor world from the same WIT package. See Binary adapters.