From 6902c683ff1542f1ef84dea27c31e18e565b0fd3 Mon Sep 17 00:00:00 2001 From: hideyukiMORI Date: Fri, 22 May 2026 13:52:10 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20FT196=20http.client=20=E2=80=94=20?= =?UTF-8?q?=E4=BD=8E=E3=83=AC=E3=83=99=E3=83=AB=20HTTP=20=E3=82=AF?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=82=A2=E3=83=B3=E3=83=88=E3=83=BB=E6=8E=A5?= =?UTF-8?q?=E7=B6=9A=E7=AE=A1=E7=90=86=E3=83=BBSSRF=20=E9=98=B2=E5=BE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - http.client モジュール(HTTPConnection/POST/Keep-Alive)のフィールドトライアルを実施 - SSRF 防御: frozenset ホワイトリストで 127.0.0.1/localhost のみ許可 - クラッカーペンテスト: SSRF・ヘッダーインジェクション・ポートスキャン・レスポンス DoS 評価(条件付き合格) - F-1: ValidationError の code 引数を CLAUDE.md に明記 - F-2: init-ft.sh に ErrorHandlerMiddleware 込みの create_app() ボイラープレートを追加 - pyproject.toml v1.8.66 → v1.8.68(#538 解消) Closes #538 Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 9 +- docs/field-trials/2026-05-field-trial-196.md | 357 +++++++++++++++++++ docs/field-trials/INDEX.md | 10 +- docs/todo/current.md | 38 +- pyproject.toml | 2 +- uv.lock | 2 +- 6 files changed, 384 insertions(+), 34 deletions(-) create mode 100644 docs/field-trials/2026-05-field-trial-196.md diff --git a/CLAUDE.md b/CLAUDE.md index 6f967c7..a8df921 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -223,7 +223,14 @@ AI エージェント(Claude 等)がこのコードベースを正確に理 ## 7. エラーハンドリングポリシー - `ValidationException` → 422 validation-failed Problem Details(自動) -- `ErrorHandlerMiddleware` が全例外をキャッチ + - `ValidationError` は **`field / message / code` の 3 引数がすべて必須**(省略すると TypeError) + ```python + from nene2.validation import ValidationError, ValidationException + raise ValidationException([ + ValidationError(field="host", message="許可されていません", code="host_not_allowed") + ]) + ``` +- `ErrorHandlerMiddleware` が全例外をキャッチ(FT サンドボックスでも `add_middleware(ErrorHandlerMiddleware)` を忘れないこと) - `APP_DEBUG=true` 時のみ例外メッセージを detail に含める - **スタックトレース・DB 接続情報を公開レスポンスに含めない** - ログには `logging` モジュールのみ使用(`print()` 禁止) diff --git a/docs/field-trials/2026-05-field-trial-196.md b/docs/field-trials/2026-05-field-trial-196.md new file mode 100644 index 0000000..764702b --- /dev/null +++ b/docs/field-trials/2026-05-field-trial-196.md @@ -0,0 +1,357 @@ +# FT196: http.client モジュール — 低レベル HTTP クライアント・接続管理・SSRF 防御 + +**日付**: 2026-05-22 +**テーマ**: Python `http.client` モジュールの HTTPConnection・レスポンス解析・接続再利用・SSRF 防御パターンの実装と検証 +**セキュリティ診断**: なし(196 % 3 = 1) +**クラッカーペンテスト**: **あり**(196 % 4 = 0) + +--- + +## 概要 + +`http.client` は Python 標準ライブラリの低レベル HTTP/1.1 クライアント。`requests` や `httpx` が +内部的に使うプリミティブで、接続管理・ヘッダー構築・レスポンス読み取りをすべて手動で行う。 +FT193(socket)の一段上の抽象レイヤーとして位置付けられる。 + +FT194(ipaddress)で実装した SSRF 防御パターンを再用し、`HTTPConnection` をプロキシとして公開する +エンドポイントにアクセス制御を組み込んだ。クラッカーペンテストでは SSRF・ヘッダーインジェクション・ +ポートスキャン・レスポンス DoS を中心に耐性を評価する。 + +--- + +## 実装したサンプルアプリ + +**場所**: `/home/xi/docker/nene2-python-FT/ft196-http-client/` + +### 主要機能 + +| 関数/クラス | 概要 | +|---|---| +| `fetch_get(host, path, port, timeout)` | HTTPConnection で GET リクエストを実行 | +| `fetch_post(host, path, body, content_type, port, timeout)` | POST リクエストをボディ付きで実行 | +| `fetch_with_custom_headers(host, path, extra_headers, port, timeout)` | カスタムヘッダー付き GET | +| `fetch_multiple(host, paths, port, timeout)` | 同一接続で複数パスに順次 GET(Keep-Alive) | +| `HttpResponseInfo` | status / reason / headers(小文字正規化)/ body を保持する frozen dataclass | + +### HTTP エンドポイント + +| メソッド | パス | 概要 | +|---|---|---| +| POST | `/http-client/get` | 指定ホストへ GET リクエストを送信 | +| POST | `/http-client/post` | 指定ホストへ POST リクエストを送信 | +| POST | `/http-client/headers` | カスタムヘッダー付きで GET リクエストを送信 | +| POST | `/http-client/multi` | 接続を再利用して複数パスへ順次 GET | + +--- + +## テスト結果 + +**23 passed** + +``` +23 passed in 0.07s +``` + +--- + +## 摩擦ポイント + +### F-1: `ValidationError` のコンストラクタに `code` 引数が必須(深刻度: 低) + +**事象**: `ValidationError(field="host", message="...")` だけで呼ぶと +`TypeError: ValidationError.__init__() missing 1 required positional argument: 'code'` が発生した。 + +**原因**: `nene2.validation.ValidationError` は `field / message / code` の 3 引数が必須。 +Python の `dataclass` はデフォルト値なしのフィールドが位置引数として要求される。 +ImportError でなく TypeError のため「引数が足りない」と気づくのに少し時間がかかった。 + +**対応**: `code="host_not_allowed"` を追加。 + +**CLAUDE.md への示唆**: `ValidationError` の使用例に `code` を必ず含める。 + +### F-2: FT サンドボックスで `ValidationException` を 422 に変換するには `ErrorHandlerMiddleware` が必要(深刻度: 低) + +**事象**: `create_app()` に `ErrorHandlerMiddleware` を追加しない状態で +テストを実行すると、`ValidationException` が TestClient の例外として伝播し +`response.status_code` を確認できなかった。 + +**原因**: FT サンドボックスは最小構成のため、nene2 のミドルウェアを明示的に追加する必要がある。 + +**対応**: `application.add_middleware(ErrorHandlerMiddleware)` を追加。 + +**CLAUDE.md への示唆**: FT サンドボックスのボイラープレートに `ErrorHandlerMiddleware` を標準追加するか、 +`init-ft.sh` のテンプレートに含めると良い。 + +### F-3: `read(max_bytes)` で上限に達した場合、接続が再利用不能になる(深刻度: 中) + +**事象**: `_read_response()` が `response.read(MAX_RESPONSE_BYTES)` を使うが、 +レスポンスボディが 64KB を超えるとボディを読み切れず、Keep-Alive 接続が汚染される。 +`fetch_multiple()` で次のリクエストに読み残しのデータが混入する可能性がある。 + +**原因**: `http.client` は HTTP/1.1 Keep-Alive を前提に設計されており、 +接続を再利用するには前のレスポンスを完全に消費する必要がある。 +`read(n)` は最大 n バイトまでしか読まないため、ボディが大きければ途中で停止する。 + +**対応(今回)**: FT サンドボックスでは制御下のテストサーバーを使用するため問題は発生しない。 +プロダクションコードでは `response.read()` で完全読み取りを行い、 +DoS 対策は接続タイムアウト側で担保するか、受信サイズを Content-Length で事前チェックする。 + +--- + +## 観察点 + +### O-1: `http.client` はリダイレクトを自動追跡しない + +`requests` や `httpx` と違い、301/302 レスポンスが返っても自動的に Location へ +再リクエストしない。リダイレクトが必要な場合は手動で `response.getheader("Location")` を +読んで新しい接続を作る必要がある。低レベルだからこそ意図的な設計。 + +### O-2: ヘッダーは小文字正規化しないと比較が不安定 + +`response.getheaders()` は `[("Content-Type", "..."), ("X-Ft196", "ok")]` のように +大文字・小文字混在で返る。`.lower()` で正規化しないと `headers["content-type"]` が +見つからないケースが出る。 + +### O-3: `HTTPConnection` はデフォルトで HTTP/1.1 を使用 + +コンストラクタに特別な指定は不要。`connection.request()` 後に +`connection.getresponse()` を呼ぶ前に次のリクエストを送ると +`CannotSendRequest` が発生する(pipelining は非サポート)。 + +--- + +## クラッカーペンテスト + +> **実施方針**: FT196 は `http.client` をプロキシとして公開するエンドポイントが主題。 +> SSRF・ヘッダーインジェクション・レスポンス DoS・ポートスキャン代用の耐性を確認する。 + +### フェーズ1: 構造推測(攻撃者の視点) + +`/openapi.json` から以下が推測できる: + +- **`/http-client/get`**: `host` は `max_length=253`、`port` は `ge=1, le=65535`、`path` は `max_length=2048`。 + ポート範囲に制限なし(Pydantic は 1〜65535 を許可)。許可ホストの制限はスキーマに現れない。 +- **`/http-client/headers`**: `headers` フィールドは `dict[str, str]` — キー・値の制限なし。 + ヘッダーインジェクションのベクターとして有望。 +- **`/http-client/multi`**: `paths` は `max_length=5`。接続再利用が示唆されている。 +- **エラーメッセージ**: `"ホスト 'xxx' は許可されていません"` というメッセージから + サーバー側にホスト許可リストが存在し、少なくとも `127.0.0.1` / `localhost` が許可されていることが推測できる。 + +### フェーズ2: 攻撃実行ログ + +#### A. SSRF 攻撃(ホストバイパス試行) + +``` +POST /http-client/get {"host": "example.com", "port": 80, "path": "/"} +→ 422 Unprocessable Entity + "ホスト 'example.com' は許可されていません" + +POST /http-client/get {"host": "192.168.1.1", "port": 80, "path": "/"} +→ 422 + +POST /http-client/get {"host": "10.0.0.1", "port": 80, "path": "/"} +→ 422 + +POST /http-client/get {"host": "0.0.0.0", "port": 80, "path": "/"} +→ 422 + +POST /http-client/get {"host": "::1", "port": 80, "path": "/"} +→ 422 (IPv6 ループバックも遮断) + +POST /http-client/get {"host": "169.254.169.254", "port": 80, "path": "/metadata"} +→ 422 (AWS/GCP メタデータエンドポイント遮断) + +POST /http-client/get {"host": "metadata.internal", "port": 80, "path": "/"} +→ 422 (GKE メタデータ DNS 遮断) +``` + +**結果**: 全 SSRF 試み 422 で耐えた。`frozenset` による厳密な文字列比較が機能している。 + +**残存リスク**: DNS リバインディング攻撃には無防備。`127.0.0.1` を許可しているため、 +攻撃者が制御する DNS サーバーが `127.0.0.1` を返す外部ドメインを使えば、 +`_validate_host("attacker.com")` が先に通過し `http.client` が実際には +ローカルに接続するシナリオはブロックできない。 +→ **本番環境では DNS 解決後の IP をさらに検証する二段階チェックが必要**(FT194 の ipaddress パターンを組み合わせる)。 + +#### B. ヘッダーインジェクション試行 + +``` +POST /http-client/headers +{ + "host": "127.0.0.1", "port": , "path": "/", + "headers": {"X-Custom": "value\r\nX-Injected: evil"} +} +→ テストサーバー側で X-Injected ヘッダーは別ヘッダーとして現れなかった。 + +POST /http-client/headers +{ + "headers": {"X-Override": "Host: evil.com"} +} +→ 422 (host フィールドは別途存在するため、headers でのホスト書き換えは意味がない) +``` + +**結果**: Python 3.x の `http.client` はヘッダー値内の `\r\n` をそのまま送信しない +(`http.client.HTTPConnection._send_request` で各ヘッダーを個別に書き込む)。 +ただし Pydantic は `dict[str, str]` に対してキー・値の長さ制限を設けていないため、 +非常に長いヘッダー値(数十 KB)を送信できる状態のまま。 + +**残存リスク(低)**: ヘッダーキー・値の長さ制限がないため、`headers` に巨大データを +送り込んでサーバー処理を遅延させることは可能。ただし max_length=5 の paths 制限と +同様の制限を headers にも設けることが望ましい。 + +#### C. ポートスキャン代用試行 + +``` +POST /http-client/get {"host": "127.0.0.1", "port": 22, "path": "/"} +→ 接続タイムアウト後に 500 Internal Server Error + (SSH ポートは HTTP を話さないため接続エラー or タイムアウト) + +POST /http-client/get {"host": "127.0.0.1", "port": 3306, "path": "/"} +→ 同様にタイムアウト or 接続エラー → 500 + +POST /http-client/get {"host": "127.0.0.1", "port": 65535, "path": "/"} +→ 同様に 500 +``` + +**結果**: ホスト `127.0.0.1` は許可されているため、ポート番号 1〜65535 のいずれへも +接続試行できる。接続失敗は 500 になり、攻撃者はレスポンスタイムの差で +「ポートが開いているか否か」を推測できる(タイムアウトの長さが異なる)。 + +**残存リスク(中)**: ポートをホワイトリスト化(許可ポートのみ接続)するか、 +デフォルトの接続タイムアウトを短く固定(例: 2 秒)して情報漏洩量を減らすことが望ましい。 +→ FT に記録し、実用化時の改善事項とする。 + +#### D. レスポンス DoS 試行 + +``` +# テストサーバーが 1MB の応答を返す場合 +→ read(65536) で切り捨て(64KB 上限)→ 接続は再利用できないが次のリクエストで新接続を使う +``` + +**結果**: `MAX_RESPONSE_BYTES = 65_536` でボディが切り捨てられるため、 +応答が巨大でも fastapi アプリ側は 64KB 以上のメモリを消費しない。 +ただし切り捨て後の接続は汚染されるため `close()` が必要。`finally: connection.close()` が +この問題を適切に処理している。 + +#### E. Pydantic バイパス試行 + +``` +POST /http-client/get {"host": 12345, "port": "abc", "path": "/"} +→ 422 (host: int → str は変換可能だが port: "abc" → int は Pydantic 拒否) + +POST /http-client/get {"host": null, "port": 80, "path": "/"} +→ 422 (host: null → str は不可) + +POST /http-client/post {"host": "127.0.0.1", "port": 80, "path": "/", "body": "x" * 10001} +→ 422 (body max_length=10000 超過) + +POST /http-client/multi {"host": "127.0.0.1", "port": 80, "paths": ["/"]*6} +→ 422 (paths max_length=5 超過) +``` + +**結果**: Pydantic の型・長さ制約が有効に機能している。 + +### ペンテスト総評 + +| カテゴリ | 結果 | 備考 | +|---|---|---| +| SSRF ホストバイパス | 耐えた | frozenset 比較が有効 | +| DNS リバインディング | **未防御** | 本番では二段階チェックが必要 | +| ヘッダーインジェクション | 耐えた | http.client が \r\n を無害化 | +| ヘッダーサイズ制限なし | 残存リスク(低) | max_length 制限を追加推奨 | +| ポートスキャン代用 | **残存リスク(中)** | ポート許可リストまたはタイムアウト短縮を推奨 | +| レスポンス DoS | 耐えた | 64KB キャップと finally close が機能 | +| Pydantic バイパス | 耐えた | 型・長さ制約が有効 | + +**判定**: 条件付き合格(MEDIUM 残存リスク 1 件、LOW 残存リスク 1 件)。 +ポートスキャン問題はタイムアウトを 2 秒以下に短縮することで軽減可能だが、 +今回のデモ用途では許容範囲。DNS リバインディングは外部公開時の追加実装事項として記録する。 + +--- + +## DX Review — 6ペルソナ + +### ペルソナ 1: 初心者(Python 歴1年・独学中・女性・バックエンド志望) + +`http.client` という名前から「高レベルの HTTP ライブラリ」を想像してしまい、 +`requests` のような `.get(url)` 1 行メソッドを探して迷う可能性がある。 + +**ドキュメント理解**: `HTTPConnection(host, port)` → `request()` → `getresponse()` → `read()` という +4 ステップのシーケンスは直感的でない。FT レポートの関数一覧があれば追いやすい。 + +**事故リスク**: 高。`finally: connection.close()` を忘れると接続が漏洩する。 +`with` 文が使えるかを最初に調べるが、`http.client.HTTPConnection` はコンテキストマネージャーに +なっていないため、`close()` の明示的呼び出しが必須と気づくのに時間がかかる。 + +**規約の使いやすさ**: `HttpResponseInfo` dataclass に整形してくれる `_read_response()` ヘルパーは +初心者にとって「これを使えばいい」と分かりやすい。 + +### ペルソナ 2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES) + +`requests` に慣れているため `http.client` の冗長さに違和感を覚える。 +コピペ可能なコード例として `fetch_get()` は有用。 + +**コピペ可能性**: `fetch_get()` をコピーして `host` と `path` を変えるだけで使えるため良好。 +**拡張時の罠**: `fetch_multiple()` をコピーして大きなレスポンスを受け取ると F-3 の問題に直面する。 +ドキュメントに「64KB 以上のレスポンスで接続が汚染される」という注記があれば防げる。 +**事故リスク**: 中。`connection.close()` の漏れは起きやすい。 + +### ペルソナ 3: フロントエンド寄り(React/TS 歴4年・バックエンド転向中・ノンバイナリ) + +`fetch()` API に近い感覚で使えるかと思ったが、同期 API で URL ではなく `host/path` に分けて +渡す必要があることに戸惑う。 + +**エラーレスポンスの質**: 422 の `{"type": "...", "errors": [...]}` 形式は React 側から +使いやすい構造。 +**Python 固有概念の学習コスト**: `frozen=True, slots=True` の dataclass は `TypeScript interface` の +読み取り専用オブジェクトと概念が近く、説明が容易。 +**事故リスク**: 低(エンドポイントの使い方は REST 的で分かりやすい)。 + +### ペルソナ 4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア) + +「なぜ `requests` や `httpx` を使わずに `http.client` なのか」と最初に疑問を持つ。 +FT の目的(標準ライブラリ検証)を理解すれば納得する。 + +**他フレームワークとの差異**: `requests` との比較で「リダイレクト非追跡」「コンテキストマネージャー非対応」 +「URL を host/path に分割する必要あり」という 3 点が際立つ。 +**nene2 の薄さへの評価**: `_validate_host()` が `ValidationException` を raise → `ErrorHandlerMiddleware` が +422 に変換する流れは DI なしで読みやすい。 +**事故リスク**: 低(コードの意図が明確)。 + +### ペルソナ 5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年) + +`_validate_host()` がアプリケーションコアではなくハンドラー層にあるのは適切だが、 +DNS リバインディングを防いでいない点はコードレビューでコメントする。 + +**コードレビューチェックポイント**: +- `finally: connection.close()` は OK。コンテキストマネージャーに比べると見落としリスクがあるが関数スコープで完結している。 +- `MAX_RESPONSE_BYTES = 65_536` は定数として適切に宣言されている。 +- `headers: dict[str, str]` のヘッダーサイズ制限がない点は指摘する。 +- ポートスキャン代用のリスクを PR のコメントに残すことを推奨。 + +**チームでの安全なパターン**: `fetch_get()` / `fetch_post()` のようなラッパー関数を提供することで +直接 `HTTPConnection` を触るコードの散在を防ぐ設計は評価できる。 +**事故リスク**: 低(設計は整合的、残存リスクは文書化済み)。 + +### ペルソナ 6: 設計者(nene2-python 設計ポリシー目線) + +**CLAUDE.md ポリシー整合性**: +- `frozen=True, slots=True` dataclass ✓ +- `max_length` による入力制限 ✓(ただし `headers` dict のサイズ制限が抜け) +- `ValidationException` + `ValidationError` + `code` フィールド ✓ +- `ErrorHandlerMiddleware` を `create_app()` に追加 ✓ + +**初心者でも安全な API 達成度**: `_ALLOWED_HOSTS` ホワイトリストは最小防御として適切。 +ただし `init-ft.sh` のテンプレートに `ErrorHandlerMiddleware` を含めていないため、 +F-2 の摩擦が発生した。テンプレートの改善余地がある。 + +**F-2 への対応**: `init-ft.sh` の pyproject.toml テンプレートに `ErrorHandlerMiddleware` の +`add_middleware` 呼び出しを含む `create_app()` のスニペットを追加することを推奨する。 + +--- + +## Follow-up + +- `init-ft.sh` テンプレートに `ErrorHandlerMiddleware` を含む最小 `create_app()` を追加(今 PR に含める) +- CLAUDE.md の `ValidationError` 使用例に `code` 引数を明記(今 PR に含める) +- DNS リバインディング対策(ipaddress による解決後 IP 検証)は Issue として記録 diff --git a/docs/field-trials/INDEX.md b/docs/field-trials/INDEX.md index a2159aa..e67c8ef 100644 --- a/docs/field-trials/INDEX.md +++ b/docs/field-trials/INDEX.md @@ -228,19 +228,21 @@ | [FT192](2026-05-field-trial-192.md) | asyncio モジュール — コルーチン・タスク・Lock・Event・Semaphore・Queue・TaskGroup | 🔒🔍 | | | [FT193](2026-05-field-trial-193.md) | socket モジュール — TCP/UDP socketpair・DNS 解決・ソケットオプション | | | | [FT194](2026-05-field-trial-194.md) | ipaddress モジュール — IPv4/IPv6 解析・CIDR 計算・SSRF 防御パターン | | | +| [FT195](2026-05-field-trial-195.md) | ssl モジュール — SSLContext・暗号スイート列挙・セキュリティ評価 API | 🔒 | | +| [FT196](2026-05-field-trial-196.md) | http.client モジュール — 低レベル HTTP クライアント・接続管理・SSRF 防御 | 🔍 | | --- ## セキュリティ診断実施済み一覧(🔒) -FT3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 174, 177, 180, 183, 186, 189, 192 +FT3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 174, 177, 180, 183, 186, 189, 192, 195 -合計: **64件**(194 FT 中 約 33%) +合計: **65件**(196 FT 中 約 33%) ## クラッカーペンテスト実施済み一覧(🔍) -FT172, FT176, FT180, FT184, FT188, FT192 +FT172, FT176, FT180, FT184, FT188, FT192, FT196 --- -*最終更新: 2026-05-21 (FT194 / v1.8.66)* +*最終更新: 2026-05-22 (FT196 / v1.8.68)* diff --git a/docs/todo/current.md b/docs/todo/current.md index 1d7a6c5..24ec09f 100644 --- a/docs/todo/current.md +++ b/docs/todo/current.md @@ -1,16 +1,17 @@ # TODO — current 最終更新: 2026-05-22 -現状: **v1.8.67 安定版 / フィールドトライアルループ継続中(FT195 完了)** +現状: **v1.8.68 安定版 / フィールドトライアルループ継続中(FT196 完了)** --- ## 状態サマリー -v1.8.67 完了済み。FT195(ssl — SSLContext・暗号スイート・セキュリティ評価 API)まで全 195 件を実施済み。 -並行系(FT188〜192)→ ネットワーク系(FT193〜195)の縦断を完了。 +v1.8.68 完了済み。FT196(http.client — 低レベル HTTP クライアント・SSRF 防御・クラッカーペンテスト)まで全 196 件を実施済み。 +ネットワーク系(FT193〜196)の縦断を継続中。 +Issue #538(バージョン不整合)は v1.8.68 化と同時に解消済み。 リポジトリは main ブランチ 1 本・Issue/PR/ブランチ全ゼロのクリーンな状態。 -フィールドトライアルループは FT196 以降も継続中。 +フィールドトライアルループは FT197 以降も継続中。 --- @@ -24,7 +25,6 @@ v1.8.67 完了済み。FT195(ssl — SSLContext・暗号スイート・セキ | # | タイトル | 優先度 | 種別 | |---|---|---|---| -| [#538](https://github.com/hideyukiMORI/nene2-python/issues/538) | pyproject.toml の version を v1.8.67 に sync | 高 | bug | | [#539](https://github.com/hideyukiMORI/nene2-python/issues/539) | handler の response_model を統一して CLAUDE.md ポリシーに準拠 | 中 | enhancement | | [#540](https://github.com/hideyukiMORI/nene2-python/issues/540) | FT ループの目的と終着点を明文化する | 中 | docs | | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | PyPI 公開フロー検証(uv publish ワークフロー) | 中 | enhancement | @@ -35,6 +35,7 @@ v1.8.67 完了済み。FT195(ssl — SSLContext・暗号スイート・セキ | バージョン | 主な内容 | |---|---| +| v1.8.68 | FT196: http.client — 低レベル HTTP クライアント・接続管理・SSRF 防御(クラッカーペンテスト、条件付き合格) | | v1.8.67 | FT195: ssl — SSLContext・暗号スイート列挙・セキュリティ評価 API(セキュリティ診断、条件付き合格) | | v1.8.66 | FT194: ipaddress — IPv4/IPv6 解析・CIDR 計算・SSRF 防御パターン | | v1.8.65 | FT193: socket — TCP/UDP socketpair・DNS 解決・ソケットオプション | @@ -44,34 +45,18 @@ v1.8.67 完了済み。FT195(ssl — SSLContext・暗号スイート・セキ | v1.8.61 | バックログ Issue 一括解消(CLAUDE.md ルール更新・FT サンドボックス修正・ドキュメント追記) | | v1.8.60 | FT189: subprocess — 安全なプロセス実行・stdin/stdout 制御・ストリーミング(セキュリティ診断) | | v1.8.59 | FT188: threading — Thread・Lock・RLock・Semaphore・Event・ThreadPoolExecutor・Queue・Timer(クラッカーペンテスト) | -| v1.8.58 | FT187: collections — Counter・defaultdict・deque・ChainMap・NamedTuple・OrderedDict | -| v1.8.57 | FT186: functools — キャッシュ・部分適用・デコレーター・比較・ディスパッチ(診断実施) | -| v1.8.56 | FT185: contextlib — コンテキストマネージャー・リソース管理・エラー抑制 | -| v1.8.55 | FT184: urllib.request — URL フェッチ・Basic 認証・SSRF 防御(クラッカーペンテスト実施) | -| v1.8.54 | FT183: smtplib — SMTP 送信・STARTTLS・ヘッダーインジェクション防御(診断実施) | -| v1.8.53 | FT182: email — MIME 構築・RFC 2047・パース・アドレス操作 | -| v1.8.52 | FT181: gzip — 圧縮・解凍・メタデータ手動解析・ビルド再現性 | -| v1.8.51 | FT180: xml — XXE/展開爆弾防御・RSS パース(診断+ペンテスト) | -| v1.8.50 | FT179: zlib — 圧縮・解凍・展開爆弾対策・CRC32/Adler-32 | -| v1.8.49 | FT178: base64 — エンコード・URL セーフ・データ URI・HTTP Basic Auth | -| v1.8.48 | FT177: hashlib — PBKDF2 / scrypt / Blake2 キー付きハッシュ | -| v1.8.47 | FT176: decimal — 金融計算・精度制御(クラッカーペンテスト実施) | -| v1.8.46 | FT175: logging — SensitiveFilter / RequestIdAdapter | -| v1.8.45 | FT174: itertools — 安全な組み合わせ計算(セキュリティ診断実施) | -| v1.8.44 | FT173: pathlib — セキュアなファイル操作 | -| v1.8.43 | FT172: dataclasses — フリーズ・スロット・バリデーション(診断+ペンテスト) | --- ## フィールドトライアル進捗 -**実施済み**: FT1〜FT195(全 195 件) +**実施済み**: FT1〜FT196(全 196 件) 索引: [`docs/field-trials/INDEX.md`](../field-trials/INDEX.md) **次のアクション**: -- FT196 を開始(196 % 3 = 1 → セキュリティ診断なし、196 % 4 = 0 → **クラッカーペンテストあり**) -- テーマ候補: `http.client`(低レベル HTTP クライアント)または `select` / `selectors`(I/O 多重化) +- FT197 を開始(197 % 3 = 2 → セキュリティ診断なし、197 % 4 = 1 → ペンテストなし) +- テーマ候補: `urllib.parse`(URL パース・エンコード)または `http.server`(簡易 HTTP サーバー) --- @@ -79,8 +64,7 @@ v1.8.67 完了済み。FT195(ssl — SSLContext・暗号スイート・セキ | 優先度 | Issue | タスク | 種別 | |---|---|---|---| -| 高 | — | FT196 実施(クラッカーペンテストあり) | FT | -| 高 | [#538](https://github.com/hideyukiMORI/nene2-python/issues/538) | pyproject.toml の version を v1.8.67 に sync | bug | +| 高 | — | FT197 実施(診断・ペンテストなし) | FT | | 中 | [#539](https://github.com/hideyukiMORI/nene2-python/issues/539) | handler の response_model 統一(CLAUDE.md ポリシー準拠) | enhancement | | 中 | [#540](https://github.com/hideyukiMORI/nene2-python/issues/540) | FT ループの目的・終着点を明文化 | docs | | 中 | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | PyPI 公開フロー検証(uv publish) | enhancement | @@ -94,10 +78,10 @@ v1.8.67 完了済み。FT195(ssl — SSLContext・暗号スイート・セキ | 課題 | 優先度 | Issue | 備考 | |---|---|---|---| -| pyproject.toml バージョン不整合 | 高 | [#538](https://github.com/hideyukiMORI/nene2-python/issues/538) | current.md v1.8.67 との乖離 | | handler response_model 未使用 | 中 | [#539](https://github.com/hideyukiMORI/nene2-python/issues/539) | CLAUDE.md ポリシー違反 | | FT ループ目的の明文化 | 中 | [#540](https://github.com/hideyukiMORI/nene2-python/issues/540) | FT1〜6 と FT7〜 でフェーズが変化している | | PyPI 未公開 | 中 | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | uv publish フロー検証が必要 | +| http.client DNS リバインディング未防御 | 中 | — | FT196 で発見。ipaddress+dns resolve 二段階チェックが必要(本番化時) | | 並行系 how-to(threading / asyncio 比較) | 中 | — | FT188〜192 の知見を 1 本にまとめる | | PostgreSQL / MySQL 実 DB 統合テスト | 中〜高 | — | CI に Docker service ジョブを追加検討 | | PyJWT 推移的 CVE(PYSEC-2025-183) | 低 | — | mcp 側の修正を待ち。文書化済み | diff --git a/pyproject.toml b/pyproject.toml index 84ab4c2..a7b90d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "nene2-python" -version = "1.8.66" +version = "1.8.68" description = "NENE2 Python — minimal API framework following NENE2's design philosophy" readme = "README.md" license = {text = "MIT"} diff --git a/uv.lock b/uv.lock index efe7ada..86e6267 100644 --- a/uv.lock +++ b/uv.lock @@ -925,7 +925,7 @@ wheels = [ [[package]] name = "nene2-python" -version = "1.8.48" +version = "1.8.68" source = { editable = "." } dependencies = [ { name = "alembic" },