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: create and consume your own

Plugins are how Maskura transforms text data. A plugin is a WebAssembly component that receives each object’s payload, optionally transforms it, and returns a decision. Plugins run in a pipeline — the output of one is the input of the next — so you compose transforms: filter, then encrypt, then convert.

The interface

Plugins implement one world, s4:filter (wit/s4-filter/world.wit):

FunctionCalledPurpose
begin(context)once per objectPer-object setup; context carries format, content-type, policy-version, and optional public-key-pem, stable-key, stable-fields
transform(payload)once per recordTransform the bytes; return emit(bytes), drop, or reject(reason)
finish()once at the endFlush buffered output; return trailing bytes

Sandbox limits: wasmtime, 64 MiB memory, 10K table entries, 512 KiB stack, no host imports, and a fuel budget (MASKURA_WASM_FUEL, default 1B; enough for crypto filters).

Write one (Rust)

Cargo.toml:

[package]
name = "my-filter"
edition = "2021"

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

[dependencies]
wit-bindgen = "0.60"

src/lib.rs:

#![allow(unused)]
fn main() {
wit_bindgen::generate!({
    world: "filter",
    path: "path/to/wit/s4-filter/world.wit",
});

struct MyFilter;

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

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

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

export!(MyFilter);
}

Build and wrap as a component:

cargo build --release --target wasm32-unknown-unknown
wasm-tools component new target/wasm32-unknown-unknown/release/my_filter.wasm \
  -o my-filter.component.wasm

filters/noop/ is a minimal example; filters/pii-default/ shows detection + redaction with addr-spec / card-validate style libraries and a pure-Wasm crypto fallback.

Load it

At runtime — no gateway rebuild, no restart:

maskura plugin upload my-filter.component.wasm     # prints the plugin id
maskura plugin list                                # shows pipeline order
maskura plugin reorder my-filter pii-default       # output of one feeds the next
maskura plugin disable <id>                        # remove from the pipeline
maskura plugin delete <id>                         # drop the plugin

Or auto-load a directory of plugins at gateway startup:

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

The default local setup preloads pii-default via MASKURA_FILTER_COMPONENT.

Decision semantics

  • emit(bytes) — pass the transformed bytes to the next plugin.
  • drop — discard this record entirely.
  • reject(reason) — fail the request with the reason.

Notes

  • Plugins are pure byte-in/byte-out. The gateway handles transport (S3 API), auth, and storage.
  • Plugins do not declare an output schema, so they cannot be used directly for Avro, Parquet, or another typed binary format. Binary codecs use schema-aware transforms and optional s4:binary-reductor components instead; see Binary adapters.
  • Filters shipped in-tree: noop (pass-through baseline), pii-default, email-detect, ssn-detect, card-detect, envelope-encrypt, stable-encrypt.
  • The original WIT design is recorded in ADR-0001: component model and WIT.

Hosted workspaces (maskura hosted)

Self-hosted/local gateways load plugins with the maskura plugin commands above and a directory (MASKURA_PLUGINS_DIR) at startup. Hosted Maskura workspaces instead manage plugins as first-class relational configuration owned by the workspace owner. The maskura plugin commands and directory auto-enable do not apply there and are not available on hosted Maskura. Hosted management is available only when the hosted deployment enables filter pipelines (and separately enables custom uploads). It authenticates with a Supabase access token (MASKURA_ACCESS_TOKEN or --token) and a workspace ID (MASKURA_WORKSPACE_ID or --workspace); a Maskura data-plane API key is never accepted for hosted mutations. The s4ctl binary name remains available as an alias for maskura.

export MASKURA_ACCESS_TOKEN=<supabase-jwt>
export MASKURA_WORKSPACE_ID=<workspace-uuid>
maskura hosted catalog                  # catalog + versions + capability grants
maskura hosted upload ./my-filter.component.wasm \
  --slug my-filter --display-name "My Filter" --version 1.0.0 \
  --world s4-filter@0.2.0 --wit-version 0.2.0 --capability stable_fields
maskura hosted validation <version-id>  # poll the secret-free validation run
maskura hosted grant --installation-id <id> --capability stable_fields --version-id <version-id>
maskura hosted pipelines create write "redact"
maskura hosted pipelines draft <pipeline-id> --step <install-id>:<version-id>:config.json
maskura hosted pipelines publish <pipeline-id>
maskura hosted assign-default write --pipeline-id <id>
maskura hosted assign-bucket write ingest --pipeline-id <id>
maskura hosted audit
  • Worlds. Components implement s4-filter@0.1.0 (no config) or s4-filter@0.2.0 (operation + optional config-json). Config is only valid for v0.2 components.
  • Ordering. Draft steps run in the order given (installation_id:version_id[:config]); the fingerprint covers ordered versions, enabled flags, configs, and grants.
  • Capability grants. A component only receives sensitive context (for example stable_fields) after the owner explicitly grants it per installation/version.
  • Pass-through. An empty chain is only publishable when --passthrough is set; a missing bucket assignment inherits the workspace default, and an exact bucket assignment replaces the chain entirely.
  • Read spooling. Custom read filters are spooled to encrypted storage and never disclosed as a raw fallback on failure.
  • Ownership. Only workspace owners mutate plugins, grants, pipelines, or assignments; members may inspect the effective configuration and audit trail.