cradle-plugin-api-0.1.3 has been yanked.
cradle-plugin-api
This crate is intended for use when developing plugins for the Cradle agent
Documentation of the available functions is located in the docs.rs page.
[!WARNING]
Every plugin must implement Default, as the registration macro relies on it.
Plugins must also be compiled as cdylib crates.
Cargo.toml
[package]
name = "my-cradle-plugin"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
cradle-plugin-api = "0.1"
cradle-shared = "0.1"
Minimal plugin
use cradle_plugin_api::*;
struct TestPlugin {
results: PluginResults,
}
impl Default for TestPlugin {
fn default() -> Self {
Self {
results: PluginResults::new("test-plugin"),
}
}
}
impl CradlePlugin for TestPlugin {
fn name(&self) -> &str {
"test-plugin"
}
fn on_event(&mut self, event: &AgentEvent) -> CradleResult {
if let AgentEvent::ProcessCreated { target_path, .. } = event {
self.results
.pass("process-created", format!("process created: {target_path}"));
}
Ok(())
}
fn results(&mut self) -> Vec<CheckResult> {
self.results.take()
}
}
register_plugin!(TestPlugin);
Emitting results
PluginResults helps build check output with the plugin name filled in
Use take() in your results() implementation so each result is returned only once
self.results.pass("loaded", "plugin initialized successfully");
self.results.info("target", "received process metadata");
self.results.warn_with("network", "direct IP connection observed", "no DNS event matched it");
self.results.fail("policy", "blocked behavior detected");
Hooking an export
Plugins that need runtime inspection can install hooks during init
Hooking is unsafe because it reads and writes target process memory
use cradle_plugin_api::*;
struct HookPlugin {
results: PluginResults,
}
impl Default for HookPlugin {
fn default() -> Self {
Self {
results: PluginResults::new("hook-plugin"),
}
}
}
impl CradlePlugin for HookPlugin {
fn name(&self) -> &str {
"hook-plugin"
}
fn init(&mut self, engine: CradleMutex<cradle_hooks::HookEngine>) -> CradleResult {
unsafe {
engine.lock().hook_export("kernel32.dll", "CreateFileW", Box::new(|ctx| {
let file_name_ptr = ctx.arg(0);
let file_name = ctx.read_wide_string(file_name_ptr, 512);
clog!("CreateFileW called for {file_name}");
Ok(cradle_hooks::HookAction::Continue)
}))
}
}
fn results(&mut self) -> Vec<CheckResult> {
self.results.take()
}
}
register_plugin!(HookPlugin);
Logging
Use clog! for general plugin logs or plog!(self, "...") to prefix logs with the plugin name.
Logs are stored by the plugin API and drained by the agent.
ABI exports
Use register_plugin!(YourPluginType) unless you are intentionally implementing the raw FFI boundary yourself.
The macro exports the required symbols for API versioning, lifecycle callbacks, result transfer, and plugin-owned string cleanup.