Permission Modes and Converted Plugin Security in OpenCode
The permission mode selected during Claude-to-OpenCode conversion determines whether the resulting plugin operates under least-privilege constraints, broad unrestricted access, or complete sandbox isolation.
When migrating Claude Code plugins to the OpenCode format using the EveryInc/compound-engineering-plugin toolchain, the --permissions flag controls how tool authorizations are encoded in the final bundle. This choice directly impacts converted plugin security, determining whether the runtime grants unrestricted filesystem access, denies all external operations, or enforces fine-grained resource constraints derived from the original plugin's command definitions.
How Permission Modes Work in the Conversion Pipeline
The conversion logic resides in src/converters/claude-to-opencode.ts, where the PermissionMode type (line 16) defines three strategies: "none", "broad", and "from-commands". The applyPermissions function (lines 94-191) implements the transformation logic that maps these modes to the OpenCode runtime configuration.
The none Mode: Complete Sandbox Isolation
When --permissions none is specified, applyPermissions returns early (lines 99-100), omitting both the permission and tools blocks from the generated opencode.json. The OpenCode runtime interprets this absence as implicit denial, preventing the plugin from invoking any external tools regardless of the host environment.
The broad Mode: Unrestricted Tool Access
The broad mode grants the converted plugin access to every available OpenCode tool. The implementation iterates through sourceTools (lines 101-117), setting config.tools[tool] = true and config.permission[tool] = "allow" for each entry (lines 145-149). This creates the least secure configuration, suitable only for trusted internal plugins or testing scenarios where functionality takes precedence over isolation.
The from-commands Mode: Least-Privilege Enforcement
The most secure option, from-commands, analyzes the allowedTools array in each Claude command definition. The parseToolSpec function (lines 399-409) handles tool specifications like read:/data/*, extracting both the tool name and glob patterns. The logic builds two structures:
- An
enabledSet containing all referenced tools - A
patternsMap associating tools with their whitelisted paths
The function then populates config.tools (lines 141-144) and constructs granular config.permission entries. Tools with patterns receive a pattern map with "*": "deny" defaults and explicit "allow" entries for each pattern (lines 150-158). Tools without patterns receive simple "allow" or "deny" strings (lines 159-162).
Special logic merges write and edit permissions (lines 169-189), ensuring that patterns granted to either tool automatically apply to both, maintaining functional parity with Claude's tool behavior.
Security Implications of Each Permission Mode
The choice of permission mode creates distinct security boundaries in the converted plugin:
| Mode | Runtime Configuration | Security Posture |
|---|---|---|
none |
No permission or tools blocks |
Deny-all sandbox. Plugin cannot access filesystem, network, or execution tools. Ideal for pure computation plugins. |
broad |
All tools enabled with "allow" |
Full access. Plugin can read, write, execute, and network without restriction. High risk if plugin code is compromised. |
from-commands |
Selective tool enablement with pattern constraints | Least privilege. Only tools and paths explicitly referenced in source commands are accessible. Limits blast radius of compromised commands. |
Pattern-Based Resource Confinement
The from-commands mode enables resource-level confinement impossible with broad. For example, a Claude command specifying allowedTools: ["read:/var/log/*.log"] generates:
{
"permission": {
"read": {
"*": "deny",
"/var/log/*.log": "allow"
}
},
"tools": {
"read": true
}
}
This configuration permits reading only /var/log/*.log files while denying access to all other paths, including sensitive locations like /etc/passwd or application source code.
Practical Conversion Example
Consider a Claude plugin that analyzes log files:
Source (log-analyzer.claude):
commands:
- name: analyze-logs
description: Analyze system logs
body: |
#!tool read:/var/log/*.log
cat /var/log/syslog | grep error
allowedTools:
- read:/var/log/*.log
Conversion command:
cai convert log-analyzer.claude --to opencode --permissions from-commands
Generated OpenCode configuration (opencode.json):
{
"config": {
"command": {
"analyze-logs": {
"description": "Analyze system logs",
"template": "#!tool read:/var/log/*.log\ncat /var/log/syslog | grep error"
}
},
"permission": {
"read": {
"*": "deny",
"/var/log/*.log": "allow"
},
"write": "deny",
"edit": "deny"
},
"tools": {
"read": true,
"write": false,
"edit": false
}
}
}
The conversion correctly identifies that only the read tool is required, restricts it to /var/log/*.log, and explicitly disables all other capabilities.
Summary
- Permission modes determine the security boundary of Claude plugins converted to OpenCode format.
nonecreates a complete sandbox denying all tool access, suitable for computation-only plugins.broadgrants unrestricted access to all OpenCode tools, creating the highest risk but maximum compatibility.from-commandsenforces least-privilege by analyzingallowedToolsarrays and generating pattern-based permissions that restrict tool access to specific resources.- The conversion logic in
src/converters/claude-to-opencode.tshandles pattern parsing viaparseToolSpecand mergeswrite/editpermissions to maintain functional parity with Claude's tool behavior.
Frequently Asked Questions
What is the default permission mode when converting Claude plugins?
The default permission mode is broad, which grants the converted plugin access to all available OpenCode tools. This is defined in the CLI validation logic within src/commands/install.ts (lines 12-13), where broad serves as the fallback when no specific mode is requested. While this ensures maximum compatibility, it provides the least security and should be changed to from-commands for production deployments.
How does the from-commands permission mode improve security?
The from-commands mode implements principle-of-least-privilege by analyzing the allowedTools array in each Claude command definition. The applyPermissions function in src/converters/claude-to-opencode.ts extracts specific tool requirements and resource patterns (e.g., read:/data/*.json), then generates a configuration that denies all tools by default while explicitly allowing only the required operations on specific paths. This prevents compromised commands from accessing sensitive files or unauthorized tools.
Can I change the permission mode after conversion?
No, the permission mode is baked into the generated OpenCode bundle during the conversion process. The applyPermissions function generates static config.permission and config.tools objects that are serialized into the output JSON. To change permissions, you must re-run the conversion command with a different --permissions flag (e.g., cai convert plugin.claude --to opencode --permissions from-commands) and redeploy the resulting bundle.
What happens if a tool pattern in allowedTools is malformed?
Malformed tool specifications are handled by the parseToolSpec function in src/converters/claude-to-opencode.ts (lines 399-409). If a pattern cannot be parsed (e.g., missing the colon separator or invalid glob syntax), the function typically treats the specification as a tool name without patterns or skips it entirely, depending on the error handling logic. This ensures that conversion does not fail silently but may result in broader or narrower permissions than intended. Always validate your allowedTools arrays before conversion to ensure patterns like read:/path/* are correctly formatted.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →