Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.malbox.app/llms.txt

Use this file to discover all available pages before exploring further.

Getting started

Create a new repository from the Rust plugin template by clicking Use this template on GitHub. Clone your new repository and rename the project in Cargo.toml and plugin.toml to match your plugin name. The template gives you a working plugin with Cargo configuration, a plugin.toml manifest, a build.rs, example source code, and CI already set up.

Prerequisites

  • Rust toolchain (stable, 2024 edition)
  • For Windows guest plugins: MinGW cross-compilation toolchain (provided by the Malbox devenv)

Project structure

Cargo.toml

The SDK is declared as a dependency with either the host or guest feature enabled depending on your plugin type. For a guest plugin:
[package]
name = "my-plugin"
version = "0.1.0"
edition = "2024"

[dependencies]
malbox-plugin-sdk = { path = "path/to/malbox-plugin-sdk", features = ["guest"] }
serde = { version = "1", features = ["derive"] }
For a host plugin, swap the feature:
malbox-plugin-sdk = { path = "path/to/malbox-plugin-sdk", features = ["host"] }

plugin.toml

Every plugin needs a manifest alongside its binary. This tells the daemon how to manage your plugin, and its [runtime] section is baked into the plugin binary at compile time by the SDK’s proc-macro.
[plugin]
name = "my-plugin"
version = "0.1.0"
description = "My analysis plugin"
authors = ["Your Name"]
type = "guest"                    # or "host"

[runtime]
state = "ephemeral"               # or "persistent", "scoped"
execution = "exclusive"           # or "sequential", "parallel", "unrestricted"
port = 50051                      # gRPC listen port (default: 50051)
log_filter = "info"               # tracing filter (default: "info")

[runtime.paths]
sample_dir       = "/tmp/malbox/samples"      # where daemon pushes the sample
artifact_dir     = "/tmp/malbox/artifacts"    # where plugin writes output (auto-collected)
stash_dir        = "/tmp/malbox/stash"        # internal result stash spillover
log_dir          = "/tmp/malbox/logs"         # SDK-internal log overflow files
external_log_dir = "/tmp/malbox/ext-logs"     # external tool/driver logs (auto-collected)

[runtime.stash]
threshold_bytes = 1048576
ttl_secs = 120

[results.my_result]
description = "What this result contains"
user_visible = true
display_name = "My Result"
render = "json"
All [runtime] fields are optional and have sensible defaults. See the Plugin Configuration Reference for the full list and defaults.

build.rs

The build.rs ensures cargo re-runs the proc-macro when plugin.toml changes:
fn main() {
    println!("cargo:rerun-if-changed=plugin.toml");
}
See Plugin Configuration for details on plugin types, state management, and execution contexts.

src/main.rs

The SDK uses attribute macros to wire your plugin into the runtime. You annotate your struct and impl block instead of manually implementing traits.
extern crate malbox_plugin_sdk as malbox;

use malbox::prelude::*;

#[malbox::guest_plugin]
struct MyPlugin;

#[malbox::handlers]
impl MyPlugin {
    #[malbox::on_start]
    fn init(&self) -> Result<()> {
        info!("Plugin ready - waiting for tasks");
        Ok(())
    }

    #[malbox::on_task]
    fn process(&self, task: Task, ctx: &Context) -> Result<()> {
        let sample = task.sample_bytes()?;
        ctx.emit_progress(0.5, "analyzing")?;

        // ... perform analysis ...

        ctx.push_result(PluginResult::json("my_result", &serde_json::json!({
            "status": "clean"
        }))?)?;
        Ok(())
    }

    #[malbox::on_stop]
    fn shutdown(&self) -> Result<()> {
        info!("Plugin shutting down");
        Ok(())
    }
}
The #[malbox::handlers] macro scans your impl block for annotated methods and generates the necessary trait implementations. Your method names can be anything you like - the attributes determine their role.

Plugin struct macros

Use one of these on your struct to declare the plugin type:
MacroDescription
#[malbox::host_plugin]Host plugin - runs on the daemon machine via IPC
#[malbox::guest_plugin]Guest plugin - runs inside a VM/container via gRPC
State and execution settings are read from plugin.toml’s [runtime] section at compile time - no attribute configuration is needed on the struct.

Handler methods

Annotate methods inside a #[malbox::handlers] impl block:
AttributeSignatureRequired
#[malbox::on_task]fn(&self, Task, &Context) -> Result<()>Yes
#[malbox::on_start]fn(&self) -> Result<()>No
#[malbox::on_stop]fn(&self) -> Result<()>No
#[malbox::health_check]fn(&self) -> HealthStatusNo
#[malbox::on_event(...)]Varies (see Subscribing to events)No
Only #[malbox::on_task] is required. All other handlers are optional and default to no-ops.

Pushing results

Results are pushed to the daemon during on_task via ctx.push_result(). You can call it multiple times to stream results incrementally. There are three result types:
ctx.push_result(PluginResult::json("name", &my_struct)?)?;
ctx.push_result(PluginResult::bytes("name", raw_data))?;
ctx.push_result(PluginResult::file("name", "/path/to/file"))?;
Each result name should match an entry in your plugin.toml under [results.*]. For structured analysis output with verdicts, indicators, TTPs, and frontend-renderable sections, use the ReportBuilder to construct a Report and push it as a result:
use malbox::types::report::{ReportBuilder, Classification, Confidence, Indicator};

let report = ReportBuilder::new("my-plugin", "0.1.0")
    .summary("Analysis complete")
    .verdict(Classification::Malicious, Some(85), Some(Confidence::High))
    .indicator(Indicator::new("sha256", "abc123..."))
    .section("overview", "Overview", |s| {
        s.markdown("Found malicious indicators")
    })
    .build();

ctx.push_result(report.into_plugin_result()?)?;
See the Rust SDK Reference for the full report API.

Handling errors

The SDK uses its own Result<T> type aliased to std::result::Result<T, SdkError>. Handler methods return Result<()> and can use ? to propagate errors naturally.
#[malbox::on_task]
fn process(&self, task: Task, ctx: &Context) -> Result<()> {
    let sample = task.sample_bytes()?;

    if sample.is_empty() {
        return Err(SdkError::Plugin(anyhow::anyhow!("empty sample")));
    }

    ctx.push_result(PluginResult::json("result", &"ok")?)?;
    Ok(())
}

Subscribing to events

Use #[malbox::on_event(...)] to react to system events. Specify the event variant as the argument:
#[malbox::handlers]
impl MyPlugin {
    #[malbox::on_event(TaskEvent::TaskCompleted)]
    fn on_task_done(&self, payload: TaskEventPayload, ctx: &Context) -> Result<()> {
        info!(task_id = payload.task_id, "Task completed");
        Ok(())
    }

    #[malbox::on_event(DaemonEvent::ConfigReloaded)]
    fn on_config_reload(&self) {
        info!("Configuration reloaded");
    }

    #[malbox::on_event(PluginEvent::PluginResultProduced, from = ["host-file-info"])]
    fn on_result(&self, payload: PluginEventPayload, _ctx: &Context) -> Result<()> {
        info!(plugin_id = payload.plugin_id, "Got result from watched plugin");
        Ok(())
    }
}
The from filter narrows plugin events to results from specific named plugins. You can also declare event subscriptions in plugin.toml:
[events]
subscribe = ["TaskCreated", "TaskStarting"]
See the Events Reference for the full list of available events.

Holding state

Your plugin struct can hold fields. For concurrent access with ExecutionContext::Parallel, wrap mutable state in Mutex or similar:
#[malbox::host_plugin]
struct MyPlugin {
    cache: Mutex<HashMap<String, String>>,
}

Thread safety

When using ExecutionContext::Parallel, multiple on_task calls may execute concurrently. The SDK requires Send + Sync for guest plugins. Protect shared mutable state with Mutex, RwLock, or atomic types. Health checks may also arrive on a different thread regardless of execution context.

Build

Linux

Host plugins always target Linux (they run on the daemon machine). Guest plugins targeting a Linux guest VM also use this:
cargo build --release

Windows (cross-compile)

Guest plugins that run inside a Windows analysis VM need to be cross-compiled:
cargo build --release --target x86_64-pc-windows-gnu
The Malbox devenv automatically configures the MinGW linker and library paths for the x86_64-pc-windows-gnu target. No additional setup is needed if you’re using devenv shell.

Deploy

Host plugins

Place the compiled binary and plugin.toml in a subdirectory of the daemon’s plugin directory. The daemon discovers plugins automatically on startup.
~/.config/malbox/plugins/
my-host-plugin-bin
plugin.toml

Guest plugins

Guest plugin binaries must be deployed inside the analysis VM. Place them in the daemon’s plugin directory (so the daemon knows about them), then provision the binary into the VM:
~/.config/malbox/plugins/
my-guest-plugin-bin
plugin.toml
Provision into the VM using the deploy playbook:
cargo run -p malbox-cli machine provision <machine_id> \
  --provisioner ansible \
  --config '{"playbook": "configuration/infrastructure/ansible/playbooks/windows/deploy-guest-plugin.yml"}' \
  --snapshot <snapshot_name> \
  --plugins my-guest-plugin

Examples

See the example plugins on GitHub.