Skip to content

Capabilities & security

The plugin security model is: deny by default, grant explicitly, intersect strictly, and never expose secrets or keys. This page unpacks each layer.

The capability set

Every plugin execution has an effective capability set — the exact permissions it holds at runtime. It is the intersection of four sources:

effective = manifest_request ∩ binding_grant ∩ actor_authorization ∩ agent_token
                                                                    ^ (only when the actor is an agent)

If any layer denies a capability, the plugin does not get it. A plugin cannot grant itself capabilities (REQ-PLUGIN-032).

Default (deny-by-default) set

read_metadata               = true
read_clear_paths            = []       # no cleartext file contents
read_protected_metadata     = false
read_protected_plaintext    = false    # never provided by default
write_working_copy          = false
write_plugin_cache          = false
network_access              = false
emit_audit                  = false
invoke_tovio_command        = false
propose_resolution          = false
propose_transform           = false
block_operation             = false

Field-by-field

Capability What it enables Guardrails
read_metadata Path/change-kind/target/actor metadata in the event payload. Always safe; on by default.
read_clear_paths Cleartext file contents for these glob patterns. Globs must match non-protected paths; protected paths are still redacted.
read_protected_metadata Redacted metadata for protected paths (path, change kind, policy id, hash where allowed). Never plaintext.
read_protected_plaintext Plaintext contents of protected paths. Requires all five conditions in §10 (manifest, binding, actor, sandbox trust, audit). Even then, MVP defaults to metadata-only, and it is the one capability --grant refuses outright (TVO-PLUGIN-013).
write_working_copy Propose changes to the working copy. Only applied if the binding grants transform-apply.
write_plugin_cache Write to the plugin's own sandbox-scoped cache directory. Isolated per plugin; not shared.
network_access Reserved for a future sandbox egress channel. Denied by default — and today it cannot be turned on. The sandbox exposes no network host functions at all, so a plugin whose effective grant asks for the network is refused before it runs (TVO-PLUGIN-012).
emit_audit Attach structured audit_metadata to the result. Required for policy-adapter/enforcing use.
invoke_tovio_command Call back into tovio from inside the sandbox. Heavily gated; never grants extra authority.
propose_resolution Emit a conflict Resolution (required for resolver type). Applied through the normal conflict path.
propose_transform Emit a transform proposal (required for transform type). Applied only if the binding grants transform-apply.
block_operation Cause an operation to fail-close when bound in enforcing mode. Without this, fail cannot block.

The empty set, on purpose

One event goes the other way and demands nothing: a plugin declaring transcript-parse must request an empty capability set, is refused at install if it requests even one, and is executed with a completely empty grant no matter what its binding approves.

That is not tidiness — it is the reason the event can exist at all. AI agent transcripts live outside the repository, next to ~/.aws, ~/.ssh, and ~/.config/gh, while every capability in the table above is repository-bounded. Honoring those files through the sandbox would need a host-filesystem read capability, which would escape the boundary that makes this whole set reasonable to reason about and, composed with network_access, would be a general exfiltration primitive. So TOVIO reads the file itself, first-party and in-process under your own privileges, and sandboxes only the parsing — a pure bytes-to-turns function that needs no reach whatsoever.

Protected content — the strictest rule

Protected files (encrypted per TOVIO's cryptographic permission model) are the sharpest edge of the capability model.

  • Protected plaintext MUST NOT be provided to plugins by default (REQ-PLUGIN-033).
  • Protected plaintext, if ever provided, requires all of (REQ-PLUGIN-034):
  • manifest declaration,
  • binding approval,
  • actor read authorization,
  • sandbox trust approval,
  • audit metadata recording.
  • Key material MUST NEVER be provided (REQ-PLUGIN-035). No manifest field, no policy override, no binding grant makes this possible.
  • Plugin output MUST NOT contain protected plaintext or key material in summary, findings, audit_metadata, stdout, or stderr (REQ-PLUGIN-036).
  • Protected paths default to redacted metadata (REQ-PLUGIN-037): {path, change_kind, policy, content_hash?}. Never plaintext.

The sandbox

The preferred runtime is WASM/WASI (REQ-PLUGIN-038); the shipped one is Wasmer with the Cranelift compiler, in the tovio-plugin-host crate. The runner:

  • enforces the manifest timeout_ms (REQ-PLUGIN-039) with two independent bounds — a deterministic instruction (fuel) budget, so a loop {} that never calls a host function still traps, and a wall-clock deadline,
  • bounds every plane a guest artifact can size: linear memory, table element counts per table and in aggregate, and the host-side compiled-module cache (REQ-PLUGIN-040). "The runtime does not support that limit" is not a licence to run a plane unbounded — a runtime that cannot enforce a guest-side bound is refused unless the runner enforces an equivalent host-side one, before instantiation,
  • denies network (REQ-PLUGIN-041) — by exposing no network host functions at all, so a plugin whose effective grant asks for network_access is refused rather than run,
  • denies arbitrary filesystem access (REQ-PLUGIN-042),
  • never exposes .tovio/ internals directly (REQ-PLUGIN-043),
  • runs plugins with a minimal environment — no inherited ambient secrets (REQ-PLUGIN-044),
  • deems native executable plugins not part of MVP default trust; if allowed later, only through explicit opt-in with clear trust warnings (REQ-PLUGIN-045).

Intersection with agent tokens

When an agent runs a plugin, the agent's capability token further constrains what the plugin sees (REQ-PLUGIN-031, REQ-PLUGIN-077). For example:

  • An agent with path_scope = ["src/**"] cannot cause a plugin invocation to see file contents outside src/**, even if the plugin's manifest requests broader read_clear_paths.
  • An agent without secret_clearance cannot cause a plugin to receive protected plaintext.
  • An agent cannot install, update, bind, or grant capabilities to plugins unless policy explicitly authorizes it (REQ-PLUGIN-075).

The rule is simple: a plugin never sees more than the invoking actor could see directly.

What is computed today

The two contract-level operands — manifest_request ∩ binding_grant — are computed in pure tovio-core and enforced on every execution. The last two operands narrow further and can only ever shrink the set, but the CLI edge does not yet resolve a real actor DID or thread an agent capability token into plugin input; a local run carries a fixed local actor marker. Because plugin input is metadata-only for protected paths regardless, the missing narrowing cannot widen what a plugin sees beyond the bound set.

Audit — what's recorded, what's not

Every enforcing plugin execution writes signed audit metadata (REQ-PLUGIN-058..060):

  • plugin id and version, event, mode, binding id,
  • actor, repository, target change/ref,
  • status, finding count, input hash, output hash, timestamp,
  • sandbox runtime,
  • the effective capability name set (plugin_granted_capabilities) — names only, never globs or plaintext,
  • a plugin_touches_protected boolean — whether the gated operation touched protected paths.

Audit records MUST NOT include protected plaintext or key material. Verifying with tovio audit verify covers the plugin fields as they ride the signed AuditEntry body.

Threat model — what plugins can and cannot do

Threat Mitigated by
Plugin reads a .env outside the repo Sandbox denies arbitrary filesystem access (REQ-PLUGIN-042).
Plugin exfiltrates data over the network The sandbox exposes no network host functions at all, so there is nothing to call (REQ-PLUGIN-041); a plugin needing egress is refused, not run.
Plugin reads protected file plaintext read_protected_plaintext requires the full §10 approval chain and is metadata-only in MVP.
Plugin leaks a secret in stderr/findings Output is treated as untrusted; protected/key content must not appear (REQ-PLUGIN-036).
Plugin escalates its own authority A plugin cannot grant itself capabilities (REQ-PLUGIN-032); the effective set is intersected.
Local malicious plugin gates Forge merge Forge does not trust local results without policy-approved attestation (REQ-PLUGIN-070).
A package drops a native binary MVP allows only wasm-wasi; native is opt-in with warnings (REQ-PLUGIN-045).
Malformed output silently passes Malformed output is treated as error and fails closed on protected operations.

Last reviewed September 9, 2026

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