Skip to content

Commit 60687f1

Browse files
quangdang46claude
andcommitted
docs: add permission modes research report (7 repos + dcg-core analysis)
Comprehensive research across claude-code, codex, pi-agent-rust, opencode, oh-my-openagent, oh-my-pi, codebuff. Covers: - Cross-repo mode/decision/classification comparison - 10 proven patterns (tri-state, effect taxonomy, denial tracking, etc.) - 10 unique novel patterns (YOLO classifier, guardian, O(1) snapshot, etc.) - dcg-core has-vs-needs matrix - Architecture recommendation: layered pipeline - Proposed TOML config schema - Open questions for further discussion Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 29d937d commit 60687f1

1 file changed

Lines changed: 363 additions & 0 deletions

File tree

docs/PERMISSION_MODES_RESEARCH.md

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
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

Comments
 (0)