Plugin System¶
malwar supports third-party detector plugins that extend the scan pipeline with custom detection logic. Plugins are loaded at startup and run alongside the built-in detection layers.
Architecture¶
┌────────────────────────────────────────────┐
│ ScanPipeline │
│ │
│ Built-in layers (order 10-40): │
│ rule_engine -> url_crawler -> │
│ llm_analyzer -> threat_intel │
│ │
│ Plugin layers (order >= 100): │
│ plugin:foo -> plugin:bar -> ... │
└────────────────────────────────────────────┘
Plugins implement the DetectorPlugin abstract class, which extends the core
BaseDetector interface with metadata and lifecycle support.
Writing a Plugin¶
1. Implement DetectorPlugin¶
from malwar.plugins.base import DetectorPlugin, PluginMetadata
from malwar.models.finding import Finding
from malwar.scanner.context import ScanContext
class MyDetector(DetectorPlugin):
@property
def plugin_metadata(self) -> PluginMetadata:
return PluginMetadata(
name="my_detector",
version="1.0.0",
author="Your Name",
description="Detects something interesting",
layer_name="plugin:my_detector",
tags=["custom"],
)
async def detect(self, context: ScanContext) -> list[Finding]:
findings = []
# Your detection logic here, analyzing context.skill
return findings
2. Optional Lifecycle Hooks¶
Plugins inherit setup() and teardown() from BaseDetector:
async def setup(self) -> None:
"""Called once before the first scan."""
self._model = load_model()
async def teardown(self) -> None:
"""Called on shutdown."""
self._model.close()
3. Event Hooks¶
Plugins can register callbacks for scan lifecycle events:
from malwar.plugins.hooks import HookManager, HookType
def on_scan_start(**kwargs):
print(f"Scan {kwargs['scan_id']} starting")
hook_manager = HookManager()
hook_manager.register(HookType.PRE_SCAN, on_scan_start)
Available hook types:
| Hook | Fired when... | Keyword arguments |
|---|---|---|
PRE_SCAN |
Before the pipeline executes | scan_id, skill |
POST_SCAN |
After the pipeline completes | scan_id, result |
ON_FINDING |
Each time a finding is created | finding_id, finding |
Both sync and async callbacks are supported. Exceptions in hooks are logged and swallowed so they cannot break the scan pipeline.
Installing Plugins¶
Option A: Entry Points (recommended for packages)¶
Register your plugin class as a malwar.detectors entry point in
pyproject.toml:
After pip install my-package, malwar will discover it automatically.
Option B: Plugins Directory¶
Place .py files in a directory and set the environment variable:
All *.py files (excluding those starting with _) will be imported and
scanned for DetectorPlugin subclasses.
Option C: Explicit Module Paths¶
Set comma-separated dotted module paths:
Configuration¶
| Environment Variable | Description | Default |
|---|---|---|
MALWAR_PLUGINS_DIR |
Directory to scan for plugin .py files |
(none) |
MALWAR_ENABLED_PLUGINS |
Comma-separated list of plugin names to enable | (all) |
MALWAR_PLUGIN_MODULE_PATHS |
Comma-separated dotted module paths | (none) |
When MALWAR_ENABLED_PLUGINS is not set, all discovered plugins are enabled.
When set, only the listed plugins are enabled; all others are loaded but
disabled.
CLI Commands¶
# List installed plugins
malwar plugin list
# Enable a plugin
malwar plugin enable my_detector
# Disable a plugin
malwar plugin disable my_detector
API Endpoints¶
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/plugins |
List all plugins |
| POST | /api/v1/plugins/{name}/enable |
Enable a plugin by name |
| POST | /api/v1/plugins/{name}/disable |
Disable a plugin by name |
Example: List plugins¶
[
{
"name": "my_detector",
"version": "1.0.0",
"author": "Your Name",
"description": "Detects something interesting",
"layer_name": "plugin:my_detector",
"enabled": true
}
]
Example Plugin¶
A complete example plugin is provided at examples/plugins/example_detector.py.
It detects TODO/FIXME markers in skill files and demonstrates the full
plugin interface including metadata, detection logic, and finding creation.