{
    "product": "Cenvero Stratum",
    "generated_at": "2026-08-03T07:16:16+00:00",
    "format": "cenvero-docs-v1",
    "document_count": 1,
    "documents": [
        {
            "slug": "plugins/overview",
            "title": "Building Plugins",
            "category": "Plugins",
            "url": "https://stratum.cenvero.com/docs/plugins/overview",
            "headings": [
                {
                    "level": 1,
                    "text": "Building Plugins"
                },
                {
                    "level": 2,
                    "text": "How plugins run (sandboxed, out-of-process)"
                },
                {
                    "level": 3,
                    "text": "What your plugin can and cannot reach"
                },
                {
                    "level": 2,
                    "text": "1. Get the packer"
                },
                {
                    "level": 2,
                    "text": "2. Lay out your plugin"
                },
                {
                    "level": 2,
                    "text": "3. Become a developer + get your certificate"
                },
                {
                    "level": 2,
                    "text": "4. Pack & sign"
                },
                {
                    "level": 2,
                    "text": "5. Ship it"
                },
                {
                    "level": 2,
                    "text": "Capabilities"
                },
                {
                    "level": 2,
                    "text": "Scopes"
                },
                {
                    "level": 2,
                    "text": "Dependencies & licensing"
                },
                {
                    "level": 2,
                    "text": "See also"
                }
            ],
            "word_count": 1551,
            "markdown": "# Building Plugins\n\nPlugins 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](/docs/plugins/installing).\n\nEvery 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.\n\n## How plugins run (sandboxed, out-of-process)\n\nA 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:\n\n- 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.\n- Your plugin then runs as a **separate process under a dedicated unprivileged user** (`cenvero-str-plugin`), 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**.\n- 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.\n\nThe 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.\n\n### What your plugin can and cannot reach\n\nThese are the constraints to design against. They are properties of the sandbox, not settings you can relax, and each one has caught someone out:\n\n| Your plugin… | What that means for your code |\n|---|---|\n| **has no network of its own** | It 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. |\n| **cannot see other processes** | It has a private process table: it cannot list, inspect, or signal the agent or anything else on the host. |\n| **cannot affect the host filesystem** | It has a private filesystem view; changes it makes do not propagate back to the host. |\n| **may only make ordinary syscalls** | The kernel refuses anything outside a fixed allowlist — mounting, loading modules, and similar privileged operations are denied even if something in the package tries. |\n| **is restarted if it dies** | A 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. |\n| **must answer a hook promptly** | Each 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. |\n\nThe 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.\n\n> **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.\n\n## 1. Get the packer\n\nDownload `cnvstrpack` from the [Releases](/releases) page (it's listed alongside the agent). It's a single self-contained binary — no install needed.\n\n```bash\ncnvstrpack --help\n```\n\n## 2. Lay out your plugin\n\nA plugin is a directory containing your **entrypoint executable** plus a `manifest.json`:\n\n```\nmy-plugin/\n├── manifest.json\n└── entrypoint            # your compiled binary (any language) — must be executable\n```\n\n```json\n{\n  \"name\": \"my-plugin\",\n  \"version\": \"1.0.0\",\n  \"author\": \"Acme Corp\",\n  \"description\": \"What this plugin does\",\n  \"min_agent_version\": \"1.0.0\",\n  \"entrypoint\": \"entrypoint\",\n  \"requested_capabilities\": [\"log\", \"emit_event\", \"register_hook\"],\n  \"hooks\": [\"on:flow.new\"],\n  \"dependencies\": []\n}\n```\n\n- **`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.\n- **`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).\n- **`hooks`** are the data-plane hook points your plugin registers for. You may only register hooks you declared here.\n\nOn 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.\n\nThe declarable hook points are:\n\n| Hook | Fires on |\n|------|----------|\n| `on:flow.new` | A new connection is seen. |\n| `on:dns.query` | A DNS query is received. |\n| `before:packet.forward` | A packet is about to be forwarded. |\n| `on:alert.trigger` | An alert fires. |\n\n> **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.\n\n## 3. Become a developer + get your certificate\n\nFirst, **apply for developer access**: in your account, choose *Become a developer*. Once an admin approves you, open your **Developer portal** at `/account/developer`.\n\nGenerate your signing keypair locally and keep the `.key` private:\n\n```bash\ncnvstrpack keygen -o mydev          # writes mydev.key (secret) and mydev.pub\n```\n\nIn the Developer portal, paste the contents of **`mydev.pub`**, choose a **scope** (see **Scopes** below), and request a certificate — it is **issued instantly**. From the same page, download the two certificate files you'll sign with: your **`developer.cert`** and the accompanying **`plugin-intermediate.cert`** bundle. Your private key never leaves your machine — you only ever handle your own `.key`; Cenvero manages all signing infrastructure on our side.\n\n## 4. Pack & sign\n\n```bash\ncnvstrpack pack ./my-plugin \\\n  --key mydev.key \\\n  --devcert developer.cert \\\n  --intcert plugin-intermediate.cert \\\n  -o my-plugin-1.0.0.cenvero-plugin\n```\n\n`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`.\n\nSanity-check it before sending:\n\n```bash\ncnvstrpack verify my-plugin-1.0.0.cenvero-plugin --pub mydev.pub\n```\n\n## 5. Ship it\n\nHand the `.cenvero-plugin` file to the node operator — they install it with one command (see [Installing Plugins](/docs/plugins/installing)). 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.\n\n## Capabilities\n\nYour 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.\n\nThe fixed set of host-API capabilities:\n\n| Capability | What it lets your plugin do |\n|------------|-----------------------------|\n| `log` | Write structured log lines to the agent log. |\n| `emit_event` | Publish an event onto the agent's event bus (tagged with your plugin name). |\n| `get_config_value` | Read a small, **read-only** subset of non-secret config values (e.g. node id, agent version). Never exposes API tokens or keys. |\n| `register_hook` | Register for a hook point — but only one you also declared in `hooks`. |\n| `kv_get` / `kv_put` | A small key/value scratch store **scoped to your plugin** — you cannot read or write another plugin's keys. |\n\nCapability availability by scope:\n\n- **`any`** (public distribution): `log`, `emit_event`, `get_config_value`, `register_hook`.\n- **`license:<serial>` / `hardware:<id>`** (bound to a specific deployment): the above **plus** `kv_get` / `kv_put`.\n\nRequest the least you need: a smaller capability set is easier to get approved and reduces blast radius.\n\n## Scopes\n\nWhen 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:\n\n| Scope | Use it for |\n|-------|------------|\n| `any` | A general-purpose plugin you distribute publicly — runs on any licensed node. |\n| `license:<serial>` | A customer- or enterprise-specific plugin — runs only on nodes activated with that license. |\n| `hardware:<id>` | A one-off plugin pinned to a single machine. Get the node's id with `cenvero-str-ctl node info`. |\n\nScope is fixed at issuance; to change it, request a new certificate.\n\n## Dependencies & licensing\n\n- If your `manifest.json` declares dependencies, the agent installs in dependency order automatically and rejects circular or unsatisfiable version constraints at install time — you don't manage load order.\n- Plugin **installs** are blocked while a node's license is in the frozen state; already-running plugins keep going. See [Licensing](/docs/licensing).\n\n## See also\n\n- [Installing Plugins](/docs/plugins/installing) — install, list, verify, and remove on a node.\n- [CLI Reference](/docs/cli) — the `plugin` command group.\n"
        }
    ]
}