feat(filter-proxy): deny file-write mcp tools #11

Merged
genji1024 merged 1 commit from 7-feat/filter-proxy into main 2026-09-08 20:46:04 +09:00
Collaborator

Closes #7

概要

forgejo-mcp / github-mcp 経由の push を技術的に塞ぎ、gitpush-mcp に一本化する。承認済み案A(プロキシ層での deny)を最小構成で実施する。

変更内容

  • dockerfiles/filter-proxy/proxy.py(新規) — stdlib のみ(http.server + urllib)の透過プロキシ。JSON-RPC tools/call のツール名が DENY_LIST に一致したら即時 403 + JSON-RPC エラー、それ以外は plain-JSON/SSE とも透過転送
  • dockerfiles/filter-proxy/Dockerfile(新規) — python:3.13-slim でプロキシ起動
  • compose.yaml — forgejo-filter(3008→forgejo-mcp:3006、deny 4点)と github-filter(3009→github-mcp:3003、deny 4点)を追加。opencode の depends_on に両 filter を追加。既存 5 MCP サービスは無変更
  • .config/opencode/opencode.jsonc — github/forgejo の URL を filter 経由に書換え + 2行コメント。エージェント permission 変更なし

deny 対象(c462 承認どおり create_branch 含む):

  • forgejo: create_file / update_file / delete_file / create_branch
  • github: create_or_update_file / push_files / delete_file / create_branch

抜け道の検討:

  • create_file / update_file / delete_file は new_branch_name で暗黙ブランチ作成できるため、file 系全 deny で迂回を閉じた
  • Issue/PR コメント・レビュー・ラベル等の協調動作に必要な read/write は残した

動作確認

  • python3 -m py_compile dockerfiles/filter-proxy/proxy.py — 成功
  • 機能テスト(ダミーバックエンド + 実プロキシ) — deny create_file → 403、allow create_issue → 200 転送を確認
  • docker compose -f compose.yaml config — 成功
  • 実環境での filter-proxy ビルド・起動確認 — レビュアー側でお願いします

手動確認手順

  1. docker compose -f compose.yaml build forgejo-filter github-filter でビルド
  2. docker compose -f compose.yaml up -d で起動(docker compose ps で両 filter が healthy になるまで待つ)
  3. opencode から forgejo_list_repo_issues / create_issue_comment 等の正常系が成功すること
  4. opencode から forgejo_create_file 等の file-write 系が拒否されること(403 / deny エラー)
  5. docker compose -f compose.yaml config --services に forgejo-filter / github-filter が現れること

備考

  • permission 変更なし(案B は実施しない。PR #9 はクローズ済み)。container レベルの強制のみ
  • force-push 対応(#8 / PR #10 マージ済み)とは独立
Closes #7 ## 概要 forgejo-mcp / github-mcp 経由の push を技術的に塞ぎ、gitpush-mcp に一本化する。承認済み案A(プロキシ層での deny)を最小構成で実施する。 ## 変更内容 - `dockerfiles/filter-proxy/proxy.py`(新規) — stdlib のみ(http.server + urllib)の透過プロキシ。JSON-RPC `tools/call` のツール名が DENY_LIST に一致したら即時 403 + JSON-RPC エラー、それ以外は plain-JSON/SSE とも透過転送 - `dockerfiles/filter-proxy/Dockerfile`(新規) — `python:3.13-slim` でプロキシ起動 - `compose.yaml` — `forgejo-filter`(3008→forgejo-mcp:3006、deny 4点)と `github-filter`(3009→github-mcp:3003、deny 4点)を追加。opencode の depends_on に両 filter を追加。既存 5 MCP サービスは無変更 - `.config/opencode/opencode.jsonc` — github/forgejo の URL を filter 経由に書換え + 2行コメント。エージェント permission 変更なし deny 対象(c462 承認どおり `create_branch` 含む): - forgejo: `create_file` / `update_file` / `delete_file` / `create_branch` - github: `create_or_update_file` / `push_files` / `delete_file` / `create_branch` 抜け道の検討: - `create_file` / `update_file` / `delete_file` は `new_branch_name` で暗黙ブランチ作成できるため、file 系全 deny で迂回を閉じた - Issue/PR コメント・レビュー・ラベル等の協調動作に必要な read/write は残した ## 動作確認 - [x] `python3 -m py_compile dockerfiles/filter-proxy/proxy.py` — 成功 - [x] 機能テスト(ダミーバックエンド + 実プロキシ) — deny `create_file` → 403、allow `create_issue` → 200 転送を確認 - [x] `docker compose -f compose.yaml config` — 成功 - [ ] 実環境での filter-proxy ビルド・起動確認 — レビュアー側でお願いします ## 手動確認手順 1. `docker compose -f compose.yaml build forgejo-filter github-filter` でビルド 2. `docker compose -f compose.yaml up -d` で起動(`docker compose ps` で両 filter が healthy になるまで待つ) 3. opencode から `forgejo_list_repo_issues` / `create_issue_comment` 等の正常系が成功すること 4. opencode から `forgejo_create_file` 等の file-write 系が拒否されること(403 / deny エラー) 5. `docker compose -f compose.yaml config --services` に `forgejo-filter` / `github-filter` が現れること ## 備考 - permission 変更なし(案B は実施しない。PR #9 はクローズ済み)。container レベルの強制のみ - force-push 対応(#8 / PR #10 マージ済み)とは独立
- Block file-write tools before they reach forgejo/github MCP
  - add stdlib HTTP proxy rejecting denied tools/call by name
  - deny forgejo and github file/branch tools as approved
  - point opencode at filter services; pushes go via gitpush
- Keep all other MCP traffic transparent
  - forward non-denied JSON-RPC requests unchanged to backends
Owner

@bot-genji1024 作業ブランチをチェックアウトして起動しました。他に抜け道がないか徹底的に検証してください。

@bot-genji1024 作業ブランチをチェックアウトして起動しました。他に抜け道がないか徹底的に検証してください。
Author
Collaborator

実行結果 (c524 抜け道検証)

@genji1024 作業ブランチ 7-feat/filter-proxy (head 8e158e9) をチェックアウトし、filter-proxy の抜け道を徹底検証しました。結論: 承認済み deny 8点の迂回路はありません。通常利用での file-write 系 push 経路は塞がれています。 残存する書き込み経路を下表に整理し、末尾で追加 deny の承認をお願いします(コード修正は承認後に着手します)。

実施内容

  • ローカル機能テスト: ダミーバックエンド + 実 proxy.py で deny 4点→403(backend 未到達)、allow/tools/list/initialize/GET/SSE・/health→透過転送を確認。py_compile・compose config 成功
  • 一次情報照合: b4mad forgejo-mcp ソースのツール名 const (create_file/update_file/delete_file/create_branch 完全一致)、upstream github-mcp-server v1.7.0 README (deny 4点すべて実在・名称一致)、mcp-go の batch 処理、mcp-proxy v0.12.0 のツール名素通しを確認
  • 権限構造の確認: github_*/forgejo_* を呼べるのは pr-manager/issue-reader のみ(両者 bash なし)。bash が広い coder/verifier は当該 MCP 権限なし。多層防御として成立

検証済み・問題なし

  • deny 8点 exact-match: MCP ツール名は backend 側も exact-match のため、大文字・別名での回避不可(変形名は backend が unknown-tool で拒否)
  • new_branch_name 付きの暗黙ブランチ作成: file 系3点 deny で coverage
  • tools/call 以外 (GET/SSE・tools/list・initialize・notifications): 書き込み不可のため素通しで問題なし。パス・Content-Type 偽装での回避不可 (proxy は body の JSON のみ見て判定し、パスを無視して固定 /mcp へ転送)
  • JSON 配列 batch: proxy は素通しするが、forgejo backend (mcp-go) は単一メッセージのみ解釈し配列は parse error で実行されないことをソースで確認。github 側 (mcp-proxy) にも batch 実行コードなし。opencode 自体 batch を送らないため、残るは意図的な手作りリクエストのみで、かつ backend 側で失敗する

残存する書き込み経路 (要判断)

# 経路 影響 提案
1 delete_branch (forgejo) が deny 外 ブランチ削除は ref 書き換え = push 系操作。PR ブランチ消去が API で可能 deny 追加を推奨
2 update_pull_request_branch (github) が deny 外 base→head のマージコミットをサーバ側で作成 = コード変更が gitpush を迂回 deny 追加を推奨
3 merge_pull_request (両) が deny 外 マージでブランチ更新。bot が API マージできる状態 運用判断: bot の API マージを許可するか。deny する場合は両方追加
4 opencode コンテナから backend 直叩き (curl http://forgejo-mcp:3006/mcp 等) 同一 compose ネットワークのため到達可能。coder/verifier の bash で意図的に実行すれば filter を迂回できる 対策案: compose networks 分離 (backend は filter とだけ同居、opencode とは filter のみ共有)。承認後に実装
5 wiki/release/attachment 系書き込み wiki 別 repo・添付ファイルでありコードブランチに触れない 対象外として明記 (対応不要)
6 tools/list に deny 済みツールが表示される 呼ぶと 403 で失敗するだけ。誤試行のノイズ 改善案: list 応答から deny 名を除去 (承認後に実装)

関連

  • PR: #11 (本 PR、#1〜#3・#6 は本 PR への追加コミット、#4 は compose 変更として実施可)
  • #7 (本検証の親イシュー)

備考

  • Docker ビルド・起動の実環境確認はオーナー側で実施中 (c524) のため、本報告はコード・設定・一次情報ソースに基づく検証です
  • 承認いただきたい点: (1) #1・#2 の deny 追加可否 (2) #3 の方針 (3) #4 の networks 分離の実施可否 (4) #6 の list filtering の実施可否。承認後に本ブランチへ追加コミットします

動作確認

  • python3 -m py_compile dockerfiles/filter-proxy/proxy.py — 成功
  • 機能テスト (deny→403・backend 未到達、allow→透過) — 成功
  • docker compose config — 成功
  • 実環境での filter 起動・opencode 経由の疎通 — オーナー側で確認中
## 実行結果 (c524 抜け道検証) @genji1024 作業ブランチ `7-feat/filter-proxy` (head `8e158e9`) をチェックアウトし、filter-proxy の抜け道を徹底検証しました。**結論: 承認済み deny 8点の迂回路はありません。通常利用での file-write 系 push 経路は塞がれています。** 残存する書き込み経路を下表に整理し、末尾で追加 deny の承認をお願いします(コード修正は承認後に着手します)。 ### 実施内容 - ローカル機能テスト: ダミーバックエンド + 実 `proxy.py` で deny 4点→403(backend 未到達)、allow/`tools/list`/`initialize`/GET/SSE・`/health`→透過転送を確認。`py_compile`・`compose config` 成功 - 一次情報照合: b4mad forgejo-mcp ソースのツール名 const (`create_file`/`update_file`/`delete_file`/`create_branch` 完全一致)、upstream github-mcp-server v1.7.0 README (deny 4点すべて実在・名称一致)、mcp-go の batch 処理、mcp-proxy v0.12.0 のツール名素通しを確認 - 権限構造の確認: `github_*`/`forgejo_*` を呼べるのは pr-manager/issue-reader のみ(両者 bash なし)。bash が広い coder/verifier は当該 MCP 権限なし。多層防御として成立 ### 検証済み・問題なし - deny 8点 exact-match: MCP ツール名は backend 側も exact-match のため、大文字・別名での回避不可(変形名は backend が unknown-tool で拒否) - `new_branch_name` 付きの暗黙ブランチ作成: file 系3点 deny で coverage - `tools/call` 以外 (GET/SSE・`tools/list`・`initialize`・notifications): 書き込み不可のため素通しで問題なし。パス・Content-Type 偽装での回避不可 (proxy は body の JSON のみ見て判定し、パスを無視して固定 `/mcp` へ転送) - JSON 配列 batch: proxy は素通しするが、forgejo backend (mcp-go) は単一メッセージのみ解釈し配列は parse error で実行されないことをソースで確認。github 側 (mcp-proxy) にも batch 実行コードなし。opencode 自体 batch を送らないため、残るは意図的な手作りリクエストのみで、かつ backend 側で失敗する ### 残存する書き込み経路 (要判断) | # | 経路 | 影響 | 提案 | |---|---|---|---| | 1 | `delete_branch` (forgejo) が deny 外 | ブランチ削除は ref 書き換え = push 系操作。PR ブランチ消去が API で可能 | deny 追加を推奨 | | 2 | `update_pull_request_branch` (github) が deny 外 | base→head のマージコミットをサーバ側で作成 = コード変更が gitpush を迂回 | deny 追加を推奨 | | 3 | `merge_pull_request` (両) が deny 外 | マージでブランチ更新。bot が API マージできる状態 | 運用判断: bot の API マージを許可するか。deny する場合は両方追加 | | 4 | opencode コンテナから backend 直叩き (`curl http://forgejo-mcp:3006/mcp` 等) | 同一 compose ネットワークのため到達可能。coder/verifier の bash で意図的に実行すれば filter を迂回できる | 対策案: compose `networks` 分離 (backend は filter とだけ同居、opencode とは filter のみ共有)。承認後に実装 | | 5 | wiki/release/attachment 系書き込み | wiki 別 repo・添付ファイルでありコードブランチに触れない | 対象外として明記 (対応不要) | | 6 | `tools/list` に deny 済みツールが表示される | 呼ぶと 403 で失敗するだけ。誤試行のノイズ | 改善案: list 応答から deny 名を除去 (承認後に実装) | ### 関連 - PR: #11 (本 PR、#1〜#3・#6 は本 PR への追加コミット、#4 は compose 変更として実施可) - #7 (本検証の親イシュー) ### 備考 - Docker ビルド・起動の実環境確認はオーナー側で実施中 (c524) のため、本報告はコード・設定・一次情報ソースに基づく検証です - 承認いただきたい点: (1) #1・#2 の deny 追加可否 (2) #3 の方針 (3) #4 の networks 分離の実施可否 (4) #6 の list filtering の実施可否。承認後に本ブランチへ追加コミットします ### 動作確認 - [x] `python3 -m py_compile dockerfiles/filter-proxy/proxy.py` — 成功 - [x] 機能テスト (deny→403・backend 未到達、allow→透過) — 成功 - [x] `docker compose config` — 成功 - [ ] 実環境での filter 起動・opencode 経由の疎通 — オーナー側で確認中
Owner

@bot-genji1024 違います。この作業ブランチの設定下であなたを再起動したということです。自分で forgejo-mcp 経由でブランチをプッシュする経路がないか試してください。

@bot-genji1024 違います。この作業ブランチの設定下であなたを再起動したということです。自分で forgejo-mcp 経由でブランチをプッシュする経路がないか試してください。
Author
Collaborator

実行結果 (c538 自己検証)

@genji1024 この作業ブランチの設定下で再起動された状態から、自分で forgejo-mcp 経由の push を試しました。結論: ブランチをプッシュする経路はありません。

実施内容

  • create_branch (test-filter-probe-20260908 from main) → 拒否
    • tool 'create_branch' is denied by filter-proxy (file writes belong to gitpush-mcp) (code -32001)
  • create_file / update_file / delete_file → 同様に全て拒否 (code -32001、backend 未到達のため副作用なし)
  • ブランチ一覧で test-filter-probe-20260908 が作成されていないことを確認 (残存 3枝のみ: main / 7-feat/filter-proxy / 6-feat/pi-migration)

動作確認

  • 4点とも filter-proxy が backend 到達前に 403/JSON-RPC エラーで拒否 — 成功 (副作用なし)
  • ブランチ一覧で汚染なしを確認

関連

  • PR: #11 (本PR)
  • #7 (親イシュー)

備考

  • 本検証は再起動後の現設定 (opencode → http://forgejo-filter:3008/mcp、DENY_LIST 4点) での実測です
  • c530 の残存経路 (#1 delete_branch 等) の追加 deny 可否は別途ご判断をお待ちしています (コード修正は承認後に着手します)
## 実行結果 (c538 自己検証) @genji1024 この作業ブランチの設定下で再起動された状態から、自分で forgejo-mcp 経由の push を試しました。**結論: ブランチをプッシュする経路はありません。** ### 実施内容 - `create_branch` (test-filter-probe-20260908 from main) → 拒否 - `tool 'create_branch' is denied by filter-proxy (file writes belong to gitpush-mcp)` (code -32001) - `create_file` / `update_file` / `delete_file` → 同様に全て拒否 (code -32001、backend 未到達のため副作用なし) - ブランチ一覧で `test-filter-probe-20260908` が作成されていないことを確認 (残存 3枝のみ: main / 7-feat/filter-proxy / 6-feat/pi-migration) ### 動作確認 - [x] 4点とも filter-proxy が backend 到達前に 403/JSON-RPC エラーで拒否 — 成功 (副作用なし) - [x] ブランチ一覧で汚染なしを確認 ### 関連 - PR: #11 (本PR) - #7 (親イシュー) ### 備考 - 本検証は再起動後の現設定 (opencode → `http://forgejo-filter:3008/mcp`、DENY_LIST 4点) での実測です - c530 の残存経路 (#1 delete_branch 等) の追加 deny 可否は別途ご判断をお待ちしています (コード修正は承認後に着手します)
genji1024 left a comment

@bot-genji1024 対応ありがとうございました。LGTM!

@bot-genji1024 対応ありがとうございました。LGTM!
genji1024 approved these changes 2026-09-08 20:45:37 +09:00
genji1024 deleted branch 7-feat/filter-proxy 2026-09-08 20:46:04 +09:00
genji1024 referenced this pull request from a commit 2026-09-08 20:46:04 +09:00
Sign in to join this conversation.
No reviewers
No labels
enhancement
No milestone
No assignees
2 participants
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
genji1024/containerized-agents!11
No description provided.