* feat(permissions): deny direct git/gh calls and add role-based tool access * feat(permissions): update docs-reviewer to use commit tool * test(permissions): add tests for deny rules and tool access * docs(handoff): add handoff and ADR for permissions lock * docs: update project map + handoff + ADR --------- Co-authored-by: opencode-agent <agent@slaid098.dev>
5.2 KiB
ADR-016: Deny direct git/gh bash calls + role-based tool access
Статус
Accepted (2026-07-24)
Контекст
PR #38 добавил 3 детерминированных TS tool'а (commit, create_pr, create_issue) с встроенной валидацией форматов. Но агенты могли всё ещё вызывать git commit/gh pr create/gh pr merge/gh issue create через raw bash (разрешено в permission.bash), обходя валидацию tools. Это нарушало pure-orchestrator model (ADR-010/PR#30) и контракты форматов (AGENTS.md rules не enforced).
opencode имеет два независимых уровня permissions:
permission.bash— glob-паттерны на bash-команды (findLast — последние побеждают). Custom TS tools bypass черезspawnSync(trusted code, не проходит черезpermission.bash).agent.<name>.tools— boolean map per-agent (включает/отключает конкретные tools для конкретного агента).
Нужно: (1) заблокировать прямые bash-вызовы мутаций, не блокируя tools, (2) настроить role-based access — кто из subagent'ов какие tools может вызывать.
Решение
1. Global deny rules в permission.bash (.opencode/opencode.json)
4 deny-правила в КОНЕЦ секции bash (findLast — last wins, перекрывают более ранние allow):
"git commit *": "deny"— блокирует прямойgit commit,committool bypass'ит"gh pr create *": "deny"— блокирует прямойgh pr create,create_prtool bypass'ит"gh pr merge *": "deny"— блокирует прямойgh pr merge,merge_prtool bypass'ит"gh issue create *": "deny"— блокирует прямойgh issue create,create_issuetool bypass'ит
git push * остаётся allow (НЕ добавлен deny — push нужен для пуша веток).
2. Role-based tool access в agent.<name>.tools
| Agent | commit | create_pr | create_issue | merge_pr |
|---|---|---|---|---|
| general | ✅ | ✅ | ✅ | ❌ |
| reviewer | ❌ | ❌ | ❌ | ❌ |
| docs-reviewer | ✅ | ❌ | ❌ | ❌ |
| memory-syncer | ❌ | ❌ | ❌ | ❌ |
| main (orchestrator) | ✅ | ✅ | ✅ | ✅ (наследует global all-true) |
Main agent НЕ указан в agent секции → наследует global tools (all true by default). Явные restrictions только для subagents.
3. docs-reviewer frontmatter + prompt
"git commit*": allow убран из frontmatter (global deny покрывает). Prompt body: git commit -m ... → commit({ message: ... }) в 3 местах. docs-reviewer теперь использует commit tool для коммита project map/handoff.
4. DANGEROUS_PATTERNS — БЕЗ ИЗМЕНЕНИЙ
Новые deny rules не требуют deterministic guards: (1) deny-действия не триггерят violations (скрипт флагует только allow), (2) существующие allow rules оставлены в opencode.json (перекрыты deny через findLast), добавление guard флагнуло бы легитимные allow. gh pr merge* уже имеет guard (scope=agent).
Альтернативы
- Удалить существующие allow rules вместо добавления deny — отклонено: спека issue #39 явно говорит "добавить deny rules в конец" (findLast), не "удалить allow". Удаление allow нарушило бы другие use cases (e.g.
gh issue*allow покрываетgh issue view,gh issue list). Deny в конце точечно перекрывает только create-команды. - Добавить DANGEROUS_PATTERNS для
git commit *: allow/gh pr create *: allow/gh issue create *: allow— отклонено: флагнуло бы существующие легитимные allow rules выше по файлу (которые перекрыты deny через findLast, но всё ещё присутствуют в JSON). CI упал бы на валидных конфигах. Глобальныйgh pr merge*: allowнамеренно оставлен для main agent (guard scope=agent пропускает global). - Per-agent
git commit*: denyв frontmatter вместо global — отклонено: global deny покрывает всех агентов одной строкой. Per-agent deny в 4 frontmatter файлах = дублирование. Global + defense-in-depth per-agent (только дляgh pr merge*, уже есть) — достаточно. - Запретить
git push *тоже — отклонено: push нужен для пуша feature-веток в remote (PR workflow). Push НЕ мутация в том же смысле, что commit/create/merge — push синхронизирует локальные коммиты с remote. Issue явно запрещает deny наgit push *.