Skip to content

Use a plugin

This page walks through installing a plugin, verifying its integrity, binding it to an event, running it, and reading the result. If you are writing a plugin, see Write a plugin; if a required plugin is blocking you, jump to When a plugin blocks you.

Local and Forge plugin execution are implemented

The contracts, CLI, WASM/WASI runtime, local bindings, publisher-signature trust, bounded HTTPS installation, and Forge server-side enforcement run today. The command shapes below describe the implemented local surface. There is no plugin registry.

flowchart LR
    I["1. Install<br/>tovio plugin install"] --> V["2. Verify<br/>tovio plugin verify"] --> B["3. Bind<br/>tovio plugin bind<br/>(or policy hook add)"] --> R["4. Run<br/>(event fires, or<br/>tovio plugin run)"] --> O{{"pass / warn /<br/>fail / error"}}

More diagrams for every step, including the blocked-land recovery flow: Workflows.

1. Install a plugin

A plugin package is a directory holding manifest.toml plus at most one runtime artifact (plugin.wasm), or a bare manifest file. TOVIO installs it from a local path or an HTTPS URL into this repository's store (.tovio/plugins/<id>/ — local, never synced). Every install validates the manifest before writing anything and checks the integrity metadata: a signed package's artifact hash, publisher signature, trust, and revocation state. A hash-only or local_dev package is stored as a development-policy install and is never reported as publisher-verified.

$ tovio plugin install ./license-check
✓ installed `com.example.license-check` v1.2.0 (check)  artifact: plugin.wasm
  source: local/offline · verification: sha256-only
  not a repo/org/Forge policy gate; bind it with `tovio plugin bind`

An HTTPS source ends in / or /manifest.toml and serves a sibling plugin.wasm. The download uses the system root certificates, is size-bounded, and refuses redirects, credentials, query strings, and archives. A remote package has to be publisher-signed by an identity you have trusted:

$ tovio plugin trust add did:key:1111111111111111111111111111111111111111111111111111111111111111
$ tovio plugin install https://plugins.example.com/license-check/

Inspect what installed:

$ tovio plugin list
$ tovio plugin show com.example.license-check
$ tovio plugin verify com.example.license-check     # re-verify integrity, signature, trust

2. Bind the plugin to an event

An installed plugin does nothing until it is bound. Local bindings live in .tovio/plugins/bindings.toml (scope = "local", inside the unsynced .tovio/ tree); both tovio plugin bind and tovio policy hook add write there. A local binding is never a repository, organization, or Forge policy gate (REQ-PLUGIN-025) — Forge-side required gates work differently and are described under Lifting an enforcing binding.

The binding's grant is deny-by-default: name each capability the plugin may use with --grant, and each clear-path glob it may read with --grant-read-path. The effective grant is always the intersection with what the manifest requests, so a binding can never widen a plugin.

$ tovio plugin bind com.example.license-check \
    --event pre-land \
    --mode enforcing \
    --required \
    --branches main --branches "release/**" \
    --grant emit_audit --grant block_operation \
    --grant-read-path Cargo.toml --grant-read-path Cargo.lock
✓ bound `com.example.license-check` → pre-land (enforcing, required), pinned to v1.2.0
  grant: emit_audit, block_operation · read-paths: Cargo.toml, Cargo.lock
  a LOCAL binding — not a repo/org/Forge policy gate (REQ-PLUGIN-025)

The resulting binding on disk (the [bindings.grant] table lists every capability field):

[[bindings]]
plugin_id = "com.example.license-check"
event     = "pre-land"
mode      = "enforcing"
scope     = "local"
required  = true
paths     = []
branches  = ["main", "release/**"]
actors    = []

[bindings.version]
exact = "1.2.0"

[bindings.grant]
read_metadata            = true
read_clear_paths         = ["Cargo.toml", "Cargo.lock"]
read_protected_metadata  = false
read_protected_plaintext = false
write_working_copy       = false
write_plugin_cache       = false
network_access           = false
emit_audit               = true
invoke_tovio_command     = false
propose_resolution       = false
propose_transform        = false
block_operation          = true

Bindings pin the installed version

tovio plugin bind always pins the binding to the version that is installed ([bindings.version] exact = "1.2.0"), so a later re-install to a different version does not silently re-scope it. An at_least floor exists only if you hand-edit the file; keep enforcing bindings pinned so CI, Forge, and every developer laptop run the same code for the same gate. See Bindings for scope and precedence rules.

For a policy-integrated enforcing binding (the hook surface for required checks):

$ tovio policy hook add pre-land com.example.license-check \
    --required \
    --paths "**" \
    --branches main

3. Run it

Bindings run automatically when the event fires:

$ tovio land feature/deps --into main

A pass prints nothing extra. A warning, fail, or error is echoed as plugin <id> (<mode>) → <status>: <summary> followed by its findings, and a required enforcing fail refuses the land with TVO-PLUGIN-009 (see below).

You can also invoke a bound plugin explicitly:

$ tovio plugin run com.example.license-check \
    --event pre-land \
    --dry-run \
    --json

run needs a binding for the event — the mode, required flag, and capability grant all come from it — and it re-verifies the artifact before executing. --dry-run reports what would block without gating anything, and --json gives you the structured result (status, summary, findings, sandbox runtime, and whether it blocked or would block).

plugin run sends an empty payload

It builds a schema-valid input envelope with no changed paths and no target — enough to prove the module loads, speaks the protocol under its real grant, and returns a well-formed result. It is not a content check. A guest that scans changed paths sees none, and one that fails closed on an undecodable payload (as the SDK's harness does) reports status = "error". To exercise a plugin over real content, drive its actual event — tovio commit for pre-snapshot, tovio land for pre-land.

See the CLI reference for every flag.

When a plugin blocks you

Enforcing plugins produce a structured, explainable block. It always tells you which plugin, which binding required it, what failed, what to do next, and whether override is possible.

✗ Land blocked by required plugin: com.example.license-check

  A REQUIRED enforcing `pre-land` plugin blocked the land (sandboxed-lifecycle-plugins.md §14/§57,
  REQ-PLUGIN-054/055/065). The operation was gated BEFORE it took effect — nothing changed.

  Plugin:    com.example.license-check v1.2.0
  Binding:   com.example.license-check@pre-land (LOCAL, enforcing, required)
  Result:    fail (enforcing fail)
  Why:       One dependency violates repository license policy.
    [error] Dependency xyz uses a prohibited license. (Cargo.toml)

  This was a LOCAL enforcing binding — not a CI-side or Forge-side gate. Override IS possible for the
  repo owner (this is a local binding, not org policy).

  Fix what the plugin flagged (see the findings above), then re-run `tovio land`
  Or inspect the enforcing binding that required it:
    tovio plugin bindings
  Owner override: drop the local enforcing binding, or re-bind it advisory:
    tovio plugin unbind <plugin-id> --event pre-land

  [TVO-PLUGIN-009]

Common resolutions, by cause:

Cause What to do
Plugin returned fail Fix what the finding describes. code and remediation guide you.
Plugin returned error The plugin's own summary says why — that is all you get; the sandbox captures guest stderr for bounding and never surfaces it. tovio plugin doctor checks the store.
Plugin timed out (TVO-PLUGIN-007) Retry once; if consistent, raise timeout_ms in the manifest or file a plugin bug.
Plugin unavailable (TVO-PLUGIN-010) Reinstall or update (or rebuild tovio with plugins-wasm if the sandbox is missing); the repo owner can drop or re-bind the local binding advisory meanwhile.
Integrity check failed (TVO-PLUGIN-003) Do not override. Report to the plugin publisher — the artifact does not match its hash or signature.
Capability denied (TVO-PLUGIN-005) The manifest asks for more than the binding grants. Update the binding or the manifest.

See the full error catalog.

Lifting an enforcing binding

Local advisory bindings are trivially overridden — you already have the warning, and the operation already ran.

Enforcing bindings have no override flag on tovio land or tovio commit. A local enforcing binding belongs to the repository owner, who lifts it by dropping the binding or re-binding it advisory:

$ tovio plugin unbind com.example.license-check --event pre-land
$ tovio plugin bind com.example.license-check --event pre-land --mode advisory

The block itself is already in the audit trail as a signed PluginBlocked entry, so the sequence "blocked, then unbound, then landed" is visible to anyone who verifies the chain.

On the Forge, a required plugin is a plugin:<id> check on the proposal. The land gate accepts only a server-recorded, revision-stamped success posted by a policy-approved attester — a local pass is never trusted (TVO-PLUGIN-015), and the org plugin policy decides which plugins may back an enforcing binding at all (TVO-PLUGIN-014). Lifting such a gate is a forge-admin's change to the proposal's required set or to the served plugin policy, not a client-side flag.

Audit — what gets recorded

An enforcing execution that blocks writes a signed PluginBlocked entry to the acting identity's audit chain, carrying:

  • the plugin id, version, and the binding id that required it (<plugin-id>@<event>),
  • the acting identity, the binding mode, and the target change or ref,
  • the blocking status, the finding count, the input and output hashes, the timestamp, and the sandbox runtime label,
  • the effective capability name set granted (names only — never globs, content, or plaintext),
  • whether the gated operation touched protected paths (metadata only; plaintext is never marshalled).

A pass writes nothing — the entry exists to record the block, not the run. A non-blocking warning writes nothing either, so it is printed to stderr even under --json and --quiet, which is the only record it gets. A Simple repo has no signing identity and therefore no chain: the block still gates the operation, but nothing is written.

Verify the chain with tovio audit verify.

Where to go next

Last reviewed September 9, 2026

Suggest an improvement to this page Not for security reports — see disclosure