$ cat plugin.md | less
Build a plugin
An InSpectre plugin is a single declarative manifest — JSON or YAML. There is no plugin code to write, compile or execute: the manifest declares HTTP / file / SNMP operations, and the InSpectre plugin engine runs them on a schedule (or in response to events), then maps the results into your device inventory.
Plugins are declarative
If you have used other monitoring tools you may expect imperative code hooks like on_device_discovered() or on_scan_complete(). InSpectre's equivalent is the declarative event-hooks map: you bind an InSpectre event to a named action the engine runs. This keeps community plugins safe to share — a manifest can only make the network calls it declares, against credentials the user explicitly enters.
| Imperative hook you might expect | InSpectre event key |
|---|---|
on_device_discovered | device.new |
on_device_online | device.online |
on_device_offline | device.offline |
on_port_opened / on_scan_complete | port.opened |
on_vulnerability_found | vuln.found (+ vuln.critical, vuln.high) |
on_device_blocked / on_device_unblocked | device.blocked / device.unblocked |
Quick start
- Copy
examples/plugins/TEMPLATE.yaml(or the JSONhello-world.json). - Set a unique lowercase
id, then editconfig_schema,endpointsandactionsto match your target API. - In InSpectre: Settings → Plugins → Upload Plugin, choose your file.
- Open the plugin, fill in config, click Test Connection.
- Toggle Enabled. Polling starts on the next cycle; blocking plugins become selectable under Settings → Security Responses → Blocking Method.
A minimal discovery plugin is just an endpoint, one action, and a polling block:
{
"id": "my-router",
"name": "My Router",
"version": "1.0.0",
"capabilities": ["discovery"],
"config_schema": [
{ "key": "host", "label": "Base URL", "type": "url", "default": "http://192.168.0.1", "required": true },
{ "key": "api_key", "label": "API Key", "type": "password", "default": "", "required": true }
],
"endpoints": { "base_url": "{host}", "auth": "api-key-header", "api_key_header": "X-API-Key" },
"actions": {
"get_leases": {
"method": "GET",
"path": "/api/dhcp/leases",
"response_mapping": { "root_path": "leases",
"fields": { "mac": "mac_address", "ip": "ip_address", "name": "hostname" } }
}
},
"polling": { "action": "get_leases", "interval_seconds": 120 }
}
Manifest reference
| Field | Req | Description |
|---|---|---|
id | ✅ | Unique slug — lowercase letters, digits, hyphens. |
name | ✅ | Display name. |
version | ✅ | SemVer, e.g. 1.0.0. |
capabilities | ✅ | What the plugin does — see below. |
config_schema | ✅ | User-configurable fields. May be empty. |
endpoints | Base URL, auth, headers. Required for HTTP/SNMP plugins. | |
actions | Named operations the engine can run. | |
event_hooks | Map InSpectre event → action name. | |
polling | Scheduled action(s). | |
data_mapping | tag_fields to promote onto device tags. |
Capabilities
Declare what your plugin genuinely does — the engine uses these to decide which flows it joins. Only discovery/presence plugins have poll results written to the inventory; only blocking plugins appear as block methods.
discoveryMAC/IP/hostname rows for the inventory.
enrichmentAdds vendor, type, tags to known devices.
presenceReports whether a device is online.
dnsIs a DNS server/resolver (AdGuard, Pi-hole).
trafficProvides DNS query logs / flow data.
blockingCan enforce per-device blocks.
firewallControls a stateful firewall.
exportSends InSpectre state to an external system.
notificationDelivers alerts.
Config schema
Each entry renders a field in the plugin's config form. Values become {key} placeholders usable in endpoints and actions.
Field types: string, password, integer, boolean, select, multiline, url, filepath.
passwordvalues are encrypted at rest (Fernet, derived from the serverSECRET_KEY) and shown as**redacted**when reading config back via the API.multilinerenders a textarea (e.g. a newline-separated keyword list).filepathis a container-internal path — mount the file into the backend container viavolumes:.
Event hooks
Bind an InSpectre event to an action. When the event fires, the engine runs the action asynchronously (fire-and-forget) for every enabled plugin that declares a matching hook.
"event_hooks": {
"device.new": "notify_action",
"vuln.critical": "notify_action",
"device.offline": "notify_action"
}
| Event | Fires when | Context |
|---|---|---|
device.new | A device (or new interface) is first seen | mac, name, ip |
device.online | A device comes back online | mac, name, ip |
device.offline | A device goes offline | mac, name, ip |
port.opened | A new open port is detected | mac, name, ip, ports |
vuln.found | A vuln scan finds issues | mac, name, ip, vulns |
device.blocked | A device was blocked | mac, ip |
device.unblocked | A device was unblocked | mac, ip |
device.blocked → block_client — the blocking coordinator already calls it directly; hooking it again would double-fire. Event hooks are for reacting to events (e.g. sending a notification).Polling
Run action(s) on a schedule. Results are upserted into the inventory when the plugin has discovery or presence capability.
| Key | Description |
|---|---|
action | Single action name to poll. |
actions | Array of action names (results merged, last write wins per MAC). |
interval_seconds | Poll interval. Default 300. |
run_on_startup | Polling begins after a ~30 s startup grace. |
max_rate_per_minute | Optional per-action rate cap. |
The blocking contract
A blocking plugin must declare block_client and unblock_client actions. Once enabled, it becomes selectable under Settings → Security Responses → Blocking Method, and the blocking coordinator calls those actions directly when you block or unblock a device. Blocking actions emit device.blocked / device.unblocked events on success, and may use read-modify-write list directives for APIs that take an allow/deny list.
Built-in plugins
The shipped manifests in backend/plugins/builtin/ are the best real-world examples — read them alongside this guide.
| Plugin | ID | Capabilities |
|---|---|---|
| AdGuard Home | adguard-home | discovery, enrichment, dns, traffic, blocking |
| Pi-hole | pihole | discovery, enrichment, dns, traffic, blocking |
| TP-Link Omada | tplink-omada | discovery, presence, blocking, enrichment |
| Home Assistant | home-assistant | export, notification |
| OPNsense | opnsense | discovery, enrichment, blocking, firewall |
| pfSense | pfsense | discovery, enrichment, blocking, firewall |
Install & test
Author
Copy the template, set id & config.
Upload
Settings → Plugins → Upload.
Configure
Fill fields, Test Connection.
Enable
Toggle on; polling starts next cycle.
Use
Discovery feeds inventory; blocking appears as a method.
A note on safety
Because a plugin is just a manifest, it can only perform the network operations it declares, against the endpoints and credentials you enter. There is no arbitrary code execution inside the backend. That said, community plugins are not audited by the InSpectre project — review a manifest before uploading it, the same way you would any config that holds your credentials.