chore(permissions): allow deletion ssh curl and fs operations #36

Closed
opened 2026-08-11 12:28:03 +03:00 by slaid098 · 0 comments
Owner

Контекст

Зачем: второе расширение allow-list permission.bash в .opencode/opencode.json. Первое (issue #34 / PR #35, merged в commit 3ef28e9) добавило read-only команды (echo, cat, head, tail, grep, rg, find, ls, wc, pwd, which, date, test, sed -n, od, xxd, git rev-parse/remote/show/get-url/show/fetch/blame/ls-tree/ls-files, git -C * read-only, node/npm queries). Этого недостаточно — остаются ночные залипания.

Контекст: анализ 56 ask-событий за 5 дней (6-10 августа) показал два главных stall-узла: rm * (20 ask, 19 stall) и ssh * (20 ask, 17 stall). Пользователь запускал overnight-задачу — она залипла на rm (удаление файлов). Также не покрыты первым расширением: git branch -D * (5 ask) и docker exec * (2 ask).

Решение пользователя: разрешить БОЛЬШЕ команд, включая удаление (rm *, git rm *) и SSH (ssh *), потому что Docker-изоляция + git history делают большинство операций восстанавливаемыми. Пользователь явно принимает риск чтения агентом credentials через SSH (маленькие балансы, solo developer, агент нужен для деплоя). Carve-out ssh * cat * — НЕ добавляем (user declined).

Задача

Изменить блок permission.bash (строки 174-381) в файле .opencode/opencode.json. Все изменения точечные — добавить/изменить паттерны в существующем JSON-массиве. Сохранить findLast-порядок: все deny/ask carve-outs идут ПОСЛЕ соответствующих allow правил.

Bucket A — read-only (добавить в allow, рядом с существующими read-only паттернами ~строки 224-236):

"curl *": "allow"
"git reflog *": "allow"
"docker images*": "allow"
"docker inspect*": "allow"
"docker stats*": "allow"
"docker exec *": "allow"

Bucket A — docker exec carve-outs (deny/ask ПОСЛЕ docker exec *: allow, findLast):

"docker exec * rm *": "deny"
"docker exec * rmdir *": "deny"
"docker exec * sh *": "ask"
"docker exec * bash *": "ask"

Bucket B — мутирующие локальные (добавить в allow):

"git switch *": "allow"
"git stash*": "allow"
"git tag": "allow"
"git config *": "allow"
"cp *": "allow"
"mv *": "allow"
"touch *": "allow"
"ln *": "allow"
"tar *": "allow"
"zip *": "allow"
"unzip *": "allow"
"wget *": "allow"

Bucket D — удаление (allow с carve-outs для секретов):

"rm *": "allow"
"git rm *": "allow"
"git branch -D *": "allow"
"git -C * branch -D *": "allow"

Bucket D — deny carve-outs для секретов (ПОСЛЕ rm *: allow, findLast — защищают gitignored файлы которые НЕ recoverable via git):

"rm .env*": "deny"
"rm *id_ed25519*": "deny"
"rm *.pem*": "deny"
"rm *secrets*": "deny"

Bucket E — SSH (заменить catch-all):

Изменить существующее "ssh *": "ask" (~строка 356) → "ssh *": "allow".

Bucket E — SSH destructive carve-outs:

Эти правила УЖЕ есть в конфиге (~строки 332-354) как ask или deny — НЕ удалять, НЕ менять. Они должны остаться ПОСЛЕ нового ssh *: allow чтобы побеждать по findLast:

  • ssh * rm *: ask
  • ssh * rmdir *: ask
  • ssh * mkfs*: deny
  • ssh * dd *: deny
  • ssh * fdisk*: deny
  • ssh * reboot*: ask
  • ssh * shutdown*: ask
  • ssh * systemctl restart*: ask
  • ssh * systemctl stop*: ask
  • ssh * apt *: ask
  • ssh * yum *: ask
  • ssh * mv *: ask
  • ssh * chmod *: ask
  • ssh * chown *: ask
  • ssh * docker restart*: ask
  • ssh * docker stop*: ask
  • ssh * docker rm*: ask
  • ssh * docker rmi*: ask
  • ssh * docker update*: ask
  • ssh * cat .env*: ask
  • ssh * powershell -Command Set-*: ask
  • ssh * powershell -Command New-*: ask
  • ssh * powershell -Command Remove-*: ask
  • ssh * powershell -Command Restart-*: ask
  • ssh * powershell -Command Stop-*: ask

Fix — убрать дубль git commit *:

Найти "git commit *": "allow" (~строка 183) и УДАЛИТЬ его. Оставить "git commit *": "deny" (~строка 358). Причина: findLast — последнее правило побеждает, deny на 358 уже выигрывает, но дубль confusing. commit tool используется вместо bash git commit.

curl * carve-out (deny ПОСЛЕ curl *: allow, findLast):

Существующее "curl * -X DELETE *FORGEJO_URL*": "deny" (~строка 359) — ОСТАВИТЬ (уже есть, после curl *: allow побеждает по findLast). Убедиться что новое curl *: allow добавлено ВЫШЕ существующего deny.

Per-agent: НЕ трогать

  • .opencode/agents/reviewer.md — НЕ менять (уже получил ruff/gh auth status в PR #35)
  • .opencode/agents/memory-syncer.md — НЕ менять (python3 bypass guard, read-only guardrail)

Контракты

  • После изменений: general/explore больше не ask на curl *, rm *, git rm *, git branch -D *, docker exec * (read-only), ssh * (неинспекционные), cp *, mv *, touch *, ln *, tar *, zip *, unzip *, wget *, git switch *, git stash*, git tag, git config *, git reflog *, docker images*, docker inspect*, docker stats*
  • Секреты (.env, id_ed25519, *.pem, *secrets*) остаются deny для rm — carve-outs после rm *: allow (findLast)
  • curl * -X DELETE *FORGEJO_URL* остаётся deny — существующее правило после curl *: allow (findLast)
  • SSH destructive операции (ssh * rm *, ssh * mkfs*, ssh * reboot*, ssh * docker restart* etc.) остаются ask/deny — существующие правила после ssh *: allow (findLast)
  • docker exec * rm *: deny, docker exec * sh */docker exec * bash *: ask — carve-outs после docker exec *: allow (findLast)
  • git push --force*, git push -f*, git push --delete *, git push origin --delete * — остаются ask (не трогаем)
  • git reset *, git clean * — остаются deny (не трогаем)
  • git commit * — остаётся deny (убираем дубль allow, оставляем deny)
  • kill *, docker rm *, docker stop *, docker restart * — остаются ask (не трогаем)

Инварианты

  • CRITICAL findLast ordering: все deny/ask carve-outs (rm .env*, rm .pem, curl -X DELETE, ssh * rm *, docker exec * rm *, etc.) должны идти ПОСЛЕ соответствующих allow правил (rm *: allow, curl *: allow, ssh *: allow, docker exec *: allow) в JSON-массиве permission.bash — opencode findLast, последнее правило побеждает
  • НЕ добавлять carve-out ssh * cat *: ask — user explicitly declined (agent may read remote creds for deploys, accepted risk)
  • НЕ добавлять python3 * в memory-syncer (bypass risk)
  • НЕ добавлять gh pr merge * (orchestrator uses merge-pr tool, not bash; gh CLI not installed)
  • НЕ трогать reviewer.md и memory-syncer.md
  • git push --force* / --delete остаются ask (irreversible remote operations)
  • git reset * остаётся deny (destructive)
  • JSON должен оставаться валидным (проверить python3 -c "import json; json.load(open('.opencode/opencode.json'))" или node -e "JSON.parse(require('fs').readFileSync('.opencode/opencode.json','utf8'))")

Граничные случаи

  • &&-chaining: opencode матчит ВСЮ строку. rm /tmp/x && git status НЕ матчит ни rm *, ни git status* — catch-all. Не лечится allow-list. Mitigation: дисциплина в промптах.
  • rm .env защищён carve-out rm .env*: deny. Но rm .env.local или rm /path/to/.env — rm .env* матчит только prefix .env*, НЕ матчит /path/to/.env. Секреты в нестандартных путях НЕ защищены. Acceptable risk (user confirmed).
  • docker exec * rm *: deny защищает от удаления внутри контейнера. Но docker exec forgejo sh -c "rm /tmp/x" — матчит docker exec * sh *: ask, НЕ docker exec * rm *: deny (findLast выберет ask). Acceptable.
  • ssh * cat rclone.conf — БЕЗ carve-out, будет allow. User accepted (creds нужны для деплоя, балансы маленькие).
  • git tag (create) — allow. git tag -d * (delete) — остаётся ask (существующее правило ~строка 366).

Влияние на связанные компоненты

  • .opencode/opencode.json — основной файл изменений (блок permission.bash, строки 174-381)
  • .opencode/agents/reviewer.md — НЕ меняется
  • .opencode/agents/memory-syncer.md — НЕ меняется
  • AGENTS.md — НЕ меняется (но поведение агентов меняется: меньше ask-промптов, меньше stall-узлов)
  • Memory files (technical/opencode-permission-*) — будут обновлены в MEMORY phase после merge PR

Вне scope

  • ❌ Глобальный Ask → Allow для ВСЕХ команд (точечное расширение, не ампутация)
  • ❌ Carve-out ssh * cat *: ask (user declined — agent may read remote creds)
  • ❌ python3 * в memory-syncer (bypass risk)
  • ❌ gh pr merge * в allow (orchestrator tool, gh not installed)
  • ❌ git push --force* / --delete в allow (irreversible)
  • ❌ git reset * в allow (destructive)
  • ❌ Изменение reviewer.md / memory-syncer.md
  • ❌ Изменение steps лимитов
  • ❌ Добавление general.md / explore.md per-agent конфигов (отдельная задача)

Критерии приемки

  • В .opencode/opencode.json добавлены все Bucket A паттерны (curl, git reflog, docker images/inspect/stats/exec)
  • docker exec * rm *: deny + docker exec * rmdir *: deny + docker exec * sh *: ask + docker exec * bash *: ask идут ПОСЛЕ docker exec *: allow (findLast)
  • Добавлены Bucket B (git switch/stash/tag/config, cp/mv/touch/ln/tar/zip/unzip/wget)
  • Добавлены Bucket D: rm *: allow + git rm *: allow + git branch -D *: allow + git -C * branch -D *: allow
  • rm .env*: deny, rm *id_ed25519*: deny, rm *.pem*: deny, rm *secrets*: deny идут ПОСЛЕ rm *: allow (findLast)
  • ssh * изменён с ask на allow (catch-all)
  • Существующие SSH destructive carve-outs (rm/mkfs/dd/fdisk/reboot/shutdown/systemctl/apt/yum/mv/chmod/chown/docker restart/stop/rm/rmi/update/powershell) сохранены и идут ПОСЛЕ ssh *: allow
  • ssh * cat *: ask НЕ добавлен (user declined)
  • Дубль git commit * убран (allow удалён, deny оставлен)
  • curl * -X DELETE *FORGEJO_URL*: deny сохранён ПОСЛЕ curl *: allow
  • reviewer.md и memory-syncer.md НЕ изменены
  • JSON валиден (node -e "JSON.parse(require('fs').readFileSync('.opencode/opencode.json','utf8'))" проходит без ошибок)
  • git diff показывает только .opencode/opencode.json
## Контекст Зачем: второе расширение allow-list `permission.bash` в `.opencode/opencode.json`. Первое (issue #34 / PR #35, merged в commit 3ef28e9) добавило read-only команды (echo, cat, head, tail, grep, rg, find, ls, wc, pwd, which, date, test, sed -n, od, xxd, git rev-parse/remote/show/get-url/show/fetch/blame/ls-tree/ls-files, git -C * read-only, node/npm queries). Этого недостаточно — остаются ночные залипания. Контекст: анализ 56 ask-событий за 5 дней (6-10 августа) показал два главных stall-узла: `rm *` (20 ask, 19 stall) и `ssh *` (20 ask, 17 stall). Пользователь запускал overnight-задачу — она залипла на `rm` (удаление файлов). Также не покрыты первым расширением: `git branch -D *` (5 ask) и `docker exec *` (2 ask). Решение пользователя: разрешить БОЛЬШЕ команд, включая удаление (`rm *`, `git rm *`) и SSH (`ssh *`), потому что Docker-изоляция + git history делают большинство операций восстанавливаемыми. Пользователь явно принимает риск чтения агентом credentials через SSH (маленькие балансы, solo developer, агент нужен для деплоя). Carve-out `ssh * cat *` — НЕ добавляем (user declined). ## Задача Изменить блок `permission.bash` (строки 174-381) в файле `.opencode/opencode.json`. Все изменения точечные — добавить/изменить паттерны в существующем JSON-массиве. Сохранить findLast-порядок: все deny/ask carve-outs идут ПОСЛЕ соответствующих allow правил. ### Bucket A — read-only (добавить в allow, рядом с существующими read-only паттернами ~строки 224-236): ``` "curl *": "allow" "git reflog *": "allow" "docker images*": "allow" "docker inspect*": "allow" "docker stats*": "allow" "docker exec *": "allow" ``` ### Bucket A — docker exec carve-outs (deny/ask ПОСЛЕ `docker exec *: allow`, findLast): ``` "docker exec * rm *": "deny" "docker exec * rmdir *": "deny" "docker exec * sh *": "ask" "docker exec * bash *": "ask" ``` ### Bucket B — мутирующие локальные (добавить в allow): ``` "git switch *": "allow" "git stash*": "allow" "git tag": "allow" "git config *": "allow" "cp *": "allow" "mv *": "allow" "touch *": "allow" "ln *": "allow" "tar *": "allow" "zip *": "allow" "unzip *": "allow" "wget *": "allow" ``` ### Bucket D — удаление (allow с carve-outs для секретов): ``` "rm *": "allow" "git rm *": "allow" "git branch -D *": "allow" "git -C * branch -D *": "allow" ``` ### Bucket D — deny carve-outs для секретов (ПОСЛЕ `rm *: allow`, findLast — защищают gitignored файлы которые НЕ recoverable via git): ``` "rm .env*": "deny" "rm *id_ed25519*": "deny" "rm *.pem*": "deny" "rm *secrets*": "deny" ``` ### Bucket E — SSH (заменить catch-all): Изменить существующее `"ssh *": "ask"` (~строка 356) → `"ssh *": "allow"`. ### Bucket E — SSH destructive carve-outs: Эти правила УЖЕ есть в конфиге (~строки 332-354) как `ask` или `deny` — НЕ удалять, НЕ менять. Они должны остаться ПОСЛЕ нового `ssh *: allow` чтобы побеждать по findLast: - `ssh * rm *`: ask - `ssh * rmdir *`: ask - `ssh * mkfs*`: deny - `ssh * dd *`: deny - `ssh * fdisk*`: deny - `ssh * reboot*`: ask - `ssh * shutdown*`: ask - `ssh * systemctl restart*`: ask - `ssh * systemctl stop*`: ask - `ssh * apt *`: ask - `ssh * yum *`: ask - `ssh * mv *`: ask - `ssh * chmod *`: ask - `ssh * chown *`: ask - `ssh * docker restart*`: ask - `ssh * docker stop*`: ask - `ssh * docker rm*`: ask - `ssh * docker rmi*`: ask - `ssh * docker update*`: ask - `ssh * cat .env*`: ask - `ssh * powershell -Command Set-*`: ask - `ssh * powershell -Command New-*`: ask - `ssh * powershell -Command Remove-*`: ask - `ssh * powershell -Command Restart-*`: ask - `ssh * powershell -Command Stop-*`: ask ### Fix — убрать дубль `git commit *`: Найти `"git commit *": "allow"` (~строка 183) и УДАЛИТЬ его. Оставить `"git commit *": "deny"` (~строка 358). Причина: findLast — последнее правило побеждает, deny на 358 уже выигрывает, но дубль confusing. `commit` tool используется вместо bash `git commit`. ### `curl *` carve-out (deny ПОСЛЕ `curl *: allow`, findLast): Существующее `"curl * -X DELETE *FORGEJO_URL*": "deny"` (~строка 359) — ОСТАВИТЬ (уже есть, после `curl *: allow` побеждает по findLast). Убедиться что новое `curl *: allow` добавлено ВЫШЕ существующего deny. ### Per-agent: НЕ трогать - `.opencode/agents/reviewer.md` — НЕ менять (уже получил ruff/gh auth status в PR #35) - `.opencode/agents/memory-syncer.md` — НЕ менять (python3 bypass guard, read-only guardrail) ## Контракты - После изменений: `general`/`explore` больше не ask на `curl *`, `rm *`, `git rm *`, `git branch -D *`, `docker exec *` (read-only), `ssh *` (неинспекционные), `cp *`, `mv *`, `touch *`, `ln *`, `tar *`, `zip *`, `unzip *`, `wget *`, `git switch *`, `git stash*`, `git tag`, `git config *`, `git reflog *`, `docker images*`, `docker inspect*`, `docker stats*` - Секреты (`.env`, `id_ed25519`, `*.pem`, `*secrets*`) остаются deny для `rm` — carve-outs после `rm *: allow` (findLast) - `curl * -X DELETE *FORGEJO_URL*` остаётся deny — существующее правило после `curl *: allow` (findLast) - SSH destructive операции (`ssh * rm *`, `ssh * mkfs*`, `ssh * reboot*`, `ssh * docker restart*` etc.) остаются ask/deny — существующие правила после `ssh *: allow` (findLast) - `docker exec * rm *`: deny, `docker exec * sh *`/`docker exec * bash *`: ask — carve-outs после `docker exec *: allow` (findLast) - `git push --force*`, `git push -f*`, `git push --delete *`, `git push origin --delete *` — остаются ask (не трогаем) - `git reset *`, `git clean *` — остаются deny (не трогаем) - `git commit *` — остаётся deny (убираем дубль allow, оставляем deny) - `kill *`, `docker rm *`, `docker stop *`, `docker restart *` — остаются ask (не трогаем) ## Инварианты - CRITICAL findLast ordering: все deny/ask carve-outs (rm .env*, rm *.pem*, curl -X DELETE, ssh * rm *, docker exec * rm *, etc.) должны идти ПОСЛЕ соответствующих allow правил (`rm *: allow`, `curl *: allow`, `ssh *: allow`, `docker exec *: allow`) в JSON-массиве `permission.bash` — opencode findLast, последнее правило побеждает - НЕ добавлять carve-out `ssh * cat *: ask` — user explicitly declined (agent may read remote creds for deploys, accepted risk) - НЕ добавлять `python3 *` в memory-syncer (bypass risk) - НЕ добавлять `gh pr merge *` (orchestrator uses merge-pr tool, not bash; gh CLI not installed) - НЕ трогать reviewer.md и memory-syncer.md - `git push --force*` / `--delete` остаются ask (irreversible remote operations) - `git reset *` остаётся deny (destructive) - JSON должен оставаться валидным (проверить `python3 -c "import json; json.load(open('.opencode/opencode.json'))"` или `node -e "JSON.parse(require('fs').readFileSync('.opencode/opencode.json','utf8'))"`) ## Граничные случаи - `&&`-chaining: opencode матчит ВСЮ строку. `rm /tmp/x && git status` НЕ матчит ни `rm *`, ни `git status*` — catch-all. Не лечится allow-list. Mitigation: дисциплина в промптах. - `rm .env` защищён carve-out `rm .env*: deny`. Но `rm .env.local` или `rm /path/to/.env` — `rm .env*` матчит только prefix `.env*`, НЕ матчит `/path/to/.env`. Секреты в нестандартных путях НЕ защищены. Acceptable risk (user confirmed). - `docker exec * rm *`: deny защищает от удаления внутри контейнера. Но `docker exec forgejo sh -c "rm /tmp/x"` — матчит `docker exec * sh *: ask`, НЕ `docker exec * rm *: deny` (findLast выберет ask). Acceptable. - `ssh * cat rclone.conf` — БЕЗ carve-out, будет allow. User accepted (creds нужны для деплоя, балансы маленькие). - `git tag` (create) — allow. `git tag -d *` (delete) — остаётся ask (существующее правило ~строка 366). ## Влияние на связанные компоненты - `.opencode/opencode.json` — основной файл изменений (блок `permission.bash`, строки 174-381) - `.opencode/agents/reviewer.md` — НЕ меняется - `.opencode/agents/memory-syncer.md` — НЕ меняется - AGENTS.md — НЕ меняется (но поведение агентов меняется: меньше ask-промптов, меньше stall-узлов) - Memory files (technical/opencode-permission-*) — будут обновлены в MEMORY phase после merge PR ## Вне scope - ❌ Глобальный Ask → Allow для ВСЕХ команд (точечное расширение, не ампутация) - ❌ Carve-out `ssh * cat *: ask` (user declined — agent may read remote creds) - ❌ `python3 *` в memory-syncer (bypass risk) - ❌ `gh pr merge *` в allow (orchestrator tool, gh not installed) - ❌ `git push --force*` / `--delete` в allow (irreversible) - ❌ `git reset *` в allow (destructive) - ❌ Изменение reviewer.md / memory-syncer.md - ❌ Изменение steps лимитов - ❌ Добавление general.md / explore.md per-agent конфигов (отдельная задача) ## Критерии приемки - [ ] В `.opencode/opencode.json` добавлены все Bucket A паттерны (curl, git reflog, docker images/inspect/stats/exec) - [ ] `docker exec * rm *: deny` + `docker exec * rmdir *: deny` + `docker exec * sh *: ask` + `docker exec * bash *: ask` идут ПОСЛЕ `docker exec *: allow` (findLast) - [ ] Добавлены Bucket B (git switch/stash/tag/config, cp/mv/touch/ln/tar/zip/unzip/wget) - [ ] Добавлены Bucket D: `rm *: allow` + `git rm *: allow` + `git branch -D *: allow` + `git -C * branch -D *: allow` - [ ] `rm .env*: deny`, `rm *id_ed25519*: deny`, `rm *.pem*: deny`, `rm *secrets*: deny` идут ПОСЛЕ `rm *: allow` (findLast) - [ ] `ssh *` изменён с `ask` на `allow` (catch-all) - [ ] Существующие SSH destructive carve-outs (rm/mkfs/dd/fdisk/reboot/shutdown/systemctl/apt/yum/mv/chmod/chown/docker restart/stop/rm/rmi/update/powershell) сохранены и идут ПОСЛЕ `ssh *: allow` - [ ] `ssh * cat *: ask` НЕ добавлен (user declined) - [ ] Дубль `git commit *` убран (`allow` удалён, `deny` оставлен) - [ ] `curl * -X DELETE *FORGEJO_URL*: deny` сохранён ПОСЛЕ `curl *: allow` - [ ] reviewer.md и memory-syncer.md НЕ изменены - [ ] JSON валиден (`node -e "JSON.parse(require('fs').readFileSync('.opencode/opencode.json','utf8'))"` проходит без ошибок) - [ ] `git diff` показывает только `.opencode/opencode.json`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/opencode-config#36
No description provided.