opencode.jsonc (14407B)
1 { 2 "$schema": "https://opencode.ai/config.json", 3 4 "providers": { 5 "ollama": { 6 "name": "Ollama Local", 7 "package": "aisdk:@ai-sdk/openai-compatible", 8 "settings": { 9 "baseURL": "http://127.0.0.1:11434/v1" 10 }, 11 "models": { 12 "gemma4:e2b": { 13 "name": "gemma4:e2b", 14 "capabilities": { 15 "tools": true, 16 "input": ["text", "image"], 17 "output": ["text"] 18 } 19 } 20 } 21 } 22 }, 23 24 "mcp": { 25 "servers": { 26 "gitlab": { 27 "type": "remote", 28 "url": "https://gitlab.com/api/v4/mcp", 29 "disabled": true 30 }, 31 "brewfather": { 32 "type": "local", 33 "command": ["uv", "run", "brewfather-mcp"], 34 "cwd": "/home/anders/Repos/brewfather-mcp", 35 "disabled": true, 36 "timeout": { 37 "catalog": 30000, 38 "execution": 30000 39 } 40 } 41 } 42 }, 43 44 // oh-my-opencode 45 "lsp": true, 46 "plugins": [ 47 "oh-my-opencode-slim" 48 ], 49 "agents": { 50 "explore": { 51 "disabled": true 52 }, 53 "general": { 54 "disabled": true 55 }, 56 // oh-my-opencode 57 58 "title": { 59 "model": "opencode/big-pickle" 60 }, 61 "slim-shady": { 62 "description": "Slim prompt agent for general questions", 63 "mode": "primary", 64 "system": "You are a helpful assistant", 65 "permissions": [ 66 { "action": "*", "resource": "*", "effect": "deny" } 67 ] 68 } 69 }, 70 71 "websearch": { 72 "provider": "random", 73 }, 74 "formatter": true, 75 76 // V2: ordered rules, last matching rule wins (broad first, exceptions after) 77 "permissions": [ 78 // Read-only tools: ask for anything, but never touch .env / .envrc 79 { "action": "read", "resource": "*", "effect": "ask" }, 80 { "action": "read", "resource": "*.env", "effect": "deny" }, 81 { "action": "read", "resource": "*.envrc", "effect": "deny" }, 82 { "action": "read", "resource": "*cloud.conf", "effect": "deny" }, 83 { "action": "read", "resource": "*secret*", "effect": "deny" }, 84 { "action": "read", "resource": "*terraform.tfstate*", "effect": "deny" }, 85 86 // glob/grep resources are the query, not a path, so path-shaped denies do nothing here 87 { "action": "glob", "resource": "*", "effect": "ask" }, 88 { "action": "grep", "resource": "*", "effect": "ask" }, 89 90 { "action": "subagent", "resource": "*", "effect": "allow" }, 91 { "action": "skill", "resource": "*", "effect": "allow" }, 92 { "action": "question", "resource": "*", "effect": "allow" }, 93 { "action": "webfetch", "resource": "*", "effect": "allow" }, 94 { "action": "websearch", "resource": "*", "effect": "allow" }, 95 96 { "action": "browser", "resource": "*", "effect": "deny" }, 97 98 { "action": "edit", "resource": "*", "effect": "ask" }, 99 100 // shell (was "bash" in V1) 101 { "action": "shell", "resource": "*", "effect": "ask" }, 102 // basic inspection 103 { "action": "shell", "resource": "echo *", "effect": "ask" }, 104 { "action": "shell", "resource": "ls*", "effect": "allow" }, 105 { "action": "shell", "resource": "pwd*", "effect": "allow" }, 106 { "action": "shell", "resource": "cat*", "effect": "allow" }, 107 { "action": "shell", "resource": "head*", "effect": "allow" }, 108 { "action": "shell", "resource": "tail*", "effect": "allow" }, 109 { "action": "shell", "resource": "find*", "effect": "allow" }, 110 { "action": "shell", "resource": "grep*", "effect": "allow" }, 111 { "action": "shell", "resource": "rg*", "effect": "allow" }, 112 { "action": "shell", "resource": "diff*", "effect": "allow" }, 113 { "action": "shell", "resource": "file*", "effect": "allow" }, 114 { "action": "shell", "resource": "which*", "effect": "allow" }, 115 { "action": "shell", "resource": "command -v *", "effect": "allow" }, 116 { "action": "shell", "resource": "wc*", "effect": "allow" }, 117 { "action": "shell", "resource": "jq*", "effect": "allow" }, 118 { "action": "shell", "resource": "sort*", "effect": "allow" }, 119 { "action": "shell", "resource": "uniq*", "effect": "allow" }, 120 { "action": "shell", "resource": "du*", "effect": "allow" }, 121 { "action": "shell", "resource": "df*", "effect": "allow" }, 122 { "action": "shell", "resource": "uname*", "effect": "allow" }, 123 { "action": "shell", "resource": "sleep *", "effect": "allow" }, 124 // read-only git 125 { "action": "shell", "resource": "git status*", "effect": "allow" }, 126 { "action": "shell", "resource": "git diff*", "effect": "allow" }, 127 { "action": "shell", "resource": "git ls-files*", "effect": "allow" }, 128 { "action": "shell", "resource": "git log*", "effect": "allow" }, 129 { "action": "shell", "resource": "git grep*", "effect": "allow" }, 130 { "action": "shell", "resource": "git show*", "effect": "allow" }, 131 // git branch/remote: read-only forms only — no -D/-f/-m/--delete, no add/remove/set-url 132 { "action": "shell", "resource": "git branch", "effect": "allow" }, 133 { "action": "shell", "resource": "git branch --list*", "effect": "allow" }, 134 { "action": "shell", "resource": "git branch -a", "effect": "allow" }, 135 { "action": "shell", "resource": "git branch -r", "effect": "allow" }, 136 { "action": "shell", "resource": "git branch -v", "effect": "allow" }, 137 { "action": "shell", "resource": "git branch -vv", "effect": "allow" }, 138 { "action": "shell", "resource": "git branch -av", "effect": "allow" }, 139 { "action": "shell", "resource": "git remote", "effect": "allow" }, 140 { "action": "shell", "resource": "git remote -v", "effect": "allow" }, 141 { "action": "shell", "resource": "git remote get-url*", "effect": "allow" }, 142 { "action": "shell", "resource": "git stash list*", "effect": "allow" }, 143 // read-only go 144 { "action": "shell", "resource": "go version*", "effect": "allow" }, 145 { "action": "shell", "resource": "go env*", "effect": "allow" }, 146 // test / check commands 147 { "action": "shell", "resource": "npm test*", "effect": "allow" }, 148 { "action": "shell", "resource": "npm run lint*", "effect": "allow" }, 149 { "action": "shell", "resource": "npm run typecheck*", "effect": "allow" }, 150 { "action": "shell", "resource": "cargo check*", "effect": "allow" }, 151 { "action": "shell", "resource": "cargo test*", "effect": "allow" }, 152 { "action": "shell", "resource": "go vet*", "effect": "allow" }, 153 { "action": "shell", "resource": "go test*", "effect": "allow" }, 154 { "action": "shell", "resource": "pytest*", "effect": "allow" }, 155 { "action": "shell", "resource": "python3 -m pytest*", "effect": "allow" }, 156 // mutating: always confirm (after the matching allows above) 157 { "action": "shell", "resource": "git add*", "effect": "ask" }, 158 { "action": "shell", "resource": "git commit*", "effect": "ask" }, 159 { "action": "shell", "resource": "git push*", "effect": "ask" }, 160 // network + writes .git 161 { "action": "shell", "resource": "git fetch*", "effect": "ask" }, 162 { "action": "shell", "resource": "git reset*", "effect": "ask" }, 163 { "action": "shell", "resource": "git checkout*", "effect": "ask" }, 164 { "action": "shell", "resource": "git clean*", "effect": "ask" }, 165 { "action": "shell", "resource": "git rebase*", "effect": "ask" }, 166 { "action": "shell", "resource": "npm install*", "effect": "ask" }, 167 { "action": "shell", "resource": "npm run build*", "effect": "ask" }, 168 { "action": "shell", "resource": "cargo build*", "effect": "ask" }, 169 { "action": "shell", "resource": "go build*", "effect": "ask" }, 170 // go env -w/-u write the persistent go env config (after the go env allow above) 171 { "action": "shell", "resource": "go env -w*", "effect": "ask" }, 172 { "action": "shell", "resource": "go env -u*", "effect": "ask" }, 173 { "action": "shell", "resource": "pip install*", "effect": "ask" }, 174 { "action": "shell", "resource": "terraform plan*", "effect": "ask" }, 175 { "action": "shell", "resource": "terraform apply*", "effect": "ask" }, 176 { "action": "shell", "resource": "docker*", "effect": "ask" }, 177 { "action": "shell", "resource": "curl*", "effect": "ask" }, 178 { "action": "shell", "resource": "wget*", "effect": "ask" }, 179 { "action": "shell", "resource": "sudo*", "effect": "ask" }, 180 { "action": "shell", "resource": "chmod*", "effect": "ask" }, 181 { "action": "shell", "resource": "chown*", "effect": "ask" }, 182 { "action": "shell", "resource": "cp*", "effect": "ask" }, 183 { "action": "shell", "resource": "mv*", "effect": "ask" }, 184 { "action": "shell", "resource": "mkdir*", "effect": "ask" }, 185 { "action": "shell", "resource": "touch*", "effect": "ask" }, 186 { "action": "shell", "resource": "rm *", "effect": "ask" }, 187 // environment $VAR 188 { "action": "shell", "resource": "printenv *", "effect": "deny" }, 189 { "action": "shell", "resource": "env *", "effect": "deny" }, 190 // .env / .envrc — content-dumping readers only; absorb * covers quoted and path-prefixed forms 191 { "action": "shell", "resource": "cat* *.env*", "effect": "deny" }, 192 { "action": "shell", "resource": "head* *.env*", "effect": "deny" }, 193 { "action": "shell", "resource": "tail* *.env*", "effect": "deny" }, 194 { "action": "shell", "resource": "diff* *.env*", "effect": "deny" }, 195 { "action": "shell", "resource": "find* *.env*", "effect": "deny" }, 196 { "action": "shell", "resource": "git diff* *.env*", "effect": "deny" }, 197 { "action": "shell", "resource": "git show* *.env*", "effect": "deny" }, 198 { "action": "shell", "resource": "git log* *.env*", "effect": "deny" }, 199 // grep/rg: absorb would also block "process.env" searches, so bare + path forms separately 200 { "action": "shell", "resource": "grep* .env*", "effect": "deny" }, 201 { "action": "shell", "resource": "grep* */.env*", "effect": "deny" }, 202 { "action": "shell", "resource": "rg* .env*", "effect": "deny" }, 203 { "action": "shell", "resource": "rg* */.env*", "effect": "deny" }, 204 // .env.example exceptions 205 { "action": "shell", "resource": "* .env.example", "effect": "allow" }, 206 // terraform state — same readers; no dot-prefix, so one absorb pattern each 207 { "action": "shell", "resource": "cat* *.tfstate*", "effect": "deny" }, 208 { "action": "shell", "resource": "head* *.tfstate*", "effect": "deny" }, 209 { "action": "shell", "resource": "tail* *.tfstate*", "effect": "deny" }, 210 { "action": "shell", "resource": "diff* *.tfstate*", "effect": "deny" }, 211 { "action": "shell", "resource": "find* *.tfstate*", "effect": "deny" }, 212 { "action": "shell", "resource": "git diff* *.tfstate*", "effect": "deny" }, 213 { "action": "shell", "resource": "git show* *.tfstate*", "effect": "deny" }, 214 { "action": "shell", "resource": "git log* *.tfstate*", "effect": "deny" }, 215 { "action": "shell", "resource": "grep* *.tfstate*", "effect": "deny" }, 216 { "action": "shell", "resource": "rg* *.tfstate*", "effect": "deny" }, 217 // Sops 218 { "action": "shell", "resource": "sops*", "effect": "deny" }, 219 // read-only flags that exec a helper or write files 220 { "action": "shell", "resource": "git* --output*", "effect": "deny" }, 221 { "action": "shell", "resource": "rg* --pre *", "effect": "deny" }, 222 { "action": "shell", "resource": "rg* --pre=*", "effect": "deny" }, 223 { "action": "shell", "resource": "sort* -o *", "effect": "deny" }, 224 // destructive: never auto-run (after the broader asks above) 225 { "action": "shell", "resource": "rm -rf*", "effect": "deny" }, 226 { "action": "shell", "resource": "rm -fr*", "effect": "deny" }, 227 { "action": "shell", "resource": "git push --force*", "effect": "deny" }, 228 { "action": "shell", "resource": "shutdown*", "effect": "deny" }, 229 { "action": "shell", "resource": "reboot*", "effect": "deny" }, 230 { "action": "shell", "resource": "mkfs*", "effect": "deny" }, 231 { "action": "shell", "resource": "dd *", "effect": "deny" }, 232 // rm -rf exceptions 233 { "action": "shell", "resource": "rm -rf /tmp/opencode/*", "effect": "ask" }, 234 { "action": "shell", "resource": "rm -fr /tmp/opencode/*", "effect": "ask" }, 235 236 // Files outside the workspace: confirm, but block secrets outright 237 { "action": "external_directory", "resource": "*", "effect": "ask" }, 238 { "action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow" }, 239 { "action": "external_directory", "resource": "~/.config/opencode/skills/*", "effect": "allow" }, 240 // credential stores, API tokens, and shell history (secrets pasted into commands) 241 { "action": "external_directory", "resource": "~/.aws*", "effect": "deny" }, 242 { "action": "external_directory", "resource": "~/.gnupg*", "effect": "deny" }, 243 { "action": "external_directory", "resource": "~/.kube*", "effect": "deny" }, 244 { "action": "external_directory", "resource": "~/.s3cfg*", "effect": "deny" }, 245 { "action": "external_directory", "resource": "~/.ssh*", "effect": "deny" }, 246 { "action": "external_directory", "resource": "~/.vault_pass.txt*", "effect": "deny" }, 247 { "action": "external_directory", "resource": "~/.vault-token*", "effect": "deny" }, 248 { "action": "external_directory", "resource": "~/.git-credentials*", "effect": "deny" }, 249 { "action": "external_directory", "resource": "~/.netrc*", "effect": "deny" }, 250 { "action": "external_directory", "resource": "~/.npmrc*", "effect": "deny" }, 251 { "action": "external_directory", "resource": "~/.pypirc*", "effect": "deny" }, 252 { "action": "external_directory", "resource": "~/.docker*", "effect": "deny" }, 253 { "action": "external_directory", "resource": "~/.cargo/credentials*", "effect": "deny" }, 254 { "action": "external_directory", "resource": "~/.config/gh*", "effect": "deny" }, 255 { "action": "external_directory", "resource": "~/.config/gcloud*", "effect": "deny" }, 256 { "action": "external_directory", "resource": "~/.config/opencode/auth.json*", "effect": "deny" }, 257 { "action": "external_directory", "resource": "~/.local/share/opencode/auth.json*", "effect": "deny" }, 258 { "action": "external_directory", "resource": "~/.claude/.credentials.json*", "effect": "deny" }, 259 { "action": "external_directory", "resource": "~/.bash_history*", "effect": "deny" }, 260 { "action": "external_directory", "resource": "~/.zsh_history*", "effect": "deny" }, 261 { "action": "external_directory", "resource": "~/.python_history*", "effect": "deny" }, 262 { "action": "external_directory", "resource": "~/.node_repl_history*", "effect": "deny" } 263 ] 264 }