Command Security
How Cosmo classifies, sandboxes, and gates shell commands before execution.
Cosmo's AI engine can run shell commands on the user's system (e.g., git status, npm install, ls). The command policy system ensures every command is classified by risk, explained in plain English, and gated by user permission before it executes.
Architecture
The command policy sits between the LLM's tool call and the actual execFile call:
LLM emits tool_call
→ CommandPolicy.evaluateCommand(command, args, workspaceDir)
→ returns CommandVerdict { risk, explanation, blocked }
→ if blocked: return error to LLM (no prompt shown)
→ if not blocked: show confirmation UI with risk + explanation
→ user allows or denies
→ execFile (only if allowed)Execution uses Node.js execFile with arguments passed as an array — never interpolated into a shell string — preventing command injection.
Risk Levels
Every command receives one of four risk classifications:
| Level | Color | Meaning | User Prompt? |
|---|---|---|---|
safe | Green | Read-only, cannot modify state | Yes |
moderate | Yellow | Writes within the workspace directory | Yes |
dangerous | Red | System-wide impact or writes outside workspace | Yes |
blocked | — | Never allowed | No — instant error |
Safe Commands
Read-only commands that cannot modify the system:
- File inspection:
ls,cat,head,tail,wc,tree,file,stat - Search:
grep,rg,find(without-exec/-delete),ag - Git (read):
git status,git log,git diff,git branch,git blame - GitHub CLI (read):
gh pr list,gh issue view,gh run list - Package managers (read):
npm list,npm outdated,npm audit - System info:
pwd,whoami,date,uname,which,env - Version checks: any command with
--versionor-v
Moderate Commands
Commands that modify files within the workspace directory or have limited scope:
- Git (write):
git add,git commit,git push,git checkout - Package managers:
npm install,npm run,pnpm build - File operations (in workspace):
mkdir,touch,cp,mv,rm - Script execution:
node script.js,python script.py
Dangerous Commands
Commands with system-wide impact or that target paths outside the workspace:
- Privilege escalation:
sudo,su - Permissions:
chmod,chown - Global installs:
npm install -g,pip install --user - Force operations:
git push --force - System package managers:
brew install - Any file-modifying command targeting a path outside the workspace directory
Blocked Commands
These are never executed — the LLM receives an error explaining why:
- Destructive system commands:
mkfs,fdisk,dd,shutdown,reboot,halt - Protected paths: operations targeting
/usr,/etc,/System,/var,/bin,/sbin,~/.ssh,~/.gnupg,~/.aws - Recursive delete on critical paths:
rm -rf /,rm -rf ~
Directory Sandboxing
Commands that modify files (rm, cp, mv, mkdir, touch, ln, rsync) have their path arguments resolved against the workspace directory:
- Relative paths are resolved from
workspaceDir ~is expanded to the user's home directory- Each resolved path is checked:
- Inside workspace directory → risk stays
moderate - Outside workspace directory → escalated to
dangerous - Targeting a protected system path →
blocked
- Inside workspace directory → risk stays
Plain-English Explanations
Every command gets a human-readable explanation generated from static templates:
| Command | Explanation |
|---|---|
ls -la | List files in the current folder |
git push origin main | Push commits to the remote repository |
rm -rf node_modules | Delete folder and all its contents: node_modules |
npm install lodash | Install package: lodash |
curl https://api.example.com | Fetch data from: https://api.example.com |
git push --force | Force-push changes to the remote (overwrites remote history) |
Unknown commands fall back to: "Run command: {command} {args}".
Denial Behavior
When a user denies a command, the engine stops immediately. It does not:
- Feed the denial back to the LLM for retry
- Continue executing other pending tool calls
- Attempt an alternative command
This is intentional — a denial is a hard stop, not a suggestion to try something else.
Types
import { evaluateCommand, type CommandRisk, type CommandVerdict } from '@cosmohq/core'
type CommandRisk = 'safe' | 'moderate' | 'dangerous' | 'blocked'
interface CommandVerdict {
risk: CommandRisk
explanation: string
blocked: boolean
blockReason?: string
}
// Evaluate a command before execution
const verdict = evaluateCommand('rm', ['-rf', 'node_modules'], '/path/to/workspace')
// → { risk: 'moderate', explanation: 'Delete folder...', blocked: false }Confirmation Interface
Shells (CLI, Desktop) receive a ToolConfirmationRequest and render it appropriately:
interface ToolConfirmationRequest {
call: ToolCall
risk: CommandRisk
explanation: string
}
type ToolConfirmFn = (request: ToolConfirmationRequest) => Promise<boolean>The CLI renders a color-coded terminal prompt. The Desktop app renders an inline confirmation card with Allow/Deny buttons.