# Permission Modes and Converted Plugin Security in OpenCode

> Understand how permission modes affect converted plugin security in OpenCode. Explore least-privilege, unrestricted access, and sandbox isolation for secure development.

- Repository: [Every/compound-engineering-plugin](https://github.com/everyinc/compound-engineering-plugin)
- Tags: security
- Published: 2026-02-16

---

**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`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/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`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/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 `enabled` Set containing all referenced tools
- A `patterns` Map 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:

```json
{
  "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`):**

```yaml
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:**

```bash
cai convert log-analyzer.claude --to opencode --permissions from-commands

```

**Generated OpenCode configuration ([`opencode.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/opencode.json)):**

```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.
- **`none`** creates a complete sandbox denying all tool access, suitable for computation-only plugins.
- **`broad`** grants unrestricted access to all OpenCode tools, creating the highest risk but maximum compatibility.
- **`from-commands`** enforces least-privilege by analyzing `allowedTools` arrays and generating pattern-based permissions that restrict tool access to specific resources.
- The conversion logic in [`src/converters/claude-to-opencode.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-opencode.ts) handles pattern parsing via `parseToolSpec` and merges `write`/`edit` permissions 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`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/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`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/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`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/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.