> ## 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 C++ plugin

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

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

  Create a new repository from the [C++ plugin template](https://github.com/MalboxSandbox/malbox-plugin-template-cpp) by clicking **Use this template** on GitHub. Clone your new repository and rename the project in `CMakeLists.txt` and `plugin.toml` to match your plugin name.

  The template gives you a working plugin with CMake configuration, a `plugin.toml` manifest, example source code, and CI already set up.

  ## Prerequisites

  * C++20 compiler (GCC 11+, Clang 13+, or MSVC 2019 16.10+)
  * CMake 3.20+

  ## Project structure

  ### `CMakeLists.txt`

  ```cmake theme={null}
  cmake_minimum_required(VERSION 3.20)
  project(my-plugin LANGUAGES CXX)

  set(CMAKE_CXX_STANDARD 20)
  set(CMAKE_CXX_STANDARD_REQUIRED ON)

  find_package(MalboxPluginSDK REQUIRED)

  add_executable(my-plugin src/main.cpp)
  target_link_libraries(my-plugin PRIVATE MalboxPluginSDK::MalboxPluginSDK)
  ```

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

  ### `src/main.cpp`

  ```cpp theme={null}
  #include <malbox/plugin.hpp>

  class MyPlugin final : public malbox::HostPlugin {
  public:
      void on_start(const std::unordered_map<std::string, std::string>& config) override {
          // Called once before tasks arrive. Use config for initialization.
      }

      void on_task(const malbox::Context& ctx) override {
          auto sample = ctx.task().sample_bytes();
          ctx.progress(0.5, "analyzing");

          // ... perform analysis ...

          std::string json = R"({"status": "clean"})";
          auto data = std::span<const uint8_t>{
              reinterpret_cast<const uint8_t*>(json.data()),
              json.size()
          };
          ctx.results().push(malbox::PluginResult::json("my_result", data));
      }

      void on_stop() override {
          // Called once on shutdown. Clean up resources.
      }
  };

  int main() {
      malbox::PluginMeta meta{};
      meta.name        = "my-plugin";
      meta.version     = "0.1.0";
      meta.description = "My analysis plugin";
      meta.authors     = "Your Name";
      meta.state       = malbox::PluginState::Ephemeral;
      meta.execution   = malbox::ExecutionContext::Exclusive;

      malbox::run_host_plugin(std::make_unique<MyPlugin>(), meta);
  }
  ```

  ## Handler methods

  Override virtual methods on the `malbox::HostPlugin` base class:

  | Method         | Signature                                                       | Required           |
  | -------------- | --------------------------------------------------------------- | ------------------ |
  | `on_task`      | `(const Context&) -> void`                                      | No (default no-op) |
  | `on_start`     | `(const std::unordered_map<std::string, std::string>&) -> void` | No                 |
  | `on_stop`      | `() -> void`                                                    | No                 |
  | `health_check` | `() -> HealthStatus`                                            | No                 |
  | `on_event`     | `(const Event&) -> void`                                        | No                 |

  ## Pushing results

  Results are pushed to the daemon via the `ResultSink` obtained from `ctx.results()`. You can call push methods multiple times to stream results incrementally:

  ```cpp theme={null}
  // Using PluginResult objects
  ctx.results().push(PluginResult::json("name", byte_span));
  ctx.results().push(PluginResult::bytes("name", byte_span));
  ctx.results().push(PluginResult::file("name", "/path/to/file"));

  // Or use the convenience methods on ResultSink directly:
  ctx.results().push_json("name", byte_span);
  ctx.results().push_bytes("name", byte_span);
  ctx.results().push_file("name", "/path/to/file");
  ```

  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.

  ```cpp theme={null}
  #include <malbox/report.hpp>

  using namespace malbox::report;

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

  ctx.results().push(into_plugin_result(report));
  ```

  ## Handling errors

  Plugin methods can throw `malbox::Error` to signal failures. The SDK catches exceptions at the FFI boundary and reports them to the runtime.

  ```cpp theme={null}
  #include <malbox/error.hpp>

  if (something_wrong) {
      throw malbox::Error{malbox::ErrorKind::Unknown, "analysis failed: corrupt sample"};
  }
  ```

  SDK methods like `ctx.task().sample_bytes()` also throw `malbox::Error` on failure.

  ## Subscribing to events

  Override `on_event` to react to system-wide or plugin lifecycle events:

  ```cpp theme={null}
  void on_event(const malbox::Event& event) override {
      if (event.tag() == malbox::EventTag::PluginResultAvailable) {
          auto source = event.source();
          auto name = event.result_name();
      }
  }
  ```

  See the [Events Reference](/reference/plugin-sdk/events) for the full list of available events.

  ## Thread safety

  When using `ExecutionContext::Parallel`, multiple `on_task` calls may execute concurrently. Protect shared mutable state with `std::mutex` or similar. 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.

  ```bash theme={null}
  cmake -B build -DCMAKE_PREFIX_PATH=/path/to/malbox-plugin-sdk/dist
  cmake --build build
  ```

  ## 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 [C++ plugin template](https://github.com/MalboxSandbox/malbox-plugin-template-cpp) by clicking **Use this template** on GitHub. Clone your new repository and rename the project in `CMakeLists.txt` and `plugin.toml` to match your plugin name.

  The template gives you a working plugin with CMake configuration, a `plugin.toml` manifest, example source code, and CI already set up.

  ## Prerequisites

  * C++20 compiler (GCC 11+, Clang 13+, or MSVC 2019 16.10+)
  * CMake 3.20+
  * For Windows guest plugins: MinGW cross-compilation toolchain (provided by the Malbox Nix flake)

  ## Project structure

  ### `CMakeLists.txt`

  ```cmake theme={null}
  cmake_minimum_required(VERSION 3.20)
  project(my-plugin LANGUAGES CXX)

  set(CMAKE_CXX_STANDARD 20)
  set(CMAKE_CXX_STANDARD_REQUIRED ON)

  find_package(MalboxPluginSDK REQUIRED)

  # Generate the compile-time runtime-config header from plugin.toml.
  malbox_generate_runtime_config(
      MANIFEST ${CMAKE_CURRENT_SOURCE_DIR}/plugin.toml
      OUTPUT   ${CMAKE_CURRENT_BINARY_DIR}/generated/malbox_runtime_config.hpp
  )

  add_executable(my-plugin src/main.cpp ${MALBOX_GENERATED_HEADERS})
  target_include_directories(my-plugin PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
  target_link_libraries(my-plugin PRIVATE MalboxPluginSDK::MalboxPluginSDK)
  ```

  <Info>
    The `malbox_generate_runtime_config` function invokes the `malbox-codegen` CLI (shipped with the SDK) to read your `plugin.toml` and emit a `malbox_runtime_config.hpp` header with `constexpr` values. The header is regenerated whenever `plugin.toml` changes.
  </Info>

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

  ### `src/main.cpp`

  ```cpp theme={null}
  #include <malbox/plugin.hpp>
  #include "malbox_runtime_config.hpp"   // generated from plugin.toml

  class MyPlugin final : public malbox::GuestPlugin {
  public:
      void on_start(const malbox::Context& ctx) override {
          auto sample = ctx.task().sample_bytes();
          ctx.progress(0.5, "analyzing");

          // ... perform analysis ...

          std::string json = R"({"status": "clean"})";
          auto data = std::span<const uint8_t>{
              reinterpret_cast<const uint8_t*>(json.data()),
              json.size()
          };
          ctx.results().push(malbox::PluginResult::json("my_result", data));
      }

      void on_stop(const malbox::Context& ctx) override {
          // Called when the current task ends.
      }
  };

  int main() {
      malbox::PluginMeta meta{};
      meta.name        = "my-plugin";
      meta.version     = "0.1.0";
      meta.description = "My analysis plugin";
      meta.authors     = "Your Name";
      meta.state       = malbox::PluginState::Ephemeral;
      meta.execution   = malbox::ExecutionContext::Exclusive;

      malbox::run_guest_plugin(
          std::make_unique<MyPlugin>(),
          meta,
          malbox::generated::runtime_config);
  }
  ```

  ## Handler methods

  Override virtual methods on the `malbox::GuestPlugin` base class:

  | Method           | Signature                                        | Required |
  | ---------------- | ------------------------------------------------ | -------- |
  | `on_start`       | `(const Context&) -> void`                       | Yes      |
  | `on_stop`        | `(const Context&) -> void`                       | Yes      |
  | `execute_sample` | `(const std::filesystem::path&) -> LaunchResult` | No       |
  | `health_check`   | `() -> HealthStatus`                             | No       |

  ## Pushing results

  Results are pushed to the daemon via the `ResultSink` obtained from `ctx.results()`. You can call push methods multiple times to stream results incrementally:

  ```cpp theme={null}
  // Using PluginResult objects
  ctx.results().push(PluginResult::json("name", byte_span));
  ctx.results().push(PluginResult::bytes("name", byte_span));
  ctx.results().push(PluginResult::file("name", "/path/to/file"));

  // Or use the convenience methods on ResultSink directly:
  ctx.results().push_json("name", byte_span);
  ctx.results().push_bytes("name", byte_span);
  ctx.results().push_file("name", "/path/to/file");
  ```

  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.

  ```cpp theme={null}
  #include <malbox/report.hpp>

  using namespace malbox::report;

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

  ctx.results().push(into_plugin_result(report));
  ```

  ## Handling errors

  Plugin methods can throw `malbox::Error` to signal failures. The SDK catches exceptions at the FFI boundary and reports them to the runtime.

  ```cpp theme={null}
  #include <malbox/error.hpp>

  if (something_wrong) {
      throw malbox::Error{malbox::ErrorKind::Unknown, "analysis failed: corrupt sample"};
  }
  ```

  SDK methods like `ctx.task().sample_bytes()` also throw `malbox::Error` on failure.

  ## Thread safety

  Protect shared mutable state with `std::mutex` or similar. 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}
  cmake -B build -DCMAKE_PREFIX_PATH=/path/to/malbox-plugin-sdk/dist
  cmake --build build
  ```

  ### 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}
  cmake -B build-win \
    -DCMAKE_TOOLCHAIN_FILE=/path/to/malbox-plugin-sdk/cmake/mingw-w64-x86_64.cmake \
    -DCMAKE_PREFIX_PATH=/path/to/malbox-plugin-sdk/dist-windows
  cmake --build build-win
  ```

  This produces a statically linked `.exe` binary with no runtime DLL dependencies.

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