Defense-in-depth security plugin for OpenCode. Blocks dangerous commands and protects sensitive files before they execute.
Documentation · Issues · Contributing · Security
AI coding agents have shell access, file read/write, and broad autonomy. A single bad command -- whether from a hallucination, prompt injection, or honest mistake -- can:
rm -rf /your filesystemDROP TABLEyour production database- Leak
~/.sshkeys or~/.awscredentials git push --forceover your team's workterraform destroyyour infrastructure
This plugin intercepts every tool call and either blocks or asks for confirmation before dangerous ones run.
Add to your opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-damage-control"]
}Restart OpenCode. Done -- all protections are active with zero configuration.
56 hard-blocked, 88 require confirmation. Covers system destruction (rm -rf /, fork bombs, dd), SQL (DROP TABLE, DELETE FROM, TRUNCATE), git (--force push, filter-branch, stash clear), cloud infrastructure (AWS, GCP, Azure, Terraform, Pulumi), Docker/Kubernetes, databases (Redis, Postgres, MySQL, MongoDB), hosting platforms (Vercel, Netlify, Heroku, Fly.io, Cloudflare, Firebase, Serverless), process/system manipulation (crontab -r, systemctl, iptables, launchctl), and Windows-specific commands (del /s /q, rd /s /q, diskpart, reg delete, bcdedit, PowerShell Remove-Item, Stop-Service, Uninstall-Package).
Shell wrapper unwrapping: Commands wrapped in bash -c "...", sh -c "...", python -c "...", cmd /c "...", powershell -Command "...", pwsh -c "...", env bash -c "...", etc. are automatically unwrapped and inspected. Nested wrappers are handled recursively.
Three-tier protection system for sensitive files:
| Level | Read | Write | Delete | Examples |
|---|---|---|---|---|
| zeroAccess | Block | Block | Block | ~/.ssh, ~/.aws, .env*, *.pem |
| readOnly | Allow | Block | Block | /etc/, lock files, node_modules/, dist/ |
| noDelete | Allow | Allow | Block | .git/, LICENSE, Dockerfile, CI configs |
| Action | Behavior | When |
|---|---|---|
block |
Hard block. Tool never executes. | Catastrophic commands (rm -rf /, DROP TABLE, terraform destroy) |
ask |
User sees confirmation dialog. | Risky-but-valid commands (git reset --hard, rm -rf, DELETE ... WHERE) |
flowchart TD
CALL["OpenCode Tool Call"]
CALL --> EXEC["bash / shell / cmd"]
CALL --> READ["read / glob / grep"]
CALL --> WRITE["edit / write / create"]
EXEC --> UNWRAP["Unwrap Shell Wrappers"]
UNWRAP --> PP["Pattern + Path Check"]
READ --> PC1["Path Check"]
WRITE --> PC2["Path Check"]
PP --> |block| THROW["THROW (hard block)"]
PP --> |ask| STASH["STASH by callID"]
PP --> |no match| ALLOW1["ALLOW"]
PC1 --> |zeroAccess| BLOCK1["BLOCK"]
PC1 --> |otherwise| ALLOW2["ALLOW"]
PC2 --> |zeroAccess / readOnly| BLOCK2["BLOCK"]
PC2 --> |otherwise| ALLOW3["ALLOW"]
STASH --> PERM["permission.ask hook"]
PERM --> DIALOG["CONFIRM DIALOG"]
style THROW fill:#dc2626,color:#fff,stroke:#991b1b
style BLOCK1 fill:#dc2626,color:#fff,stroke:#991b1b
style BLOCK2 fill:#dc2626,color:#fff,stroke:#991b1b
style DIALOG fill:#f59e0b,color:#000,stroke:#d97706
style ALLOW1 fill:#16a34a,color:#fff,stroke:#15803d
style ALLOW2 fill:#16a34a,color:#fff,stroke:#15803d
style ALLOW3 fill:#16a34a,color:#fff,stroke:#15803d
Hook 1: tool.execute.before -- inspects every tool call. Matches with block throw immediately. Matches with ask are stashed by callID and proceed to the permission system. Protected paths are enforced based on their tier and the operation type.
Hook 2: permission.ask -- looks up stashed matches and forces output.status = 'ask', ensuring the user sees the confirmation dialog even if their permission config would normally auto-allow.
Everything works out of the box. To customize, create a damage-control.json in either or both locations:
| Location | Scope |
|---|---|
~/.config/opencode/damage-control.json |
Global (all projects) |
.opencode/damage-control.json |
Project (this repo only) |
Both optional. Project merges on top of global. Invalid config logs warnings and uses defaults.
{
"patterns": {
"add": [
{ "pattern": "my-dangerous-cmd", "reason": "Custom block", "action": "block" }
],
"remove": ["SQL DROP TABLE"],
"override": {
"Recursive delete from root": "ask"
}
},
"paths": {
"add": [
{ "path": "~/.my-secrets", "level": "zeroAccess" }
],
"remove": ["~/.npmrc"],
"override": {
"~/.docker": "none"
}
}
}| Operation | What it does |
|---|---|
add |
Append new patterns/paths after defaults |
remove |
Remove by exact reason (patterns) or path (paths) |
override |
Change action or level. Use "none" to unprotect a path. |
Processing order: defaults → remove → override → add.
When both global and project configs exist: add arrays concatenate, remove arrays union, override objects shallow-merge (project wins).
Relax a block to ask:
{ "patterns": { "override": { "Terraform destroy": "ask" } } }Add a custom pattern:
{
"patterns": {
"add": [{ "pattern": "prod-db-wipe", "reason": "Wipes production DB", "action": "block" }]
}
}Unprotect a path:
{ "paths": { "override": { "~/.npmrc": "none" } } }The AI agent sees an error and adjusts:
DAMAGE_CONTROL_BLOCKED: SQL DROP TABLE
Command: DROP TABLE
OpenCode shows the standard permission dialog:
damage-control flagged: git reset --hard
[once] [always] [reject]
- Substring matching for paths. A command that merely mentions a protected path (e.g., in a comment) will be blocked.
- Shell only, not subprocesses. Inspects command strings passed to
bash/shell/cmd. Cannot inspect commands spawned by scripts (but does unwrapbash -c,python -c, etc.). - Pattern ordering matters. First match wins. Specific patterns are ordered before generic ones.
- Ask requires permission system. The
permission.askhook forces the dialog even if the user's config auto-allows, but exact UX depends on OpenCode version.
git clone https://github.com/whjvenyl/opencode-damage-control.git
cd opencode-damage-control
npm install
npm run build # output in dist/
npm test # 454 testssrc/
patterns.ts 144 patterns, 103 paths, shell unwrapping, matching helpers
config.ts Config loading, validation, merging
index.ts Plugin entry point (2 hooks)
patterns.test.ts 428 pattern + unwrapping tests
config.test.ts 26 config tests
| Module | Exports |
|---|---|
patterns.ts |
DEFAULT_PATTERNS, DEFAULT_PROTECTED_PATHS, matchPattern(), checkPathProtection(), checkShellPathViolation(), unwrapShellCommand() |
config.ts |
loadConfig(), applyConfig(), DamageControlConfig |
index.ts |
DamageControl plugin -- loads config at init, returns tool.execute.before + permission.ask hooks |
See CONTRIBUTING.md for setup, architecture, and PR conventions.
For security vulnerabilities, see SECURITY.md.
Based on the concept from claude-code-damage-control by @disler. Reimplemented as a native OpenCode plugin with zero runtime dependencies.
MIT