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

Plugins Overview

Arbitraitor supports a plugin system for extending detection, intelligence, and execution capabilities. Plugins are isolated from the core using either the Wasmtime Component Model or a subprocess protocol.

Plugin types

TypeRoleExamples
DetectorAnalyzes artifacts, returns findingsYARA-X rules, custom AV, language analyzers
IntelligenceProvides threat indicatorsURLhaus adapter, community feed, custom TI
ProvenanceVerifies signatures and attestationsminisign, cosign, custom PKI
WrapperTranslates download tool argumentscurl, wget, fetch

Each plugin type has a specific WIT world (interface contract) that limits what capabilities it can access.

Trust tiers

Plugins are classified by trust level:

TierDescriptionEnforcement
Built-inShips with ArbitraitorAlways loaded in enforcement mode
First-partyDeveloped by ArbSec teamAlways loaded, code reviewed
CommunitySubmitted by usersDisabled by default in enforcement mode

See ADR 0011 for the full trust classification model.

Plugin manifest

Every plugin has a manifest.toml:

[plugin]
id = "my-detector"
name = "My Custom Detector"
version = "1.0.0"
type = "detector"  # detector | intelligence | provenance | wrapper

# WIT world this plugin implements
world = "arbitraitor:plugin/detector"

# Trust tier
trust_class = "community"  # builtin | first_party | community

# Plugin binary
[plugin.runtime]
type = "wasmtime"  # wasmtime | subprocess
path = "./my-detector.wasm"

# Or for subprocess:
[plugin.runtime]
type = "subprocess"
path = "/usr/local/bin/my-detector"
expected_digest = "sha256:abc123..."

# Capabilities requested by this plugin
[plugin.capabilities]
# For WASM: declared in WIT, enforced by runtime
# For subprocess: explicit allowlist
allow_network = false
allow_filesystem_read = ["/tmp/arbitraitor"]
allow_environment = []

Plugin lifecycle

discover -> load -> init -> call -> shutdown

Discover

Plugins are discovered from configured search directories:

  1. Read ~/.arbitraitor/plugins/*/manifest.toml
  2. Validate manifest schema
  3. Check trust class against policy
  4. Add to available plugin registry

Load

The plugin host loads the runtime instance:

  • Wasmtime: Initialize Wasmtime engine with resource limits
  • Subprocess: Spawn process with clean environment, closed descriptors

Init

The plugin receives initialization data:

#![allow(unused)]
fn main() {
// For WASM plugins
init {
    config: PluginConfig,
    workspace: WorkspaceCapabilities,
}

// For subprocess plugins
{"type": "init", "config": {...}}
}

The plugin validates the config and returns capabilities it will use.

Call

The core calls the plugin with input data:

#![allow(unused)]
fn main() {
// For detector plugins
fn analyze(&self, artifact: ArtifactHandle) -> Vec<Finding>;

// For intelligence plugins
fn lookup(&self, indicator: Indicator) -> Vec<IndicatorMatch>;
}

Shutdown

When the pipeline completes or a timeout is reached, the plugin is dropped:

  • WASM: Guest memory is freed, resource limits are enforced
  • Subprocess: Process is terminated with SIGTERM, then SIGKILL if needed

Capability enforcement

Plugins declare capabilities in their WIT interface and cannot exceed them at runtime.

Wasmtime sandbox defaults

ControlDefault
NetworkNone
FilesystemNone
EnvironmentNone
ClockDeterministic or host-provided only
MemoryBounded (128 MB default)
Execution fuelBounded (1B instructions default)
Host callsBounded by count and deadline

Component Model execution (ADR-0006, spec ยง41.9.1)

WasmPlugin loads a .wasm Component Model binary, instantiates it inside the sandboxed Wasmtime engine, and calls the analyze export. Host bindings are generated at compile time via wasmtime::component::bindgen! from the workspace WIT file (wit/arbitraitor-plugin.wit), selecting the detector world.

The three host imports (get-artifact-bytes, get-artifact-size, log) are implemented on DetectorStore, which enforces the ADR-0006 wall-clock deadline on every host call. Fuel and epoch interruption bound guest execution; traps, OOM, and fuel exhaustion return WasmPluginError::Trap โ€” the sandbox boundaries hold and the host continues safely.

The DetectorPlugin::analyze trait impl calls analyze_artifact internally and returns empty findings on error (fail-safe per conventions). Callers that need typed errors should use analyze_artifact directly with a cached WasmEngine reference.

Subprocess controls

ControlEnforcement
Executable pathAllowlisted, digest-pinned
ArgumentsStructured (no raw shell strings)
EnvironmentClean, explicit allowlist
DescriptorsClosed inherited
Process groupIsolated (Job Object on Windows)
TimeoutEnforced with kill-tree
Output sizeLimited
MemoryLimited via cgroup/resource limits

Writing a detector plugin

See WIT Interfaces for the detector world definition and Subprocess Protocol for the JSON protocol format.

Plugin security

  • Plugins cannot access the filesystem unless explicitly granted
  • Plugins cannot make network calls unless explicitly granted
  • WASM plugins are memory-isolated from the host process
  • Subprocess plugins are killed if they exceed resource limits
  • No plugin can bypass the approval flow

Wasmtime CVE risk register

The Wasmtime runtime is tracked against known CVEs in

ADR 0037. The risk register catalogs all advisories from the Bytecode Alliance GitHub Security Advisories and the RustSec database, and records whether the pinned version is affected. The cargo-deny advisories check in CI automatically fails if a new advisory affects the pinned wasmtime version.