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
| Type | Role | Examples |
|---|---|---|
| Detector | Analyzes artifacts, returns findings | YARA-X rules, custom AV, language analyzers |
| Intelligence | Provides threat indicators | URLhaus adapter, community feed, custom TI |
| Provenance | Verifies signatures and attestations | minisign, cosign, custom PKI |
| Wrapper | Translates download tool arguments | curl, 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:
| Tier | Description | Enforcement |
|---|---|---|
| Built-in | Ships with Arbitraitor | Always loaded in enforcement mode |
| First-party | Developed by ArbSec team | Always loaded, code reviewed |
| Community | Submitted by users | Disabled 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:
- Read
~/.arbitraitor/plugins/*/manifest.toml - Validate manifest schema
- Check trust class against policy
- 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
| Control | Default |
|---|---|
| Network | None |
| Filesystem | None |
| Environment | None |
| Clock | Deterministic or host-provided only |
| Memory | Bounded (128 MB default) |
| Execution fuel | Bounded (1B instructions default) |
| Host calls | Bounded 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
| Control | Enforcement |
|---|---|
| Executable path | Allowlisted, digest-pinned |
| Arguments | Structured (no raw shell strings) |
| Environment | Clean, explicit allowlist |
| Descriptors | Closed inherited |
| Process group | Isolated (Job Object on Windows) |
| Timeout | Enforced with kill-tree |
| Output size | Limited |
| Memory | Limited 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.