> ## 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.

# Creating a Rust plugin

> Step-by-step guide to building analysis plugins in Rust using the Malbox Plugin SDK

<View title="Host plugin" icon="laptop">
  ## Getting started

  Create a new repository from the [Rust plugin template](https://github.com/MalboxSandbox/malbox-plugin-template-rust) 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)

  ## Project structure

  ### `Cargo.toml`

  ```toml theme={null}
  [package]
  name = "my-plugin"
  version = "0.1.0"
  edition = "2024"

  [dependencies]
  malbox-plugin-sdk = { path = "path/to/malbox-plugin-sdk", features = ["host"] }
  serde = { version = "1", features = ["derive"] }
  ```

  ### `plugin.toml`

  Every plugin needs a `plugin.toml` manifest alongside its binary. This tells the daemon how to manage your plugin.

  ```toml theme={null}
  [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")
  analysis_timeout = 300            # max analysis time in seconds (default: 300)

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

  [runtime.stash]
  threshold_bytes = 1048576
  ttl_secs = 120

  # Event subscriptions (host plugins only)
  [events]
  subscribe = ["other-plugin"]

  [results.my_result]
  description = "What this result contains"
  user_visible = true
  display_name = "My Result"
  render = "json"
  ```

  See the [Plugin Configuration Reference](/reference/configuration/plugins) for the full field list, defaults, and validation rules.

  ### `build.rs`

  The `build.rs` ensures cargo re-runs the proc-macro when `plugin.toml` changes:

  ```rust theme={null}
  fn main() {
      println!("cargo:rerun-if-changed=plugin.toml");
  }
  ```

  ### `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.

  ```rust theme={null}
  extern crate malbox_plugin_sdk as malbox;

  use malbox::prelude::*;

  #[malbox::host_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, ctx: &Context) -> Result<()> {
          let sample = ctx.task().sample_bytes()?;
          ctx.progress(0.5, "analyzing")?;

          // ... perform analysis ...

          ctx.results().push(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.

  ## Handler methods

  | Attribute                           | Signature                                                         | Required           |
  | ----------------------------------- | ----------------------------------------------------------------- | ------------------ |
  | `#[malbox::on_task]`                | `fn(&self, ctx: &Context) -> Result<()>`                          | No (default no-op) |
  | `#[malbox::on_start]`               | `fn(&self) -> Result<()>` or `fn(&self, config: T) -> Result<()>` | No                 |
  | `#[malbox::on_stop]`                | `fn(&self) -> Result<()>`                                         | No                 |
  | `#[malbox::health_check]`           | `fn(&self) -> HealthStatus`                                       | No                 |
  | `#[malbox::on_event(EventVariant)]` | `fn(&self) -> Result<()>` or `fn(&self)`                          | No                 |

  When `on_start` takes a typed parameter, the macro deserializes the raw `HashMap<String, String>` config into that type automatically.

  ## Pushing results

  Results are pushed to the daemon via `ctx.results()`. You can call push methods multiple times to stream results incrementally. There are three result types:

  ```rust theme={null}
  ctx.results().push(PluginResult::json("name", &my_struct)?)?;
  ctx.results().push(PluginResult::bytes("name", raw_data))?;
  ctx.results().push(PluginResult::file("name", "/path/to/file"))?;
  ```

  `json()` returns `Result<PluginResult>` (serialization can fail), while `bytes()` and `file()` return `PluginResult` directly.

  Each result name should match an entry in your `plugin.toml` under `[results.*]`.

  ## Reports

  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. Reports support:

  * **Verdicts** with classification (clean/suspicious/malicious/unknown), confidence, and score
  * **Indicators** (IOCs) with open-vocabulary types like `sha256`, `ipv4`, `domain`
  * **TTPs** referencing MITRE ATT\&CK techniques
  * **Artifact references** linking to sibling `PluginResult` entries
  * **Presentation sections** with typed blocks (markdown, tables, code, hex dumps, graphs, timelines, and more)

  See the [results and reports reference](/reference/plugin-sdk/results) for the complete type catalog, or the [SDK reference](/reference/plugin-sdk/sdk) for builder API methods.

  ```rust theme={null}
  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.results().push(report.into_plugin_result()?)?;
  ```

  ## 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.

  ```rust theme={null}
  #[malbox::on_task]
  fn process(&self, ctx: &Context) -> Result<()> {
      let sample = ctx.task().sample_bytes()?;

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

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

  ## Subscribing to events

  Declare subscriptions in the `[events]` section of your `plugin.toml`, then use `#[malbox::on_event(...)]` in your handler impl to react to specific event variants:

  ```toml theme={null}
  # plugin.toml
  [events]
  subscribe = ["string-extractor", "network-analyzer"]
  ```

  ```rust theme={null}
  #[malbox::handlers]
  impl MyPlugin {
      #[malbox::on_event(TaskCompleted)]
      fn on_task_done(&self) -> Result<()> {
          info!("A task completed");
          Ok(())
      }

      #[malbox::on_event(PluginResultAvailable)]
      fn on_upstream_result(&self) -> Result<()> {
          info!("An upstream plugin produced a result");
          Ok(())
      }
  }
  ```

  Event handlers are called with no arguments (the event metadata is matched internally by the macro). Handlers can optionally return `Result<()>`.

  See the [Events Reference](/reference/plugin-sdk/events) 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:

  ```rust theme={null}
  #[malbox::host_plugin]
  struct MyPlugin {
      cache: Mutex<HashMap<String, String>>,
  }
  ```

  ## Thread safety

  When using `ExecutionContext::Parallel`, multiple `on_task` calls may execute concurrently. Protect shared mutable state with `Mutex`, `RwLock`, or atomic types. Health checks may also arrive on a different thread regardless of execution context.

  ## Testing

  The SDK provides a `testkit` feature for writing unit tests without a running daemon. Enable it in your `Cargo.toml`:

  ```toml theme={null}
  [dev-dependencies]
  malbox-plugin-sdk = { path = "path/to/malbox-plugin-sdk", features = ["host", "testkit"] }
  ```

  This gives you `Context::test_new()` to create a mock context for testing handler logic.

  ## Build

  ### Linux

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

  ```bash theme={null}
  cargo build --release
  ```

  ## Deploy

  Place the compiled binary and `plugin.toml` in a subdirectory of the daemon's plugin directory. The daemon discovers plugins automatically on startup.

  <Tree>
    <Tree.Folder name="~/.config/malbox/plugins/" defaultOpen>
      <Tree.File name="my-host-plugin-bin" />

      <Tree.File name="plugin.toml" />
    </Tree.Folder>
  </Tree>

  ## Examples

  See the [example plugins on GitHub](https://github.com/MalboxSandbox/malbox/tree/dev/back-end/examples/plugins).
</View>

<View title="Guest plugin" icon="container">
  ## Getting started

  Create a new repository from the [Rust plugin template](https://github.com/MalboxSandbox/malbox-plugin-template-rust) 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 Nix flake)

  ## Project structure

  ### `Cargo.toml`

  ```toml theme={null}
  [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"] }
  ```

  ### `plugin.toml`

  Every plugin needs a `plugin.toml` manifest alongside its binary. This tells the daemon how to manage your plugin.

  ```toml theme={null}
  [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")
  analysis_timeout = 300            # max analysis time in seconds (default: 300)

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

  [runtime.stash]
  threshold_bytes = 1048576
  ttl_secs = 120

  # Event subscriptions (host plugins only)
  [events]
  subscribe = ["other-plugin"]

  [results.my_result]
  description = "What this result contains"
  user_visible = true
  display_name = "My Result"
  render = "json"
  ```

  See the [Plugin Configuration Reference](/reference/configuration/plugins) for the full field list, defaults, and validation rules.

  ### `build.rs`

  The `build.rs` ensures cargo re-runs the proc-macro when `plugin.toml` changes:

  ```rust theme={null}
  fn main() {
      println!("cargo:rerun-if-changed=plugin.toml");
  }
  ```

  ### `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.

  ```rust theme={null}
  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, ctx: &Context) -> Result<()> {
          info!("Plugin ready - starting task");
          let sample = ctx.task().sample_bytes()?;
          ctx.progress(0.5, "analyzing")?;

          // ... perform analysis ...

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

      #[malbox::on_stop]
      fn shutdown(&self, ctx: &Context) -> Result<()> {
          info!("Task ending");
          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.

  ## Handler methods

  | Attribute                 | Signature                                                             | Required |
  | ------------------------- | --------------------------------------------------------------------- | -------- |
  | `#[malbox::on_start]`     | `fn(&self, ctx: &Context) -> Result<()>` or `fn(&self) -> Result<()>` | No       |
  | `#[malbox::on_stop]`      | `fn(&self, ctx: &Context) -> Result<()>` or `fn(&self) -> Result<()>` | No       |
  | `#[malbox::health_check]` | `fn(&self) -> HealthStatus`                                           | No       |

  Guest plugin `on_start` and `on_stop` optionally receive `&Context` for the current task.

  ## Custom sample launching

  By default, the SDK launches the sample using the platform's native process creation API. Override `execute_sample` to customize this for non-EXE scenarios (DLL loading, COM dispatch, etc.):

  ```rust theme={null}
  #[malbox::handlers]
  impl MyPlugin {
      #[malbox::on_start]
      fn init(&self, ctx: &Context) -> Result<()> {
          // Set up monitoring before sample launch
          Ok(())
      }

      fn execute_sample(&self, sample_path: &std::path::Path) -> Result<LaunchResult> {
          // Custom launch logic (e.g. rundll32 for DLLs)
          info!("Launching sample: {}", sample_path.display());
          Ok(LaunchResult::Launched)
      }

      #[malbox::on_stop]
      fn shutdown(&self, ctx: &Context) -> Result<()> {
          // Flush results after analysis timeout
          Ok(())
      }
  }
  ```

  Return `LaunchResult::Launched` if your plugin handled the launch, or `LaunchResult::UseDefault` to fall back to the SDK's built-in launcher.

  ## Pushing results

  Results are pushed to the daemon via `ctx.results()`. You can call push methods multiple times to stream results incrementally. There are three result types:

  ```rust theme={null}
  ctx.results().push(PluginResult::json("name", &my_struct)?)?;
  ctx.results().push(PluginResult::bytes("name", raw_data))?;
  ctx.results().push(PluginResult::file("name", "/path/to/file"))?;
  ```

  `json()` returns `Result<PluginResult>` (serialization can fail), while `bytes()` and `file()` return `PluginResult` directly.

  Each result name should match an entry in your `plugin.toml` under `[results.*]`.

  ## Reports

  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. Reports support:

  * **Verdicts** with classification (clean/suspicious/malicious/unknown), confidence, and score
  * **Indicators** (IOCs) with open-vocabulary types like `sha256`, `ipv4`, `domain`
  * **TTPs** referencing MITRE ATT\&CK techniques
  * **Artifact references** linking to sibling `PluginResult` entries
  * **Presentation sections** with typed blocks (markdown, tables, code, hex dumps, graphs, timelines, and more)

  See the [results and reports reference](/reference/plugin-sdk/results) for the complete type catalog, or the [SDK reference](/reference/plugin-sdk/sdk) for builder API methods.

  ```rust theme={null}
  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.results().push(report.into_plugin_result()?)?;
  ```

  ## 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.

  ```rust theme={null}
  #[malbox::on_start]
  fn init(&self, ctx: &Context) -> Result<()> {
      let sample = ctx.task().sample_bytes()?;

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

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

  ## Holding state

  Your plugin struct can hold fields. For concurrent access, wrap mutable state in `Mutex` or similar:

  ```rust theme={null}
  #[malbox::guest_plugin]
  struct MyPlugin {
      cache: Mutex<HashMap<String, String>>,
  }
  ```

  ## Thread safety

  The SDK requires `Send + Sync` for guest plugins. Protect shared mutable state with `Mutex`, `RwLock`, or atomic types. Health checks may arrive on a different thread.

  ## Build

  ### Linux

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

  ```bash theme={null}
  cargo build --release
  ```

  ### Windows (cross-compile)

  Guest plugins that run inside a Windows analysis VM need to be cross-compiled. The Malbox Nix flake provides a MinGW toolchain for this.

  <Note>
    The Malbox Nix flake automatically configures the MinGW linker and library paths. You don't need additional setup if you're using `nix develop`.
  </Note>

  ```bash theme={null}
  cargo build --release --target x86_64-pc-windows-gnu
  ```

  ## Deploy

  You must deploy guest plugin binaries 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:

  <Tree>
    <Tree.Folder name="~/.config/malbox/plugins/" defaultOpen>
      <Tree.File name="my-guest-plugin-bin" />

      <Tree.File name="plugin.toml" />
    </Tree.Folder>
  </Tree>

  Provision into the VM using the deploy playbook:

  ```bash theme={null}
  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](https://github.com/MalboxSandbox/malbox/tree/dev/back-end/examples/plugins).
</View>
