VayuPress Plugins
Normative contract:
SPEC.mdis the formal, versioned plugin interface specification (manifest schema, capability model, IPC protocol, hook events, lifecycle, conformance). This README is the friendly companion — where they differ,SPEC.mdwins.
VayuPress plugins are out-of-process programs the host launches inside a sandbox (seccomp-BPF syscall filtering, PID/IPC/mount namespaces, cgroups v2 limits, dropped capabilities, and explicit path/network allowlists — ADR-0056). A plugin can be written in any language; it only has to speak the line-oriented JSON IPC protocol below.
IPC protocol
The host writes one JSON request per line to the plugin's stdin and reads one JSON response per line from stdout.
Request (host → plugin)
{
"hook": "article.create",
"payload": { "slug": "hello-world", "id": "…", "content": "<p>…</p>" },
"correlation_id": "…",
"causation_id": "…",
"trace_id": "…",
"capabilities": { "allow_network": false, "allowed_read_paths": [], "allowed_write_paths": [] }
}
capabilities echoes what the host actually granted. The host enforces the
sandbox regardless, but well-behaved plugins also self-check (see the
webhook-notify example).
Response (plugin → host)
{ "ok": true, "log_lines": [ { "level": "info", "msg": "…" } ] }
On failure, return { "ok": false, "error": "…" }. log_lines are forwarded
through the host's structured logging pipeline tagged with the request's
correlation ID.
Manifest
Plugins are registered in host code with a sandbox.Manifest. Grant the least
privilege the plugin needs — every field defaults to "denied".
| Field | Meaning | Default |
|---|---|---|
Name |
Identifier used in logs/metrics | — |
Executable |
Absolute path to the plugin binary | — |
Args |
Startup arguments | none |
AllowedReadPaths |
Readable path prefixes | none (no FS read) |
AllowedWritePaths |
Writable path prefixes | none (no FS write) |
AllowNetwork |
Permit outbound network | false |
Timeout |
Max duration per invocation | DefaultPluginTimeout |
Registering
m := sandbox.Manifest{
Name: "wordcount",
Executable: "/opt/vayupress/plugins/wordcount",
Timeout: 2 * time.Second,
}
// poolSize worker processes; hook is the event the plugin subscribes to.
plugins.RegisterSubprocess(reg, m, "article.create", 2)
Plugins only fire when VAYU_PLUGINS_ENABLED=true.
Examples
| Example | Capabilities | Demonstrates |
|---|---|---|
wordcount |
none (fully isolated) | A pure, side-effect-free transform that reads the payload and emits a log line. |
webhook-notify |
AllowNetwork: true |
An outbound integration that self-checks its granted capability before calling out. |
seo-stamp |
AllowedReadPaths |
Settings access: reads an exported vayupress-theme.json bundle from an allowlisted read path (self-checking the grant first) and stamps SEO from site.author / head.keywords. |
frontmatter-guard |
none (fully isolated) | A pure governance check: validates editorial invariants and signals a failure via {"ok": false}, which the host records and quarantines on. |
trace-tap |
none (fully isolated) | An observability tap: reads the correlation_id / causation_id / trace_id the host passes on every call and echoes them in its log line, so plugin work stitches into the host's trace waterfall (GET /api/v1/admin/trace/{correlation_id}). |
Build an example:
go build -o /opt/vayupress/plugins/wordcount ./docs/plugins/examples/wordcount
Security model
- Least privilege: grant only the paths/network a plugin proves it needs.
- Host-enforced: the sandbox is enforced by the host kernel mechanisms, not by plugin cooperation. Capability self-checks are defense in depth, not the primary control.
- Resource-bounded: each invocation is time-boxed (
Timeout) and the pool size caps concurrency; crashes are counted and the worker is restarted up toMaxRestarts. - Quarantine: when the system enters
quarantinedmode, plugins are suspended (see the mode state machine, Ω5/Ω6).