|
| 1 | +# Permission Modes Research Report |
| 2 | + |
| 3 | +> Generated from deep research across 7 reference repos + dcg-core analysis |
| 4 | +> Date: 2026-05-30 |
| 5 | +> Branch: experiment/dcg-permission-modes |
| 6 | +
|
| 7 | +--- |
| 8 | + |
| 9 | +## 1. Research Scope |
| 10 | + |
| 11 | +| Repo | Stack | Permission Relevance | |
| 12 | +|------|-------|---------------------| |
| 13 | +| claude-code | TypeScript / Bun | ⭐⭐⭐ Direct ancestor, 6-mode pipeline | |
| 14 | +| codex (OpenAI) | Rust | ⭐⭐⭐ OS sandboxing, exec policy engine | |
| 15 | +| pi-agent-rust | Rust 2024 | ⭐⭐⭐ Enum-driven policy, O(1) snapshot | |
| 16 | +| opencode | TypeScript / Bun | ⭐⭐ Layered ruleset, wildcard matching | |
| 17 | +| oh-my-openagent | TypeScript | ⭐⭐ Hook-chain, CC mode compat | |
| 18 | +| oh-my-pi | TypeScript + Rust | ⭐⭐ 3-tier/3-mode, ACP bridge | |
| 19 | +| codebuff | TypeScript | ⭐ Tool whitelist, propose-vs-execute | |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## 2. Cross-Repo Permission Mode Comparison |
| 24 | + |
| 25 | +### 2.1 Mode Enums |
| 26 | + |
| 27 | +| Repo | Modes | Count | |
| 28 | +|------|-------|-------| |
| 29 | +| **claude-code** | `plan`, `default`, `acceptEdits`, `dontAsk`, `auto`, `bypassPermissions` | 6 | |
| 30 | +| **codex** | `UnlessTrusted`, `OnFailure`, `OnRequest`, `Granular(Config)`, `Never` | 5 | |
| 31 | +| **pi-agent-rust** | `Strict`, `Prompt`, `Permissive` (extension policy) | 3 | |
| 32 | +| **opencode** | No mode enum; `allow/deny/ask` per-tool rules + `--dangerously-skip-permissions` | N/A | |
| 33 | +| **oh-my-pi** | `always-ask`, `write`, `yolo` (approval mode) | 3 | |
| 34 | +| **oh-my-openagent** | `default`, `plan`, `acceptEdits`, `bypassPermissions` (CC compat) | 4 | |
| 35 | +| **codebuff** | No user-facing mode enum | 0 | |
| 36 | + |
| 37 | +### 2.2 Decision Outcomes |
| 38 | + |
| 39 | +All repos converge on **tri-state decision**: |
| 40 | + |
| 41 | +| Decision | claude-code | codex | pi-agent-rust | opencode | oh-my-pi | |
| 42 | +|----------|-------------|-------|---------------|----------|----------| |
| 43 | +| Allow | ✅ | ✅ Allow | ✅ | ✅ | ✅ | |
| 44 | +| Prompt/Ask | ✅ | ✅ Prompt | ✅ | ✅ | ✅ | |
| 45 | +| Deny/Forbidden | ✅ | ✅ Forbidden | ✅ | ✅ | ✅ | |
| 46 | + |
| 47 | +### 2.3 Tool Classification |
| 48 | + |
| 49 | +| Repo | Classification Method | Tiers | |
| 50 | +|------|----------------------|-------| |
| 51 | +| **claude-code** | Per-tool `checkPermissions()` + effect-based + dangerous patterns | Read/Write/Exec + Destructive | |
| 52 | +| **codex** | `is_known_safe_command()` whitelist + `command_might_be_dangerous()` + exec policy rules | Safe/Unsafe/Forbidden | |
| 53 | +| **pi-agent-rust** | `DangerousCommandClass` (10 classes) + `ExecRiskTier` (High/Critical) + `Effect` (7 variants) | 10 danger classes | |
| 54 | +| **oh-my-pi** | `ToolTier` self-declared: `read/write/exec` + `CRITICAL_BASH_PATTERNS` (26 regex) | Read/Write/Exec + Critical | |
| 55 | +| **opencode** | Per-tool permission keys (read, edit, bash, glob...) | Per-tool | |
| 56 | +| **oh-my-openagent** | Per-agent denylist + hook-chain guards | Per-agent | |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +## 3. Proven Patterns (consistent across 3+ repos) |
| 61 | + |
| 62 | +### 3.1 Enum-Driven Mode + Pre-Check Fast Path |
| 63 | + |
| 64 | +Every mature system uses an enum to represent the active mode, with a `pre_check()` or equivalent that short-circuits before expensive evaluation: |
| 65 | + |
| 66 | +``` |
| 67 | +pre_check(mode, effects) → AllowImmediately | DenyImmediately | PromptImmediately | Continue |
| 68 | +``` |
| 69 | + |
| 70 | +**Sources:** claude-code (`PermissionMode`), codex (`AskForApproval`), pi-agent-rust (`ExtensionPolicyMode`), dcg-core (`Mode::pre_check()`) |
| 71 | + |
| 72 | +### 3.2 Effect Taxonomy |
| 73 | + |
| 74 | +Tag every tool call with effects, then mode determines which effect sets auto-allow: |
| 75 | + |
| 76 | +| Effect | Who uses it | |
| 77 | +|--------|-------------| |
| 78 | +| `Read` | All 7 repos | |
| 79 | +| `Write` | All 7 repos | |
| 80 | +| `Exec/Spawn` | claude-code, codex, oh-my-pi, pi-agent-rust | |
| 81 | +| `Irreversible` | claude-code, pi-agent-rust, dcg-core | |
| 82 | +| `Network` | claude-code, codex, oh-my-pi, dcg-core | |
| 83 | +| `MutateVcs` | dcg-core | |
| 84 | +| `Fs` | claude-code, dcg-core | |
| 85 | + |
| 86 | +**dcg-core already has this:** `Effect` enum with 7 variants + `is_read_only()` + `is_subset()`. |
| 87 | + |
| 88 | +### 3.3 ToolCall Abstraction |
| 89 | + |
| 90 | +Normalize agent-specific tool names into a common taxonomy: |
| 91 | + |
| 92 | +| Variant | Maps from | |
| 93 | +|---------|-----------| |
| 94 | +| `Bash { cmd }` | Shell, terminal, run_terminal_cmd, execute_command | |
| 95 | +| `Edit { path }` | MultiEdit, ApplyPatch, str_replace | |
| 96 | +| `Write { path }` | write_file, create_or_update_file | |
| 97 | +| `Read { path }` | read_file, glob, grep, ls | |
| 98 | +| `Network { url }` | webfetch, websearch, browser | |
| 99 | + |
| 100 | +**dcg-core already has this:** `ToolCall` enum with 5 variants. |
| 101 | + |
| 102 | +### 3.4 `--dangerously-skip-permissions` Escape Hatch |
| 103 | + |
| 104 | +Universal across all TypeScript-based agents: |
| 105 | + |
| 106 | +| Repo | Flag | |
| 107 | +|------|------| |
| 108 | +| claude-code | `--dangerously-skip-permissions` | |
| 109 | +| opencode | `--dangerously-skip-permissions` | |
| 110 | +| codex | `--dangerously-bypass-approvals-and-sandbox` (alias `--yolo`) | |
| 111 | +| oh-my-pi | `--yolo` / `--auto-approve` | |
| 112 | + |
| 113 | +**jcode already has this:** `--dangerously-skip-permissions` (added in this branch). |
| 114 | + |
| 115 | +### 3.5 Per-Tool User Overrides |
| 116 | + |
| 117 | +Users can set `allow/deny/prompt` per tool in config, overriding mode baseline: |
| 118 | + |
| 119 | +| Repo | Config location | |
| 120 | +|------|----------------| |
| 121 | +| claude-code | `permissions.allow/deny/ask` arrays in settings.json | |
| 122 | +| opencode | `permission.bash: "allow"` in config | |
| 123 | +| oh-my-pi | `tools.approval.<toolName>: allow|deny|prompt` | |
| 124 | +| codex | Named permission profiles in TOML | |
| 125 | + |
| 126 | +### 3.6 Dangerous Command Detection |
| 127 | + |
| 128 | +Regex/pattern-based detection of unsafe commands before execution: |
| 129 | + |
| 130 | +| Repo | Count | Method | |
| 131 | +|------|-------|--------| |
| 132 | +| claude-code | ~30+ patterns | `DANGEROUS_BASH_PATTERNS` regex array | |
| 133 | +| oh-my-pi | 26 patterns | `CRITICAL_BASH_PATTERNS` regex array | |
| 134 | +| pi-agent-rust | 10 classes | `DangerousCommandClass` enum | |
| 135 | +| codex | ~50+ commands | `is_known_safe_command()` + `command_might_be_dangerous()` | |
| 136 | + |
| 137 | +### 3.7 Denial Tracking / Circuit Breaker |
| 138 | + |
| 139 | +When auto mode keeps denying, fall back to interactive prompt: |
| 140 | + |
| 141 | +| Repo | Threshold | Scope | |
| 142 | +|------|-----------|-------| |
| 143 | +| claude-code | 3 consecutive / 20 total | Per session | |
| 144 | +| codex | 3 per turn | Per turn | |
| 145 | +| dcg-core | Per-command counter (exists, escalation not yet wired) | Per command hash | |
| 146 | + |
| 147 | +### 3.8 Session-Scoped Approval Caching |
| 148 | + |
| 149 | +Avoid re-prompting the same action within a session: |
| 150 | + |
| 151 | +| Repo | Mechanism | |
| 152 | +|------|-----------| |
| 153 | +| codex | `ApprovedForSession` decision | |
| 154 | +| opencode | Runtime-approved rules in memory | |
| 155 | +| claude-code | Tool permission context with cached rules | |
| 156 | +| dcg-core | Allow-once codes (6-char hex, SHA-256 derived, 24h TTL) | |
| 157 | + |
| 158 | +### 3.9 Subagent Permission Restriction |
| 159 | + |
| 160 | +Children inherit restricted subset of parent rules: |
| 161 | + |
| 162 | +| Repo | Method | |
| 163 | +|------|--------| |
| 164 | +| opencode | Derive from parent denies + external_directory rules | |
| 165 | +| oh-my-pi | Subagents forced to yolo (parent = auth boundary) | |
| 166 | +| oh-my-openagent | Per-agent denylists + team denylist | |
| 167 | +| codebuff | Agent template `toolNames[]` + `spawnableAgents[]` | |
| 168 | + |
| 169 | +### 3.10 Mode Cycling (TUI) |
| 170 | + |
| 171 | +Runtime mode switching via keyboard shortcut: |
| 172 | + |
| 173 | +| Repo | Shortcut | Cycle | |
| 174 | +|------|----------|-------| |
| 175 | +| claude-code | Shift+Tab | default → acceptEdits → plan → auto → bypassPermissions → default | |
| 176 | +| codex | Not available | N/A | |
| 177 | +| Others | Not available | N/A | |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +## 4. Unique / Novel Patterns (single repo) |
| 182 | + |
| 183 | +| Pattern | Repo | Description | |
| 184 | +|---------|------|-------------| |
| 185 | +| **YOLO/Auto Classifier** | claude-code | LLM subagent classifies actions as safe/unsafe. Two-stage (fast + thinking). Iron-gate fail-closed. | |
| 186 | +| **Guardian Auto-Reviewer** | codex | Separate LLM risk assessment with 90s timeout, fail-closed, circuit breaker (3 denials/turn). | |
| 187 | +| **O(1) PolicySnapshot** | pi-agent-rust | Precompiled policy for zero-cost hot-path lookup. Built once at dispatcher creation. | |
| 188 | +| **Graduated Rollout** | pi-agent-rust | Shadow → LogOnly → EnforceNew → EnforceAll. Auto-rollback on false-positive rate. | |
| 189 | +| **Named Permission Profiles** | codex | TOML profiles with `extends` inheritance. `read-only`, `auto`, `full-access` presets. | |
| 190 | +| **Bash Arity Model** | opencode | Command prefix → token count → human-readable matching. `docker compose up` = arity 3. | |
| 191 | +| **Write-Before-Read Guard** | oh-my-openagent | LRU tracking prevents blind file overwrites (256 sessions × 1024 paths). | |
| 192 | +| **Propose-vs-Execute** | codebuff | Implementor proposes, selector applies. Separation of duty pattern. | |
| 193 | +| **Model-Visible Context** | codex | Permission policy injected into LLM system prompt as structured instructions. | |
| 194 | +| **MCP Env Allowlist Security** | oh-my-openagent | User-only config boundary prevents project-level injection. | |
| 195 | + |
| 196 | +--- |
| 197 | + |
| 198 | +## 5. dcg-core Status: Has vs Needs |
| 199 | + |
| 200 | +### ✅ Already Available in dcg-core v0.6.0-rc.1 |
| 201 | + |
| 202 | +| Feature | File | Status | |
| 203 | +|---------|------|--------| |
| 204 | +| `Mode` enum (6 variants) | `mode.rs` | Complete with `pre_check()` fast path | |
| 205 | +| `Effect` enum (7 variants) | `effect.rs` | Complete with `is_read_only()` + `is_subset()` | |
| 206 | +| `ToolCall` enum (5 variants) | `tool_call.rs` | Bash/Edit/Write/Read/Network | |
| 207 | +| `Decision` tri-state | `decision.rs` | Allow/Prompt{reason,alternatives}/Deny{reason,alternatives} | |
| 208 | +| `Engine::evaluate()` | `engine.rs` | Mode → pre_check → protected_paths → fallthrough | |
| 209 | +| `EngineConfig` builder | `engine.rs` | working_dir + protected_paths | |
| 210 | +| `Session` state | `session.rs` | Allow-once codes (6-char hex, 24h TTL) + per-command deny counter | |
| 211 | +| `ProtectedPaths` matcher | `protected_paths.rs` | Prefix matching, `~` expansion, canonicalization | |
| 212 | + |
| 213 | +### 🔨 Needs Building (Phase 2 dcg-core) |
| 214 | + |
| 215 | +| Feature | Priority | Notes | |
| 216 | +|---------|----------|-------| |
| 217 | +| Dangerous command patterns | P0 | 26-50 regex patterns, severity levels, safer alternatives | |
| 218 | +| Safe command whitelist | P0 | Known-safe read-only commands (cat, ls, grep, git status...) | |
| 219 | +| Denial escalation logic | P1 | Use existing `deny_counter` → escalate after N denials | |
| 220 | +| Session-wide denial budget | P1 | Track total denials across commands, not just per-command | |
| 221 | +| Pack rule integration | P2 | Move dcg-cli's 50+ security packs into dcg-core | |
| 222 | +| YOLO classifier trait | P2 | Define trait, let consumer inject LLM provider | |
| 223 | +| Per-tool user overrides | P2 | TOML config for allow/deny/prompt per tool pattern | |
| 224 | +| Network policy | P3 | Host allowlist/denylist for network calls | |
| 225 | + |
| 226 | +### 🏗️ Needs Building in jcode |
| 227 | + |
| 228 | +| Feature | Priority | Notes | |
| 229 | +|---------|----------|-------| |
| 230 | +| TUI mode cycling (Shift+Tab) | P0 | Cycle through 6 modes at runtime | |
| 231 | +| TUI permission dialogs | P0 | Allow/Deny/Always-allow for Prompt decisions | |
| 232 | +| CLI `--permission-mode` flag | ✅ Done | Already implemented | |
| 233 | +| CLI `--dangerously-skip-permissions` | ✅ Done | Already implemented | |
| 234 | +| dcg_bridge wiring | ✅ Done | Already implemented | |
| 235 | +| YOLO classifier implementation | P2 | Implement trait from dcg-core, inject active provider | |
| 236 | +| MCP permission pipeline | P3 | Unified or separate, TBD | |
| 237 | +| Protected paths config | P1 | Default CC paths + user-configurable TOML | |
| 238 | + |
| 239 | +--- |
| 240 | + |
| 241 | +## 6. Architecture Recommendation |
| 242 | + |
| 243 | +### 6.1 Layered Pipeline (recommended) |
| 244 | + |
| 245 | +``` |
| 246 | +CLI flags (--permission-mode, --dangerously-skip-permissions) |
| 247 | + │ |
| 248 | + ▼ |
| 249 | +Config (TOML: default mode, per-tool overrides, protected paths) |
| 250 | + │ |
| 251 | + ▼ |
| 252 | +dcg-core Engine::evaluate(session, tool_call, mode, effects) |
| 253 | + │ |
| 254 | + ├─► Mode::pre_check() ─► AllowImmediately / DenyImmediately / Continue |
| 255 | + │ |
| 256 | + ├─► Protected paths check ─► Deny if target in protected list |
| 257 | + │ |
| 258 | + ├─► [Phase 2] Pack rule evaluation ─► Allow/Deny by pattern |
| 259 | + │ |
| 260 | + ├─► Dangerous command detection ─► Escalate severity |
| 261 | + │ |
| 262 | + ├─► Safe command whitelist ─► Auto-approve known-safe |
| 263 | + │ |
| 264 | + ├─► [Phase 2] YOLO classifier trait ─► LLM auto-approve |
| 265 | + │ |
| 266 | + ├─► Denial escalation ─► Prompt after N denials |
| 267 | + │ |
| 268 | + └─► Decision: Allow / Prompt / Deny |
| 269 | + │ |
| 270 | + ▼ |
| 271 | + jcode TUI: Auto-approve (Allow) / Show dialog (Prompt) / Block (Deny) |
| 272 | +``` |
| 273 | + |
| 274 | +### 6.2 Config Hierarchy |
| 275 | + |
| 276 | +``` |
| 277 | +CLI flag > JCODE_PERMISSION_MODE env > .jcode/config.toml > ~/.jcode/config.toml > Mode::Default |
| 278 | +``` |
| 279 | + |
| 280 | +### 6.3 TOML Config Schema (proposed) |
| 281 | + |
| 282 | +```toml |
| 283 | +[permissions] |
| 284 | +# Default mode when no CLI flag |
| 285 | +default_mode = "default" |
| 286 | + |
| 287 | +# Protected paths (always prompt, regardless of mode) |
| 288 | +protected_paths = ["~/.ssh", "~/.aws", "~/.config/gh", ".git", ".env"] |
| 289 | + |
| 290 | +# Per-tool overrides (win over mode baseline) |
| 291 | +[permissions.tools] |
| 292 | +bash = "prompt" # Always prompt for bash |
| 293 | +edit = "allow" # Always allow edits |
| 294 | +read = "allow" # Always allow reads |
| 295 | +webfetch = "prompt" # Always prompt for network |
| 296 | + |
| 297 | +# Denial tracking |
| 298 | +[permissions.denial] |
| 299 | +max_consecutive = 3 |
| 300 | +max_total = 20 |
| 301 | +``` |
| 302 | + |
| 303 | +--- |
| 304 | + |
| 305 | +## 7. Open Questions (need further discussion) |
| 306 | + |
| 307 | +1. **YOLO classifier design** — Trait-based in dcg-core vs all in jcode? What LLM provider? How to keep dcg clean? |
| 308 | +2. **MCP permission pipeline** — Unified with core tools or separate system? |
| 309 | +3. **Sandboxing** — Not in scope for now, but what's the future plan? |
| 310 | +4. **Pack rules priority** — When should Phase 2 pack integration happen relative to other work? |
| 311 | +5. **Multi-agent/swarm** — How do subagents inherit permissions from parent? |
| 312 | + |
| 313 | +--- |
| 314 | + |
| 315 | +## 8. Reference Links |
| 316 | + |
| 317 | +### claude-code |
| 318 | +- Permission pipeline: https://github.com/claude-code-best/claude-code/blob/main/src/utils/permissions/permissions.ts |
| 319 | +- Mode enum: https://github.com/claude-code-best/claude-code/blob/main/src/types/permissions.ts |
| 320 | +- Setup: https://github.com/claude-code-best/claude-code/blob/main/src/utils/permissions/permissionSetup.ts |
| 321 | +- Dangerous patterns: https://github.com/claude-code-best/claude-code/blob/main/src/utils/permissions/dangerousPatterns.ts |
| 322 | +- Denial tracking: https://github.com/claude-code-best/claude-code/blob/main/src/utils/permissions/denialTracking.ts |
| 323 | +- YOLO classifier: https://github.com/claude-code-best/claude-code/blob/main/src/utils/permissions/yoloClassifier.ts |
| 324 | +- Sandbox: https://github.com/claude-code-best/claude-code/blob/main/src/utils/sandbox/sandbox-adapter.ts |
| 325 | + |
| 326 | +### codex |
| 327 | +- AskForApproval enum: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs#L787 |
| 328 | +- SandboxPolicy enum: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs#L881 |
| 329 | +- Exec policy: https://github.com/openai/codex/blob/main/codex-rs/core/src/exec_policy.rs |
| 330 | +- Guardian: https://github.com/openai/codex/blob/main/codex-rs/core/src/guardian/mod.rs |
| 331 | +- Safe commands: https://github.com/openai/codex/blob/main/codex-rs/shell-command/src/command_safety/is_safe_command.rs |
| 332 | + |
| 333 | +### pi-agent-rust |
| 334 | +- Policy modes: https://github.com/Dicklesworthstone/pi_agent_rust/blob/main/src/extensions.rs#L2129 |
| 335 | +- Policy profiles: https://github.com/Dicklesworthstone/pi_agent_rust/blob/main/src/extensions.rs#L2051 |
| 336 | +- Dangerous commands: https://github.com/Dicklesworthstone/pi_agent_rust/blob/main/src/extensions.rs#L3855 |
| 337 | +- Rollout phases: https://github.com/Dicklesworthstone/pi_agent_rust/blob/main/src/extensions.rs#L2229 |
| 338 | +- Permission store: https://github.com/Dicklesworthstone/pi_agent_rust/blob/main/src/permissions.rs |
| 339 | + |
| 340 | +### opencode |
| 341 | +- Permission service: https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/permission/index.ts |
| 342 | +- Evaluation engine: https://github.com/anomalyco/opencode/blob/main/packages/core/src/permission.ts |
| 343 | +- Config schema: https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/config/permission.ts |
| 344 | +- Subagent permissions: https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/agent/subagent-permissions.ts |
| 345 | + |
| 346 | +### oh-my-pi |
| 347 | +- Approval engine: https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/tools/approval.ts |
| 348 | +- Critical bash patterns: https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/tools/bash.ts |
| 349 | +- Plan mode guard: https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/tools/plan-mode-guard.ts |
| 350 | + |
| 351 | +### oh-my-openagent |
| 352 | +- Permission types: https://github.com/code-yeongyu/oh-my-openagent/blob/main/src/config/schema/internal/permission.ts |
| 353 | +- CC hooks types: https://github.com/code-yeongyu/oh-my-openagent/blob/main/src/hooks/claude-code-hooks/types.ts |
| 354 | +- Write guard: https://github.com/code-yeongyu/oh-my-openagent/blob/main/src/hooks/write-existing-file-guard/hook.ts |
| 355 | + |
| 356 | +### dcg-core (local) |
| 357 | +- Engine: /data/projects/destructive_command_guard/crates/dcg-core/src/engine.rs |
| 358 | +- Mode: /data/projects/destructive_command_guard/crates/dcg-core/src/mode.rs |
| 359 | +- Effect: /data/projects/destructive_command_guard/crates/dcg-core/src/effect.rs |
| 360 | +- Decision: /data/projects/destructive_command_guard/crates/dcg-core/src/decision.rs |
| 361 | +- Session: /data/projects/destructive_command_guard/crates/dcg-core/src/session.rs |
| 362 | +- ToolCall: /data/projects/destructive_command_guard/crates/dcg-core/src/tool_call.rs |
| 363 | +- ProtectedPaths: /data/projects/destructive_command_guard/crates/dcg-core/src/protected_paths.rs |
0 commit comments