Plugins declare type and capabilities. Unknown capabilities are rejected.
Current capability names:
audit:readhttp:egresskv:readkv:writelog:writemonitor:readmonitor:adminnode:readnode:adminnotify:sendstatic:readstatic:writetask:readtask:runtunnel:adminworker:routenetwork:plannetwork:applyddns:admin
Host API calls are not direct server handles. They go through the core broker,
which checks the verified manifest's capability list on every call and records
allow/deny host-call events. http:egress is only a permission to ask the
server-owned HTTP host to dial; that host must still apply the outbound SSRF and
egress guard.
Plugin bundle bytes are not stored inside the server state database. They live
under LATTICE_PLUGIN_DIR on the server host or container, for example:
/plugins/
example.plugin/
manifest.json
artifactThe server scans that directory, verifies manifest/artifact integrity, and then
stores lifecycle metadata in server state: status, verified identity,
capabilities, digest, timestamps, and runtime health. Plugin-specific durable
data should use a plugin-owned KV namespace such as plugin:<id>.
In Docker deployments the recommended mount is read-only:
volumes:
- ./plugins:/plugins:roChanging bundle bytes should be a release operation, not a dashboard write.
LatticeNet/lattice-plugin-index is the marketplace foundation. It currently
contains a static plugins.json format plus a dependency-free validator. It is
not yet a remote install mechanism.
Operators should treat marketplace entries as install candidates only. A future server-side install flow must still verify:
- index signature;
- publisher trust policy;
- plugin manifest signature;
- artifact SHA-256 digest;
- declared capabilities and risk tier;
- runner sandbox compatibility.
Until real runners and sandbox policy are mature, the dashboard should display marketplace metadata but should not install or execute remote community bundles automatically.
The server now keeps a metadata-only lifecycle record for each bundle that passes manifest signature and artifact digest verification:
verified -> installed -> active -> disabled -> active
\-> disabledThe lifecycle API is deliberately conservative:
GET /api/plugins/lifecyclerequiresplugin:adminand returns plugin identity, capabilities, artifact digest, availability, status, and timestamps.- The response does not include the local
bundle_path; filesystem layout stays server-private. POST /api/plugins/lifecyclerequiresplugin:adminand accepts{ "id": "...", "status": "installed|active|disabled" }.- Installing or activating requires the bundle to be present in the current
verified loader set (
available:true); stale records can be disabled, not activated. - Invalid transitions are rejected and status changes are audited as
plugin.status. - Activating a plugin arms the server runtime manager and exposes
runtime.state(currentlyarmed,stopped, orfailed) andruntime.runnerin the lifecycle response. Disabling stops that in-memory runtime handle. - The current default runner is
noop: it receives a capability-scoped broker and a deadline-bearing context, then reports health without executing artifact code. - Lifecycle/runtime transitions do not execute plugin artifact code yet. Subprocess/wasm isolation, rate limits, and concrete runner implementations remain Phase B work.
System plugins are trusted built-ins. Planned system plugins:
- nft guard
- WireGuard config renderer
- nginx virtual host renderer
- cloudflared tunnel supervisor
- sing-box/xray config renderer
- Sub-Store process supervisor and reverse proxy
- SSH login alert collector
- notification fanout
The bootstrap Worker runtime is intentionally conservative. It supports template rendering and KV interpolation:
hello {{path}} {{kv:default/message}}Blocked source primitives include fetch(, require(, process.env, exec(,
and os.. A future JS runtime should remain capability-based and should not gain
filesystem, process, or arbitrary network access by default.