# `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](https://docs.rs/crate/cradle-plugin-api/) page.
> [!WARNING]
> Every plugin must implement `Default`, as the registration macro relies on it.
> Plugins must also be compiled as `cdylib` crates.
## Cargo.toml
```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
```rust
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
```rust
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
```rust
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.