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

WIT Interfaces

Arbitraitor uses the WebAssembly Component Model (WASI Preview 2) for plugin isolation. Plugins are compiled to WASM components and communicate through WIT-defined interfaces.

WIT overview

WIT (WebAssembly Interface Types) defines the types and functions a component can import and export. Each Arbitraitor plugin world has its own WIT package.

Plugin worlds

Detector world

package arbitraitor:plugin/detector@1.0.0;

interface analyzer {
  record artifact {
    digest: string,
    content-type: string,
  }

  record finding {
    id: string,
    severity: severity,
    title: string,
    description: string,
    locations: list<location>,
  }

  enum severity {
    low,
    medium,
    high,
    critical,
  }

  record location {
    path: string,
    offset: u64,
    line: u32,
  }

  analyze: func(artifact: artifact) -> list<finding>;
}

world detector {
  export analyzer;
}

Intelligence world

package arbitraitor:plugin/intelligence@1.0.0;

interface lookup {
  record indicator {
    type: indicator-type,
    value: string,
  }

  enum indicator-type {
    url,
    domain,
    ip-address,
    sha256,
    email,
  }

  record match {
    indicator: indicator,
    source: string,
    confidence: confidence,
    last-seen: string,
    tags: list<string>,
  }

  enum confidence {
    low,
    medium,
    high,
  }

  lookup: func(indicator: indicator) -> list<match>;
}

world intelligence {
  export lookup;
}

Provenance world

package arbitraitor:plugin/provenance@1.0.0;

interface verifier {
  record artifact {
    digest: string,
    size: u64,
  }

  record attestation {
    type: attestation-type,
    signer: string,
    timestamp: string,
    data: list<u8>,
  }

  enum attestation-type {
    minisign,
    cosign,
    tuf,
    gpg,
  }

  verify: func(artifact: artifact) -> option<attestation>;
}

world provenance {
  export verifier;
}

Wrapper world

package arbitraitor:plugin/wrapper@1.0.0;

interface translator {
  record command {
    tool: string,
    args: list<string>,
    env: list<tuple<string, string>>,
  }

  translate: func(command: command) -> result<command, string>;
}

world wrapper {
  export translator;
}

Host functions

Plugins can call host functions provided by Arbitraitor:

package arbitraitor:plugin/host@1.0.0;

interface host {
  // Read artifact bytes (opaque handle)
  read-artifact: func(digest: string) -> list<u8>;

  // Log a message
  log: func(level: log-level, message: string);

  // Get current time
  wall-clock: func() -> tuple<u64, u32>;  // seconds, nanoseconds

  // Get deterministic time for the current operation
  operation-time: func() -> tuple<u64, u32>;

  enum log-level {
    debug,
    info,
    warn,
    error,
  }
}

world host {
  import host;
}

Compiling a Wasm component

1. Install tooling

cargo install cargo-component
wasmtime --version

2. Define WIT

Create wit/my-detector.wit:

package my-org:my-detector@1.0.0;

interface analyzer {
  record artifact {
    digest: string,
    content-type: string,
  }

  record finding {
    id: string,
    severity: severity,
    title: string,
    description: string,
  }

  enum severity {
    low,
    medium,
    high,
    critical,
  }

  analyze: func(artifact: artifact) -> list<finding>;
}

world my-detector {
  export analyzer;
}

3. Implement in Rust

# my-detector/Cargo.toml
[package]
name = "my-detector"
version = "1.0.0"
edition = "2021"

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

[dependencies]
wit-bindgen = "0.28"
serde = { version = "1.0", features = ["derive"] }

[profile.release]
opt-level = "s"
lto = true
#![allow(unused)]
fn main() {
// src/lib.rs
use wit_bindgen::generate;

generate!({
    world: "my-detector",
    exports: {
        "my-org:my-detector/analyzer": Analyzer,
    }
});

struct Analyzer;

impl exports::analyzer::Analyzer for Analyzer {
    fn analyze(artifact: exports::analyzer::Artifact) -> Vec<exports::analyzer::Finding> {
        // Read artifact bytes via host
        let bytes = host::host::read_artifact(&artifact.digest);

        // Analyze content
        let mut findings = Vec::new();

        if contains_malicious_pattern(&bytes) {
            findings.push(exports::analyzer::Finding {
                id: "my-detector:bad-pattern".to_string(),
                severity: exports::analyzer::Severity::High,
                title: "Malicious pattern detected".to_string(),
                description: "Found known bad pattern".to_string(),
            });
        }

        findings
    }
}
}

4. Build

cargo component build --release
# Output: target/wasm32-wasip2/release/my_detector.wasm

Host function reference

read-artifact

Reads artifact bytes from CAS by digest. The plugin receives the content it was approved to analyze.

#![allow(unused)]
fn main() {
fn read-artifact(digest: &str) -> Vec<u8>
}

log

Logs a message at the specified level.

#![allow(unused)]
fn main() {
fn log(level: LogLevel, message: &str)
}

wall-clock

Returns the current wall clock time.

#![allow(unused)]
fn main() {
fn wall-clock() -> (u64, u32)  // (seconds since epoch, nanoseconds)
}

operation-time

Returns a deterministic time for the current operation. This is monotonically increasing within a single pipeline run, allowing plugins to implement time-based logic without breaking determinism.

#![allow(unused)]
fn main() {
fn operation-time() -> (u64, u32)  // (seconds, nanoseconds)
}

Resource limits

The host enforces resource limits on all plugins:

ResourceDefault limit
Memory128 MB
Execution fuel1 billion instructions
Call deadline5 seconds
Host calls per invocation100
Output size1 MB