Exclusive Access · Invitation Only

Building Plugins

Plugins extend a Stratum node with extra capabilities — custom protocol handlers, integrations, observability exporters, or specialised data-plane rules — without touching the core agent. This page is the practical guide to building, signing, and shipping your own plugin. To install one you already have, see Installing Plugins.

Every plugin is a signed .cenvero-plugin package. A node will only load a plugin signed with a developer certificate that Cenvero issued to you — so you don't manage any signing infrastructure yourself; you just sign with your own key and the certificate we give you. You build and sign packages with the cnvstrpack tool.

How plugins run (sandboxed, out-of-process)

A plugin is a signed executable, not a shared library. The agent runs it as a supervised, unprivileged child process — never inside the agent and never as root:

  • The agent verifies the package signature, your certificate, its scope, and that it has not been revoked _before_ it launches your executable. A bad package is rejected and the process is never started.
  • Your plugin then runs as a separate process under an unprivileged account of its own: a user and group id that no other plugin shares, which the agent assigns when the plugin is installed and keeps across restarts and upgrades. It runs in its own process group, with CPU / memory / open-file limits applied. It has no access to the agent's memory, private keys, configuration secrets, or root-owned files.
  • Plugins are isolated from one another. A plugin cannot read another plugin's files, see its processes, or reach its memory. It sees only its own processes, and holds no privileges of any kind.
  • The only thing your plugin can do is talk to the agent over a small JSON protocol on its stdin/stdout, calling the capability-scoped host API the agent grants it (see Capabilities below). Everything is deny-by-default: a host call you weren't granted is rejected and your plugin is terminated.

The sandbox is designed so an over-scoped or misbehaving plugin is contained: it runs unprivileged, capability-gated, and is restarted or killed when it misbehaves.

What your plugin can and cannot reach

These are the constraints to design against. They are properties of the sandbox, not settings you can relax, and each one has caught someone out:

Your plugin…What that means for your code
has no network of its ownIt cannot open sockets, call an external API, or reach the internet — it gets a private, empty network view. A plugin that phones home will fail. Anything network-facing must go through a granted host-API call.
cannot see other processesIt has a private process table: it cannot list, inspect, or signal the agent or anything else on the host.
cannot affect the host filesystemIt has a private filesystem view; changes it makes do not propagate back to the host.
may only make ordinary syscallsThe kernel refuses anything outside a fixed allowlist — mounting, loading modules, and similar privileged operations are denied even if something in the package tries.
cannot read its own packageIt starts in its install directory, but it can neither list that directory nor read the files in it other than its executable. Build anything it needs into the executable.
is restarted if it diesA crashed child is relaunched automatically on a backoff that widens with repeated failures, and resets once it stays up. Don't build your own restart loop.
comes back after the agent restartsIf it was enabled, the agent checks the package again — signature, certificate, scope, revocation, agent-version range — and starts it with the capabilities it was granted. A package that no longer passes is not started, and the operator sees why in plugin list.
must answer a hook promptlyEach hook invocation carries a deadline (5 seconds by default). A plugin that overruns it is killed, not merely timed out — do slow work in the background and answer quickly.

The practical shape of a plugin follows from the first row: it is an event handler that reacts and reports, not a service that goes and fetches. If your design needs to reach something off-box, that belongs in your own service, with the plugin reporting to it via the host API.

A signed plugin is code you have chosen to trust. The signature proves who wrote the package and that it has not been altered — it is an authenticity guarantee. The sandbox above is what limits what that code can do. Treat installing a plugin as the decision it is, and install only plugins from a developer you trust.

1. Get the packer

Download cnvstrpack from the Releases page (it's listed alongside the agent). It's a single file you run directly — no install needed.

cnvstrpack --help

2. Lay out your plugin

A plugin is a directory containing your entrypoint executable plus a manifest.json:

my-plugin/
├── manifest.json
└── entrypoint            # your compiled binary (any language) — must be executable
{
  "name": "my-plugin",
  "version": "1.0.0",
  "author": "Acme Corp",
  "description": "What this plugin does",
  "min_agent_version": "1.0.0",
  "entrypoint": "entrypoint",
  "requested_capabilities": ["log", "emit_event", "register_hook"],
  "hooks": ["on:flow.new"],
  "dependencies": []
}
  • entrypoint is the executable the agent launches as your sandboxed child (defaults to entrypoint if omitted). It can be written in any language; it just speaks the JSON protocol on stdin/stdout.
  • requested_capabilities is the list of host-API functions your plugin needs. You only get the intersection of what you request and what your certificate's scope allows — request the minimum (see Capabilities below).
  • hooks are the data-plane hook points your plugin registers for. You may only register hooks you declared here.
  • min_agent_version / max_agent_version (optional) bound the agent versions your plugin runs on. A node outside the range refuses to install it. Write them as plain versions like 1.4.0; a release candidate counts as the version it leads up to, so a 1.4.0-rc.2 agent meets a minimum of 1.4.0. Leave either out for no limit on that side.
  • dependencies (optional) lists other plugins, by name, that must be installed and enabled on the node before yours can be installed.

On startup your entrypoint must: (1) send a handshake advertising its name/version/API and declared capabilities, (2) handle hook_invoke messages within the per-hook deadline, and (3) make host-API calls only for capabilities it was granted.

The declarable hook points are:

HookFires on
on:flow.newA new connection is seen.
on:dns.queryA DNS query is received.
before:packet.forwardA packet is about to be forwarded.
on:alert.triggerAn alert fires.
Scope — hook delivery is not live yet. Packaging, signing, installing, launching, supervision, the host API and hook registration all work today, and a plugin that registers for a hook loads and runs normally. What is not wired yet is the last step: the agent's data-plane subsystems do not currently dispatch to registered hooks, so a handler for the points above will not be invoked on live traffic. Build and ship plugins against this interface by all means — it is stable and is what dispatch will use — but do not design a deployment that depends on a hook firing today. This page will say so plainly when that changes.

3. Become a developer + get your certificate

First, apply for developer access: on your account overview, choose Build plugins. Once Cenvero approves you, open your Developer portal at /account/developer.

Generate your signing keypair locally and keep the .key private:

cnvstrpack keygen -o mydev          # writes mydev.key (secret) and mydev.pub

In the Developer portal, paste the contents of mydev.pub, choose a scope (see Scopes below), and request a signing certificate — it is issued instantly. From the same page, download the two files you'll sign with: your signing certificate, saved as developer-<key id>.cert (the key id is the name the portal lists the certificate under), and Cenvero's certificate bundle, plugin-intermediate.cert. Your private key never leaves your machine — you only ever handle your own .key; Cenvero manages all signing infrastructure on our side.

4. Pack & sign

# Use the certificate file you downloaded: replace <key id> with its key id.
cnvstrpack pack ./my-plugin \
  --key mydev.key \
  --devcert developer-<key id>.cert \
  --intcert plugin-intermediate.cert \
  -o my-plugin-1.0.0.cenvero-plugin

cnvstrpack validates the manifest, archives the directory, signs it with your key, and embeds your developer certificate — producing a ready-to-ship my-plugin-1.0.0.cenvero-plugin.

Sanity-check it before sending:

cnvstrpack verify my-plugin-1.0.0.cenvero-plugin --pub mydev.pub

5. Ship it

Hand the .cenvero-plugin file to the node operator — they install it with one command (see Installing Plugins). The agent re-verifies the signature on load; a tampered or out-of-scope package is rejected with a clear error and never partially loaded.

Publishing to your store. If you have a Cenvero-hosted store, upload the package from your Developer portal. It is checked before it is listed, with the same checks a node runs at install: it must be a .cenvero-plugin package as cnvstrpack pack writes it (nothing else is accepted), its manifest must be valid, its signature must match, and your developer certificate must be current, correctly chained to Cenvero, and not revoked. The listed version is taken from the signed manifest, and a different version typed into the form is refused. Nodes still check the package again when they install it.

If your certificate is revoked, nodes stop and disable your plugin the next time they refresh the revocation list — within about six hours — and it cannot be re-enabled while the revocation stands.

Capabilities

Your plugin can only call host functions it was granted. You request capabilities in requested_capabilities; the agent grants the intersection of your request and what your certificate's scope permits — it can never exceed the certificate. Any call to an ungranted function is rejected and your plugin is terminated.

The fixed set of host-API capabilities:

CapabilityWhat it lets your plugin do
logWrite structured log lines to the agent log.
emit_eventPublish an event onto the agent's event bus (tagged with your plugin name).
get_config_valueRead a small, read-only subset of non-secret config values (e.g. node id, agent version). Never exposes API tokens or keys.
register_hookRegister for a hook point — but only one you also declared in hooks.
kv_get / kv_putA small key/value scratch store scoped to your plugin — you cannot read or write another plugin's keys.

Capability availability by scope:

  • any (public distribution): log, emit_event, get_config_value, register_hook.
  • license:<serial> / hardware:<id> (bound to a specific deployment): the above plus kv_get / kv_put.

Request the least you need: a smaller capability set is easier to get approved and reduces blast radius.

Scopes

When Cenvero issues your developer certificate, it carries a scope that decides which nodes will accept your plugins. Pick the one that matches how you distribute:

ScopeUse it for
anyA general-purpose plugin you distribute publicly — runs on any licensed node.
license:<serial>A customer- or enterprise-specific plugin — runs only on nodes activated with that license.
hardware:<id>A one-off plugin pinned to a single machine. Get the node's id with cenvero-str-ctl node info.

Scope is fixed at issuance; to change it, request a new certificate.

Dependencies & licensing

  • A node refuses to install your plugin until every plugin in its dependencies is installed and enabled there, and the refusal names the missing ones. It does not fetch them: tell operators in your documentation where to get each one. Dependencies are checked at install only, so removing a dependency later does not stop your plugin — handle its absence gracefully.
  • Plugin installs are blocked while a node's license is in the frozen state; already-running plugins keep going. See Licensing.

See also

↓ This page as JSON ↓ All documentation as JSON