fix(scripts): flag allow rules for secrets read/delete in check-permissions #61

Merged
slaid098 merged 3 commits from fix/scripts/flag-secrets-read-delete-allow into main 2026-08-12 15:51:13 +03:00
Owner

Что сделано

Расширил DANGEROUS_PATTERNS в .opencode/scripts/check-permissions.py — 16 новых паттернов, флагающих allow для чтения/удаления/копирования/перемещения секретных файлов (.env*):

  • Локальные: cat/head/tail/less/more/grep (чтение), rm (удаление), cp/mv (копирование/перемещение) — паттерны ^<cmd> .*\.env.
  • Git: git rm .env*, git -C * rm .env* — удаление секретов через git.
  • SSH: ssh * cat .env* (чтение на remote), ssh * rm .env* (удаление на remote) — закрывает дыру, которую не покрывал existing ssh * rm * (он ловит только через deny, а allow для ssh * rm .env* не флагался).
  • Docker: docker exec * cat .env*, docker exec * rm .env* — секреты в контейнерах.

Добавил 20 тест-кейсов в tests/test_check_permissions.py:

  • test_secrets_read_delete_pattern_violation (17 parametrized) — каждый новый паттерн флагается при allow.
  • test_rm_rf_env_already_caught_by_existing_pattern — edge case: rm -rf .env* ловится existing ^rm -rf паттерном (приоритет existing), hole closed.
  • test_secrets_deny_not_violation — deny/ask для секретов НЕ флагаются (контракт: только allow).
  • test_docker_rm_still_allowed — 5 паттернов из PR #55 (docker rm *, docker system prune*, docker volume rm *, git push --force*, git push -f*) продолжают НЕ флагаться (инвариант).

Отдельным коммитом: ruff format tests/test_create_changelog_tool.py (pre-existing formatting issue, пришёл с main — блокировал create-pr gate).

Почему

Линтер не флагал allow для rm .env* и cat .env* — прямая дыра безопасности (issue #57). Агент мог читать или удалять файлы секретов без ask/deny. Обнаружено во время PR #55 при проверке, что линтер ловит реальные угрозы после ослабления для docker/force-push паттернов. Новые паттерны попадают в проверку check_rules() автоматически (список расширен, функция не менялась). Scope "all" по умолчанию — секреты опасны везде (global + agents).

Watch out

  • Паттерны используют regex ^<cmd> .*\.env (.env без $), покрывая .env, .env.local, .env.production через суффикс.
  • rm -rf .env* ловится существующим паттерном ^rm -rf (reason "recursive force delete"), не новым — это норма, hole закрыт обоими, приоритет у existing.
  • Широкие cat */head*/tail* в opencode.json (global) и агентах НЕ флагаются — линтер проверяет точные ключи правил, а не резолвинг glob'ов; это по дизайну (агенты имеют broad allow для read-only операций, а .env* отдельно deny/ask).
  • CI не упадёт: проверено, что в текущем opencode.json и агентах нет allow для секретов (все .env* правила — deny/ask).

Pending

—

Closes #57

## Что сделано Расширил `DANGEROUS_PATTERNS` в `.opencode/scripts/check-permissions.py` — 16 новых паттернов, флагающих `allow` для чтения/удаления/копирования/перемещения секретных файлов (`.env*`): - **Локальные**: `cat`/`head`/`tail`/`less`/`more`/`grep` (чтение), `rm` (удаление), `cp`/`mv` (копирование/перемещение) — паттерны `^<cmd> .*\.env`. - **Git**: `git rm .env*`, `git -C * rm .env*` — удаление секретов через git. - **SSH**: `ssh * cat .env*` (чтение на remote), `ssh * rm .env*` (удаление на remote) — закрывает дыру, которую не покрывал existing `ssh * rm *` (он ловит только через `deny`, а `allow` для `ssh * rm .env*` не флагался). - **Docker**: `docker exec * cat .env*`, `docker exec * rm .env*` — секреты в контейнерах. Добавил 20 тест-кейсов в `tests/test_check_permissions.py`: - `test_secrets_read_delete_pattern_violation` (17 parametrized) — каждый новый паттерн флагается при `allow`. - `test_rm_rf_env_already_caught_by_existing_pattern` — edge case: `rm -rf .env*` ловится existing `^rm -rf` паттерном (приоритет existing), hole closed. - `test_secrets_deny_not_violation` — `deny`/`ask` для секретов НЕ флагаются (контракт: только `allow`). - `test_docker_rm_still_allowed` — 5 паттернов из PR #55 (`docker rm *`, `docker system prune*`, `docker volume rm *`, `git push --force*`, `git push -f*`) продолжают НЕ флагаться (инвариант). Отдельным коммитом: ruff format `tests/test_create_changelog_tool.py` (pre-existing formatting issue, пришёл с main — блокировал create-pr gate). ## Почему Линтер не флагал `allow` для `rm .env*` и `cat .env*` — прямая дыра безопасности (issue #57). Агент мог читать или удалять файлы секретов без `ask`/`deny`. Обнаружено во время PR #55 при проверке, что линтер ловит реальные угрозы после ослабления для docker/force-push паттернов. Новые паттерны попадают в проверку `check_rules()` автоматически (список расширен, функция не менялась). Scope `"all"` по умолчанию — секреты опасны везде (global + agents). ## Watch out - Паттерны используют regex `^<cmd> .*\.env` (`.env` без `$`), покрывая `.env`, `.env.local`, `.env.production` через суффикс. - `rm -rf .env*` ловится **существующим** паттерном `^rm -rf` (reason "recursive force delete"), не новым — это норма, hole закрыт обоими, приоритет у existing. - Широкие `cat *`/`head*`/`tail*` в `opencode.json` (global) и агентах НЕ флагаются — линтер проверяет точные ключи правил, а не резолвинг glob'ов; это по дизайну (агенты имеют broad allow для read-only операций, а `.env*` отдельно `deny`/`ask`). - CI не упадёт: проверено, что в текущем `opencode.json` и агентах нет `allow` для секретов (все `.env*` правила — `deny`/`ask`). ## Pending — Closes #57
style(tests): ruff format test_create_changelog_tool
All checks were successful
CI (always) / bootstrap (pull_request) Successful in 6s
CI / bootstrap (pull_request) Successful in 9s
Permission Security Check / check (pull_request) Successful in 13s
CI / lint (pull_request) Successful in 28s
CI / typecheck (pull_request) Successful in 29s
CI / complexity (pull_request) Successful in 27s
CI / test (3.13) (pull_request) Successful in 1m47s
eb01fdb6b8
Author
Owner

Code Review Summary

PR добавляет 16 новых паттернов в DANGEROUS_PATTERNS (check-permissions.py:100-114) для флага allow-правил на чтение/удаление/копирование/перемещение секретных файлов .env* (локально, через git, ssh, docker exec) + 20 тест-кейсов. Закрывает реальную дыру безопасности (issue #57): до фикса allow для rm .env* / cat .env* не флагалось. Качество высокое, тесты покрывают каждый паттерн и edge cases.

Positives

  • Покрытие угроз полное: 16 паттернов закрывают локальные (cat/head/tail/less/more/grep/rm/cp/mv), git (git rm, git -C * rm), ssh (ssh * cat, ssh * rm), docker (docker exec * cat, docker exec * rm) векторы. Regex ^<cmd> .*\.env без $ корректно покрывает .env.local/.env.production через суффикс.
  • Инвариант PR #55 сохранён: test_docker_rm_still_allowed явно проверяет 5 паттернов из PR #55 (docker rm *, docker system prune*, docker volume rm *, git push --force*, git push -f*) → 0 violations. Тест прошёл.
  • Edge case rm -rf .env* покрыт отдельным тестом (test_rm_rf_env_already_caught_by_existing_pattern): ловится existing ^rm -rf (приоритет first-match), hole закрыт обоими паттернами — корректно.
  • Контракт allow-only сохранён: test_secrets_deny_not_violation проверяет, что deny/ask для секретов НЕ флагаются.
  • Тесты проходят: 26/26 в test_check_permissions.py (uv run pytest). Линтер exit 0 на текущем конфиге подтверждён test_clean_configs_pass (subprocess black-box, returncode 0 + "OK" message) и test_permissions.py::test_check_permissions_passes. CI green (pipeline-status).
  • PR body: 4 heading'а (## Что сделано, ## Почему, ## Watch out, ## Pending) заполнены осмысленно; ## Pending = — (допустимо). Closes #57.
  • Code quality: файл 188 строк (< 300), check_rules 15 строк (< 50), main 21 строка. Новые паттерны — 2-элементные tuples (scope "all" по умолчанию через entry[2] if len(entry) > 2), существующие agent-scoped — 3-элементные. Стиль консистентен.
  • Cross-file impact: DANGEROUS_PATTERNS (writer) → readers: check_rules() (paired в PR, тот же файл), tests/test_check_permissions.py (paired в PR), tests/test_permissions.py::test_check_permissions_passes (black-box, не зависит от количества паттернов), CI permissions-check.yml (запускает скрипт, не парсит список). Missing paired update нет.

Suggestions (info, not blocking)

  • check-permissions.py:100-114 [style] 16 новых 2-элементных tuples визуально отличаются от 3-элементных agent-scoped выше (строки 13-53). Можно добавить комментарий-разделитель (но AGENTS.md запрещает комментарии без запроса — оставлено как есть, корректно).
  • test_check_permissions.py:135 [style] grep * .env* — паттерн с пробелом между * и .env* (соответствует regex ^grep .*\.env). Покрыто, но стоит иметь в виду, что grep .env* (без *) тоже матчится тем же regex — тест можно было бы расширить, но не критично.

Verdict: APPROVE

## Code Review Summary PR добавляет 16 новых паттернов в `DANGEROUS_PATTERNS` (`check-permissions.py:100-114`) для флага `allow`-правил на чтение/удаление/копирование/перемещение секретных файлов `.env*` (локально, через git, ssh, docker exec) + 20 тест-кейсов. Закрывает реальную дыру безопасности (issue #57): до фикса `allow` для `rm .env*` / `cat .env*` не флагалось. Качество высокое, тесты покрывают каждый паттерн и edge cases. ### Positives - **Покрытие угроз полное**: 16 паттернов закрывают локальные (`cat`/`head`/`tail`/`less`/`more`/`grep`/`rm`/`cp`/`mv`), git (`git rm`, `git -C * rm`), ssh (`ssh * cat`, `ssh * rm`), docker (`docker exec * cat`, `docker exec * rm`) векторы. Regex `^<cmd> .*\.env` без `$` корректно покрывает `.env.local`/`.env.production` через суффикс. - **Инвариант PR #55 сохранён**: `test_docker_rm_still_allowed` явно проверяет 5 паттернов из PR #55 (`docker rm *`, `docker system prune*`, `docker volume rm *`, `git push --force*`, `git push -f*`) → 0 violations. Тест прошёл. - **Edge case `rm -rf .env*`** покрыт отдельным тестом (`test_rm_rf_env_already_caught_by_existing_pattern`): ловится existing `^rm -rf` (приоритет first-match), hole закрыт обоими паттернами — корректно. - **Контракт `allow`-only сохранён**: `test_secrets_deny_not_violation` проверяет, что `deny`/`ask` для секретов НЕ флагаются. - **Тесты проходят**: 26/26 в `test_check_permissions.py` (uv run pytest). Линтер exit 0 на текущем конфиге подтверждён `test_clean_configs_pass` (subprocess black-box, returncode 0 + "OK" message) и `test_permissions.py::test_check_permissions_passes`. CI green (pipeline-status). - **PR body**: 4 heading'а (`## Что сделано`, `## Почему`, `## Watch out`, `## Pending`) заполнены осмысленно; `## Pending` = `—` (допустимо). Closes #57. - **Code quality**: файл 188 строк (< 300), `check_rules` 15 строк (< 50), `main` 21 строка. Новые паттерны — 2-элементные tuples (scope `"all"` по умолчанию через `entry[2] if len(entry) > 2`), существующие agent-scoped — 3-элементные. Стиль консистентен. - **Cross-file impact**: `DANGEROUS_PATTERNS` (writer) → readers: `check_rules()` (paired в PR, тот же файл), `tests/test_check_permissions.py` (paired в PR), `tests/test_permissions.py::test_check_permissions_passes` (black-box, не зависит от количества паттернов), CI `permissions-check.yml` (запускает скрипт, не парсит список). Missing paired update нет. ### Suggestions (info, not blocking) - **check-permissions.py:100-114** [style] 16 новых 2-элементных tuples визуально отличаются от 3-элементных agent-scoped выше (строки 13-53). Можно добавить комментарий-разделитель (но AGENTS.md запрещает комментарии без запроса — оставлено как есть, корректно). - **test_check_permissions.py:135** [style] `grep * .env*` — паттерн с пробелом между `*` и `.env*` (соответствует regex `^grep .*\.env`). Покрыто, но стоит иметь в виду, что `grep .env*` (без `*`) тоже матчится тем же regex — тест можно было бы расширить, но не критично. ### Verdict: APPROVE
slaid098 deleted branch fix/scripts/flag-secrets-read-delete-allow 2026-08-12 15:51:14 +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!61
No description provided.