Permissions
Balance safety and automation with unified permission system
Permission Layers
┌─────────────────────────────────────────────────────────────┐
│ 1. SAFE_TOOLS - Always auto-approved │
│ read_file, glob, grep (read-only operations) │
│ Stored as: source='safe', expires='never' │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. Config Permissions - Project-level auto-approve │
│ host.yaml: Bash(git status), write(*.md), etc. │
│ Stored as: source='config', expires='never' │
│ Pattern: Bash() → 'bash' with when:{command: '...'} │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. Skills - Temporary scoped permissions (one turn) │
│ /commit → auto-approve git commands for this turn only │
│ Stored as: source='skill', expires='turn_end' │
│ Preserves user approvals via snapshot/restore │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. User Approvals - Tool-level session memory │
│ User approves 'bash' once → ALL bash commands allowed │
│ Stored as: source='user', expires='session_end' │
│ TOOL-LEVEL: Approving "bash npm" = approve ALL bash │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. Tool Approval - Ask user for dangerous operations │
│ bash, edit, write → require explicit user approval │
│ If no permission in unified dict → ask user │
└─────────────────────────────────────────────────────────────┘Quick Start
Unified Permission Structure
All permissions use the same 4-field structure, stored in session['permissions']
True/False - Is this tool allowed?
"safe" | "skill" | "user"
Human-readable explanation
"never" | "turn_end" | "session_end"
Config Files Use Bash() Pattern
User-facing config files (.co/host.yaml) use a friendly Bash() pattern that automatically converts to the unified format at runtime:
User-Facing Config (.co/host.yaml)
Runtime Format (Internal)
Automatic conversion: You write Bash(git status) in config, it becomes bash with when:{command: "git status"} at runtime.
Tool-Level vs Granular Permissions
Critical Difference
User approvals are tool-level, not command-specific. This is different from config/skill permissions.
Config/Skill Permissions: Granular
User Approvals: Tool-Level
Why Tool-Level?
- ✅ Convenience for development workflows
- ✅ Don't re-approve every npm/pytest/git command
- ✅ Clear intent: "I trust bash for this session"
Security
- ✅ Config uses granular 'when' field
- ✅ Skills use granular 'when' field
- ✅ User approvals are simpler
Snapshot/Restore - Preserving User Approvals
Skills use a snapshot → grant → restore pattern to ensure user approvals are never lost:
Turn 3: User Approves
User approves write for session (tool-level approval)
Turn 5: /commit Skill
Turn 6: Continue
write still works (user approval preserved)bash requires approval (skill cleared)Security Benefits
- ✅ User approvals never overwritten by skills
- ✅ Skills add temporary permissions, don't replace
- ✅ Clean lifecycle - snapshot/restore is predictable
- ✅ No permission escalation across turns
1. SAFE_TOOLS - Always Auto-Approved
Read-only operations that can't harm the system:
No approval needed - these tools are always safe to execute.
Example
2. Skills - Temporary Scoped Permissions
Skills provide one-turn auto-approval with automatic cleanup. Perfect for workflows like git commits, deployments, or reviews.
Turn-Based
Permissions tied to specific turn number
Auto-Cleanup
Cleared when turn completes
Secure
No permission escalation across turns
4. Session Memory - Remember User Decisions
When you approve a tool, you can choose to remember it for the session:
