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

Merged
slaid098 merged 4 commits from chore/permissions/allow-deletion-ssh-curl into main 2026-08-11 12:48:22 +03:00
Owner

Что сделано

Второе расширение allow-list permission.bash в .opencode/opencode.json (продолжение issue #34 / PR #35). 4 логических коммита:

Bucket A — read-only + docker exec (4dad9c0):

  • curl *, git reflog *, docker images*, docker inspect*, docker stats* → allow
  • docker exec * → allow (был ask) + carve-outs: docker exec * rm */rmdir * → deny, docker exec * sh */bash * → ask

Bucket B — fs + local git mutations (0f198db):

  • cp, mv, touch, ln, tar, zip, unzip, wget → allow
  • git switch *, git stash*, git tag, git config * → allow

Bucket D — удаление + secret carve-outs (54baffc):

  • rm *, git rm *, git branch -D *, git -C * branch -D * → allow
  • carve-outs после rm *: allow: rm .env*, rm *id_ed25519*, rm *.pem*, rm *secrets* → deny

Bucket E — ssh catch-all + fix дубля (a333fc6):

  • ssh * → allow (был ask), перенесён в начало ssh-секции (см. Watch out)
  • удалён дубль git commit *: allow (остался git commit *: deny)

Почему

Анализ 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). Docker-изоляция + git history делают большинство операций восстанавливаемыми — пользователь явно принимает риск.

Watch out

  • CRITICAL findLast ordering: opencode использует findLast (последнее совпадающее правило побеждает). Все deny/ask carve-outs идут ПОСЛЕ соответствующих allow: curl * < curl * -X DELETE *FORGEJO_URL*, docker exec * < docker exec * rm */sh *, rm * < rm .env*/*.pem*, ssh * < ssh * rm */mkfs*/reboot* и т.д. Проверено программно (все 8 пар allow < carve OK).
  • Отклонение от буквальной инструкции issue: issue body утверждал, что SSH carve-outs "уже идут ПОСЛЕ ssh *" — но в реальном файле ssh * (стр. 356) шёл ПОСЛЕ carve-outs (300-354). Простая замена ssh *: ask → allow на месте сломала бы все destructive carve-outs (ssh * rm *, ssh * mkfs*, ssh * reboot* стали бы allow — security regression). Поэтому ssh *: allow перенесён в начало ssh-секции (после nohup *: ask, до cat *id_ed25519*), а старая строка ssh *: ask удалена. Критерий приёмки "carve-outs идут ПОСЛЕ ssh *: allow" соблюдён.
  • ssh * cat * — БЕЗ carve-out (user declined): ssh * cat rclone.conf будет allow. Принятый риск (creds нужны для деплоя, балансы маленькие, solo).
  • &&-chaining: opencode матчит всю строку — rm /tmp/x && git status не матчит ни rm *, ни git status* → catch-all. Mitigation: дисциплина в промптах.
  • rm .env* защищает только prefix .env* — rm /path/to/.env НЕ защищён. Acceptable risk (user confirmed).
  • docker exec * rm * → deny, но docker exec forgejo sh -c "rm /tmp/x" матчит docker exec * sh *: ask (НЕ rm *: deny, т.к. rm внутри кавычек без rm с пробелом). Acceptable.
  • git tag (exact) — allow; git tag -d * (delete) — остаётся ask.
  • НЕ тронуто: git push --force*/--delete (ask), git reset */git clean * (deny), kill */docker rm */docker stop */docker restart * (ask), reviewer.md, memory-syncer.md.

Pending

—

Closes #36

Closes #36

## Что сделано Второе расширение allow-list `permission.bash` в `.opencode/opencode.json` (продолжение issue #34 / PR #35). 4 логических коммита: **Bucket A — read-only + docker exec** (`4dad9c0`): - `curl *`, `git reflog *`, `docker images*`, `docker inspect*`, `docker stats*` → allow - `docker exec *` → allow (был ask) + carve-outs: `docker exec * rm *`/`rmdir *` → deny, `docker exec * sh *`/`bash *` → ask **Bucket B — fs + local git mutations** (`0f198db`): - `cp`, `mv`, `touch`, `ln`, `tar`, `zip`, `unzip`, `wget` → allow - `git switch *`, `git stash*`, `git tag`, `git config *` → allow **Bucket D — удаление + secret carve-outs** (`54baffc`): - `rm *`, `git rm *`, `git branch -D *`, `git -C * branch -D *` → allow - carve-outs после `rm *: allow`: `rm .env*`, `rm *id_ed25519*`, `rm *.pem*`, `rm *secrets*` → deny **Bucket E — ssh catch-all + fix дубля** (`a333fc6`): - `ssh *` → allow (был ask), перенесён в **начало** ssh-секции (см. Watch out) - удалён дубль `git commit *: allow` (остался `git commit *: deny`) ## Почему Анализ 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). Docker-изоляция + git history делают большинство операций восстанавливаемыми — пользователь явно принимает риск. ## Watch out - **CRITICAL findLast ordering**: opencode использует findLast (последнее совпадающее правило побеждает). Все deny/ask carve-outs идут **ПОСЛЕ** соответствующих allow: `curl *` < `curl * -X DELETE *FORGEJO_URL*`, `docker exec *` < `docker exec * rm *`/`sh *`, `rm *` < `rm .env*`/`*.pem*`, `ssh *` < `ssh * rm *`/`mkfs*`/`reboot*` и т.д. Проверено программно (все 8 пар `allow < carve` OK). - **Отклонение от буквальной инструкции issue**: issue body утверждал, что SSH carve-outs "уже идут ПОСЛЕ `ssh *`" — но в реальном файле `ssh *` (стр. 356) шёл **ПОСЛЕ** carve-outs (300-354). Простая замена `ssh *: ask` → `allow` на месте сломала бы все destructive carve-outs (`ssh * rm *`, `ssh * mkfs*`, `ssh * reboot*` стали бы allow — security regression). Поэтому `ssh *: allow` **перенесён в начало** ssh-секции (после `nohup *: ask`, до `cat *id_ed25519*`), а старая строка `ssh *: ask` удалена. Критерий приёмки "carve-outs идут ПОСЛЕ `ssh *: allow`" соблюдён. - `ssh * cat *` — **БЕЗ** carve-out (user declined): `ssh * cat rclone.conf` будет allow. Принятый риск (creds нужны для деплоя, балансы маленькие, solo). - `&&`-chaining: opencode матчит всю строку — `rm /tmp/x && git status` не матчит ни `rm *`, ни `git status*` → catch-all. Mitigation: дисциплина в промптах. - `rm .env*` защищает только prefix `.env*` — `rm /path/to/.env` НЕ защищён. Acceptable risk (user confirmed). - `docker exec * rm *` → deny, но `docker exec forgejo sh -c "rm /tmp/x"` матчит `docker exec * sh *: ask` (НЕ `rm *: deny`, т.к. `rm` внутри кавычек без ` rm ` с пробелом). Acceptable. - `git tag` (exact) — allow; `git tag -d *` (delete) — остаётся ask. - НЕ тронуто: `git push --force*`/`--delete` (ask), `git reset *`/`git clean *` (deny), `kill *`/`docker rm *`/`docker stop *`/`docker restart *` (ask), reviewer.md, memory-syncer.md. ## Pending — Closes #36 Closes #36
chore(permissions): allow ssh catch-all and fix git commit duplicate
All checks were successful
CI (always) / bootstrap (pull_request) Successful in 4s
CI / bootstrap (pull_request) Successful in 7s
Permission Security Check / check (pull_request) Successful in 9s
CI / lint (pull_request) Successful in 24s
CI / typecheck (pull_request) Successful in 25s
CI / complexity (pull_request) Successful in 25s
CI / test (3.13) (pull_request) Successful in 1m31s
a333fc61c2
Author
Owner

Code Review Summary

PR расширяет allow-list permission.bash в .opencode/opencode.json (удаление, ssh, curl, fs-операции). Все findLast ordering инварианты проверены — каждый deny/ask carve-out стоит ПОСЛЕ соответствующего allow. Дубль git commit *: allow удалён. Guard-скрипт check-permissions.py проходит. Изменён только .opencode/opencode.json.

Positives

findLast ordering — все 8 пар allow→carve-out проверены (последнее совпадающее правило побеждает):

  • rm *: allow (стр. 307) → ДО rm .env*/*id_ed25519*/.pem/secrets: deny (308-311) ✅
  • ssh *: allow (стр. 323) → ДО ВСЕХ destructive carve-outs: mkfs*/dd */fdisk* deny (326-328), rm */rmdir */reboot*/shutdown*/systemctl restart*/stop*/apt */yum */mv */chmod */chown */docker restart*/stop*/rm*/rmi*/update*/cat *.env*/cat .env*/powershell Set-*/New-*/Remove-*/Restart-*/Stop-* ask (358-380) ✅
  • curl *: allow (стр. 228) → ДО curl * -X DELETE *FORGEJO_URL*: deny (стр. 383) ✅
  • docker exec *: allow (стр. 300) → ДО docker exec * rm */rmdir *: deny (301-302), sh */bash *: ask (303-304) ✅

Критический ssh-fix верифицирован: ssh *: allow перенесён в НАЧАЛО ssh-секции (стр. 323, после nohup *: ask, до cat *id_ed25519*). Старая строка ssh *: ask (которая шла ПОСЛЕ carve-outs) удалена. Простая замена ask→allow на месте сломала бы все destructive carve-outs (security regression) — implement subagent правильно это распознал и перенёс правило.

Дубль git commit * убран: git commit *: allow удалён, остался единственный git commit *: deny (стр. 382) ✅

Запрещённые НЕ добавлены: gh pr merge * отсутствует, git push --force*/-f*/--delete * остались ask (385-396), git reset */git clean * остались deny (315-316), kill */docker rm */docker stop */docker restart * остались ask (297-299, 320), python3 * в memory-syncer не тронут ✅

ssh * cat *: ask НЕ добавлен (user declined) — только pre-existing ssh * cat *.env*/.env* (specific to .env) остаются ✅

Scope: изменён ТОЛЬКО .opencode/opencode.json (31+/7-). reviewer.md, memory-syncer.md НЕ тронуты ✅

Валидация: JSON валиден (json.load OK), guard-скрипт python3 .opencode/scripts/check-permissions.py → "OK: No dangerous permission rules found." ✅

PR body: 4 heading'а (## Что сделано, ## Почему, ## Watch out, ## Pending = —) + Closes #36 ✅. Watch out подробно документирует findLast ordering, ssh-fix, отклонение ssh * cat * carve-out, &&-chaining gotcha, rm .env* prefix-only limitation.

CI: green (pipeline-status confirmed).

Suggestions (info, not blocking)

  • opencode.json:PR body [cosmetic] Closes #36 появляется дважды в конце body. Forgejo всё равно закроет issue, но дубликат можно убрать.
  • opencode.json:308 [security-info] rm .env* защищает только prefix-match (.env* в начале строки). rm /path/to/.env НЕ защищён (т.к. pattern не содержит */.env). Уже задокументировано в Watch out как accepted risk — без действия.

Verdict: APPROVE

## Code Review Summary PR расширяет allow-list `permission.bash` в `.opencode/opencode.json` (удаление, ssh, curl, fs-операции). Все findLast ordering инварианты проверены — каждый deny/ask carve-out стоит ПОСЛЕ соответствующего allow. Дубль `git commit *: allow` удалён. Guard-скрипт `check-permissions.py` проходит. Изменён только `.opencode/opencode.json`. ### Positives **findLast ordering — все 8 пар allow→carve-out проверены (последнее совпадающее правило побеждает):** - `rm *: allow` (стр. 307) → ДО `rm .env*`/`*id_ed25519*`/*.pem*/*secrets*: deny (308-311) ✅ - `ssh *: allow` (стр. 323) → ДО ВСЕХ destructive carve-outs: `mkfs*`/`dd *`/`fdisk*` deny (326-328), `rm *`/`rmdir *`/`reboot*`/`shutdown*`/`systemctl restart*`/`stop*`/`apt *`/`yum *`/`mv *`/`chmod *`/`chown *`/`docker restart*`/`stop*`/`rm*`/`rmi*`/`update*`/`cat *.env*`/`cat .env*`/`powershell Set-*/New-*/Remove-*/Restart-*/Stop-*` ask (358-380) ✅ - `curl *: allow` (стр. 228) → ДО `curl * -X DELETE *FORGEJO_URL*: deny` (стр. 383) ✅ - `docker exec *: allow` (стр. 300) → ДО `docker exec * rm *`/`rmdir *: deny` (301-302), `sh *`/`bash *: ask` (303-304) ✅ **Критический ssh-fix верифицирован:** `ssh *: allow` перенесён в НАЧАЛО ssh-секции (стр. 323, после `nohup *: ask`, до `cat *id_ed25519*`). Старая строка `ssh *: ask` (которая шла ПОСЛЕ carve-outs) удалена. Простая замена `ask`→`allow` на месте сломала бы все destructive carve-outs (security regression) — implement subagent правильно это распознал и перенёс правило. **Дубль `git commit *` убран:** `git commit *: allow` удалён, остался единственный `git commit *: deny` (стр. 382) ✅ **Запрещённые НЕ добавлены:** `gh pr merge *` отсутствует, `git push --force*`/`-f*`/`--delete *` остались ask (385-396), `git reset *`/`git clean *` остались deny (315-316), `kill *`/`docker rm *`/`docker stop *`/`docker restart *` остались ask (297-299, 320), `python3 *` в memory-syncer не тронут ✅ **`ssh * cat *: ask` НЕ добавлен** (user declined) — только pre-existing `ssh * cat *.env*`/`.env*` (specific to .env) остаются ✅ **Scope:** изменён ТОЛЬКО `.opencode/opencode.json` (31+/7-). reviewer.md, memory-syncer.md НЕ тронуты ✅ **Валидация:** JSON валиден (`json.load` OK), guard-скрипт `python3 .opencode/scripts/check-permissions.py` → "OK: No dangerous permission rules found." ✅ **PR body:** 4 heading'а (`## Что сделано`, `## Почему`, `## Watch out`, `## Pending` = `—`) + `Closes #36` ✅. Watch out подробно документирует findLast ordering, ssh-fix, отклонение `ssh * cat *` carve-out, `&&`-chaining gotcha, `rm .env*` prefix-only limitation. **CI:** green (pipeline-status confirmed). ### Suggestions (info, not blocking) - **opencode.json:PR body** [cosmetic] `Closes #36` появляется дважды в конце body. Forgejo всё равно закроет issue, но дубликат можно убрать. - **opencode.json:308** [security-info] `rm .env*` защищает только prefix-match (`.env*` в начале строки). `rm /path/to/.env` НЕ защищён (т.к. pattern не содержит `*/.env`). Уже задокументировано в Watch out как accepted risk — без действия. ### Verdict: APPROVE
slaid098 deleted branch chore/permissions/allow-deletion-ssh-curl 2026-08-11 12:48:23 +03:00
Sign in to join this conversation.
No reviewers
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!37
No description provided.