RFD 077: Plugin Configuration and Trust Policy
- Status: Discussion
- Category: Design
- Authors: Jean Mertz git@jeanmertz.com
- Date: 2026-04-07
- Requires: RFD 072
Summary
This RFD defines the configuration surface for JP's plugin system. It introduces a [plugins] section in AppConfig that controls plugin installation, execution policy, binary checksum pinning, and per-plugin options. The config system replaces standalone approval files as the single source of truth for plugin trust decisions, and it participates in JP's existing config inheritance chain (global → workspace → local → CLI overrides).
Motivation
RFD 072 defines the command plugin system: standalone binaries that communicate with JP over a JSON-lines protocol. Phase 3 of that RFD adds a plugin registry and auto-install flow. But RFD 072 leaves open how users control plugin behavior:
Trust decisions are ad-hoc. Without config integration, plugin approval must be tracked in a separate JSON file that doesn't benefit from config inheritance. A user who trusts a plugin globally has no way to deny it in a specific workspace, or vice versa.
No checksum pinning. The registry provides checksums for download verification, but there is no mechanism for a user to pin a specific binary and refuse to run a changed one. This matters for supply-chain security: if a registry-hosted binary is compromised and re-published with a new checksum, users who haven't pinned are silently exposed.
No per-plugin options. Plugins like
jp-serveneed configuration (bind address, port) that is specific to the plugin. Without a config path, these settings must be passed as CLI arguments every time, or the plugin must invent its own config file.Plugin types aren't distinguished. The registry currently assumes all plugins are command plugins. When wasm plugins (RFD 016) arrive, the registry and config need a way to distinguish between plugin kinds.
This RFD addresses all four gaps by defining the plugin config model and its interaction with the dispatch pipeline.
Design
Plugin Kind Taxonomy
The registry introduces a kind field on each plugin entry:
{
"version": 1,
"plugins": {
"serve": {
"kind": "command",
"description": "Read-only web UI for conversations",
"official": true,
"binaries": { ... }
}
}
}kind defaults to "command" when absent, so existing registry entries remain valid. The dispatch pipeline filters on kind and ignores entries with unrecognized values, allowing future plugin types (e.g. wasm) to be added to the registry without breaking older JP versions.
The PluginKind enum in jp_plugin::registry:
pub enum PluginKind {
Command, // standalone binary, RFD 072 protocol
// Wasm, // future: RFD 016
}Configuration Schema
The [plugins] section is added to AppConfig:
[plugins]
# Auto-install official registry plugins on first invocation.
auto_install = true # default: true
# Grace period (seconds) before SIGKILL after sending Shutdown.
shutdown_timeout_secs = 5 # default: 5
# Per-plugin configuration, keyed by plugin name.
[plugins.command.serve]
install = true # override auto_install per plugin
run = "unattended" # execution policyplugins.auto_install
Controls whether official plugins are automatically downloaded and installed when first invoked. When false, the user must run jp plugin install <name> explicitly. Third-party (non-official) plugins are never auto-installed regardless of this setting unless their per-plugin install field is true.
plugins.shutdown_timeout_secs
The grace period between sending Shutdown over the protocol and sending SIGKILL to the child process. Applies to all command plugins.
Per-Plugin Configuration
Each entry under plugins.command.<name> configures a specific command plugin:
[plugins.command.serve]
install = true
run = "unattended"
[plugins.command.serve.checksum]
algorithm = "sha256"
value = "e3b0c44298fc1c149afbf4c8996fb924..."
[plugins.command.serve.options]
web.port = 3141
web.host = "127.0.0.1"install (Option<bool>) — Whether to auto-install this plugin from the registry. Overrides the global auto_install for this specific plugin. When absent, falls back to the global setting (for official plugins) or false (for third-party plugins).
run (RunPolicy) — Execution policy:
| Value | Behavior |
|---|---|
ask | Prompt the user before running. Default for third-party |
| plugins and PATH-discovered plugins. | |
unattended | Run without prompting. Default for official registry |
| plugins. | |
deny | Never run. JP exits with an error if the plugin is invoked. |
The run policy applies at every execution point: installed plugins, registry auto-install, and PATH-discovered plugins. It replaces the standalone approval file system from the initial Phase 3 implementation.
checksum — Pins the binary to a specific hash. When set, JP computes the binary's checksum before execution and refuses to run if it doesn't match. This catches two scenarios:
- A registry-hosted binary is re-published with different content (supply-chain compromise).
- A PATH-discovered binary is replaced or modified.
The checksum config reuses the existing ChecksumConfig type from MCP server configuration:
pub struct ChecksumConfig {
pub algorithm: AlgorithmConfig, // sha256 (default) | sha1
pub value: String, // hex-encoded digest
}When a checksum mismatch occurs, JP prints the expected and actual values and tells the user which config key to update. This makes it easy to intentionally accept a new binary after reviewing the change.
options (Option<serde_json::Value>) — An opaque JSON value passed to the plugin in the config field of the init message. JP does not validate the contents — the plugin is responsible for parsing and error reporting. This follows the same pattern as tool options (RFD 042).
Example: the serve plugin reads options.web.port and options.web.host from its init config to configure its HTTP listener.
Config Inheritance
Plugin config participates in JP's standard config inheritance chain:
- Global config (
$XDG_CONFIG_HOME/jp/config.toml): user-wide defaults. Trust a plugin globally, set default options. - Workspace config (
.jp/config.toml): per-project overrides. Deny a plugin in a sensitive workspace, or change its options. - Local config (
.jp.toml): directory-scoped overrides. - CLI flags (
--cfg plugins.command.serve.run=deny): one-shot overrides.
This means a user can set run = "unattended" globally and override it to run = "deny" in a workspace that handles sensitive data.
Plugin Management Without a Workspace
Plugin management commands (jp plugin list, jp plugin install, jp plugin update) run before workspace loading. They work from any directory, including outside of any JP workspace. This is intentional: plugin binaries are user-global (installed to $XDG_DATA_HOME/jp/plugins/), not workspace-local, so requiring jp init before installing a plugin would be unnecessary friction.
The trade-off is that management commands only see the user-global config layer. Workspace and local config layers are unavailable. This means a run = "deny" set in a workspace config is enforced during jp <plugin> dispatch (which runs inside a workspace) but not during jp plugin install (which runs outside). This is acceptable: deny means "don't run this plugin here," not "don't download it."
Dispatch Integration
The dispatch pipeline in resolve_plugin_binary reads AppConfig::plugins to make decisions at each resolution step:
- Deny check: If
plugins.command.<name>.run = "deny", abort immediately. - Installed plugins: Verify pinned checksum if configured.
- Registry install: Respect
installandrunpolicy. Official plugins withauto_install = trueandrun = "unattended"install silently. Third-party plugins withrun = "ask"prompt interactively. - PATH plugins: Verify pinned checksum. Respect
runpolicy. Default isask, which prompts once per invocation (not persisted — userun = "unattended"in config for permanent trust). - Post-install verification: After downloading from the registry, verify against the pinned checksum if one is configured. This catches the scenario where the registry checksum passes (download is intact) but differs from the user's pinned value (binary changed since the user last reviewed it).
Future: Plugin Options Schema
The current options field is an opaque Value — JP passes it through without validation. A future extension could have plugins declare their options schema via the describe protocol:
{
"type": "describe",
"name": "serve",
"options_schema": {
"type": "object",
"properties": {
"web": {
"type": "object",
"properties": {
"port": { "type": "integer", "default": 3141 },
"host": { "type": "string", "default": "127.0.0.1" }
}
}
}
}
}This would enable config validation, jp config show integration, and help text generation. It is explicitly deferred — the opaque approach is sufficient for the initial plugin set and avoids coupling the config system to plugin internals.
Drawbacks
No version constraints. The config has no
versionfield for constraining which plugin version to install. This requires the registry to carry version metadata and JP to implement a resolution algorithm. The checksum pin provides a weaker but simpler guarantee: "run exactly this binary, or nothing."Opaque options are unvalidated. A typo in
options.web.prrtis silently ignored. The plugin may or may not report the error. This is the same tradeoff as tool options (RFD 042) and is acceptable until the options schema protocol is implemented.No checksum auto-population. Users must manually obtain the checksum value (e.g., from the registry or by running
shasum) and paste it into config. A futurejp plugin pin <name>command could automate this.
Alternatives
Standalone approval file
The initial Phase 3 implementation stored plugin approvals in a separate $XDG_DATA_HOME/jp/plugin-approvals.json file, tracking binary path and checksum per approved plugin.
Rejected because:
- Does not participate in config inheritance. Can't deny a plugin per-workspace.
- Separate persistence mechanism to maintain alongside the config system.
- No support for execution policy beyond binary approve/deny.
- Mixes concerns: trust decisions (should this run?) and identity assertions (is this the right binary?) should be expressible independently.
Environment variables for plugin options
Pass plugin options via environment variables instead of the config file (e.g., JP_PLUGIN_SERVE_PORT=3141).
Rejected because:
- Doesn't compose with config inheritance.
- Awkward for nested options (port is fine, but complex structures don't map well to env vars).
- The plugin protocol already sends the full config in the
initmessage, so the transport is free.
Typed plugin config sections
Define a typed struct per plugin in jp_config (e.g., ServePluginConfig with port: u16 and host: String).
Rejected for the same reasons as in RFD 042: it couples the config crate to plugin internals and doesn't scale to third-party plugins. The opaque Value approach is the right starting point, with the schema protocol as the future validation layer.
Non-Goals
Plugin version management. Semantic version constraints, update channels, and rollback are package-manager features that are out of scope. Checksum pinning covers the security use case.
Options schema validation. Validating plugin options against a plugin-declared schema is deferred to a future RFD extending the
describeprotocol.Wasm plugin configuration. RFD 016 defines the wasm plugin system. When wasm plugins need configuration, the
plugins.wasm.<name>namespace is reserved but its schema is undefined here.
Risks and Open Questions
Checksum rotation workflow. When a plugin is legitimately updated, users with a pinned checksum must manually update the value. If many users pin checksums, plugin authors need a way to communicate new checksums (release notes, a
jp plugin pin --updatecommand, etc.). The UX for this needs attention.Options forwarding path. The
initmessage sends the fullAppConfigas JSON. Plugin-specific options currently live atplugins.command.<name>.optionsin this blob. Plugins must navigate this path to find their options. A cleaner approach might extract the plugin's options and send them in a dedicatedoptionsfield in theinitmessage. This is a protocol change that should be coordinated with RFD 072.Config scope during management commands. As described in the "Plugin Management Without a Workspace" section, management commands only see user-global config. If a future use case requires workspace-aware management (e.g. workspace-scoped plugin lists), the management commands would need to optionally load the workspace when one is available.
Implementation Plan
Phase 1: Config types and dispatch integration
- Add
PluginsConfig,CommandPluginConfig, andRunPolicytojp_config. - Wire
pluginsintoAppConfigwith fullAssignKeyValue/PartialConfigDelta/ToPartialsupport. - Reuse
ChecksumConfigfrom MCP for checksum pinning. - Update
resolve_plugin_binaryto read config for policy decisions. - Remove the standalone approval file system.
- Can be merged independently.
Phase 2: Options forwarding
- Extract
plugins.command.<name>.optionsfrom the config and include it in the plugin'sinitmessage in a well-known location. - Document the options path for plugin authors.
- Depends on Phase 1.
Phase 3: Plugin kind in registry
- Add
kindfield toRegistryPlugin(defaulting to"command"). - Filter on
kindin the dispatch pipeline. - Update
jp plugin listto show plugin kind. - Can be merged independently of Phase 1.