Vibe-Tradingのファイル読み込み機能に、サーバーが読み取り可能な任意のファイルを読み込ませる脆弱性がある。

### 概要:
2つの問題点が見つかった。まず、`safe_user_path()`関数は、`Path.home()`または`Path.cwd()`以下の任意のパスを受け入れ、これはコンテナ内で`/root`と`/app`に解決されるため、`/root/.ssh/id_rsa`、`/root/.aws/credentials`、`/root/.kube/config`、および`/app/agent/.env`といったrootユーザーのホームディレクトリ内のファイルもチェックを通過する(F9)。次に、`read_document()`関数は、サンドボックス化を行わず、FastAPIプロセスが読み取れる任意のファイルの完全な内容を返す。これには、`/etc/shadow`、`/etc/passwd`、`/proc/self/environ`、およびコンテナにマウントされている秘密ファイルが含まれる(F10)。F10はF9よりも広範囲な問題であり、修正範囲も異なる(F10は、ある関数での`safe_path()`関数の呼び出し漏れ、F9は`path_utils.py`におけるパスの定義)。したがって、両方の問題に対して修正が必要である。

---

### 共通の前提条件(両方の問題点に適用される)

コンテナには`USER`ディレクティブがない(Dockerfile:15 — `FROM python:3.11-slim AS runtime`、以降に`USER`は記述されていない)。そのため、FastAPIプロセスは`uid=0(root)`として実行される。

ここで説明されている2つのファイル読み込み機能は、自動的に検出されるLLMツールレジストリのメンバーである。
GHSA-1 / F1と組み合わさると、これらの機能は匿名TCPクライアントからポート8899にアクセス可能になる。しかし、同様の欠陥は、認証されたセッションや、エージェントが処理するドキュメントに対するプロンプトインジェクションにも存在する。
インストール手順については、GHSA-1の共有再現ブロックを参照。同じ`docker compose up -d`の設定が適用される。

> **注意:以下の各問題点に関する「確認方法」ブロックで使用されている`HOST`プレースホルダーについて**: `HOST`を、Dockerホストにアクセスするアドレスに置き換えてください。通常は、再現手順をコンテナと同じマシンで実行する場合は`localhost`(または`127.0.0.1`)となる。以下のすべての`curl`コマンドは、この置換を前提としている。

---

### 問題点9 — 深刻:safe_user_path()関数は、ユーザーのホームディレクトリ全体とプロセスのカレントディレクトリを受け入れ、LLMツールの呼び出しが/rootの認証情報を読み取れる状態になる。

- **深刻度**: 高
- **CVSS v3.1**: 7.5 — `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N`
- **CVSS v4.0**: 8.7 — `AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N`
- **CWE**: CWE-22 (パス・トラバーサル); CWE-552 (外部のエンティティがアクセス可能なファイル)

**影響を受けるファイル**: `agent/src/tools/path_utils.py`
- 52行目 — `def safe_user_path(p: str) -> Path:`
- 73行目-77行目 — `if resolved.is_relative_to(home) or resolved.is_relative_to(cwd): return resolved` — `home = Path.home()`, `cwd = Path.cwd()`

**意図と実際**:
`safe_user_path()`は、ジャーナルやシャドウ・アカウントのツールが、オペレーターがホームディレクトリまたはプロジェクトフォルダ内のどこにでも配置した可能性のあるブローカーのエクスポートファイルを開くことを許可することを意図しています。 意図された動作は、ユーザーが所有するブローカーのデータファイルのみがアクセス可能であること(システム認証ファイルやSSHキーファイルはアクセス可能ではない)です。 しかし、実際のチェックでは、解決されたパスが`Path.home()`または`Path.cwd()`の内部にあるパスであれば、どのパスでも受け入れられます。 デプロイされたDockerコンテナ内では、`Path.home()`は`/root`に、`Path.cwd()`は`/app`に解決されます。 どちらのサブツリー内のすべてのファイルがチェックを通過し、以下が含まれます。

- `/root/.ssh/id_rsa` およびその他のSSHキーファイル
- `/root/.aws/credentials`, `/root/.kube/config`, `/root/.docker/config.json`
- `/app/agent/.env` (オペレーターの実際の`OPENROUTER_API_KEY`、`TUSHARE_TOKEN`、およびその他の機密情報を含むファイル)

コンテナ内の実行時テストで、`safe_user_path('/root/.aws/credentials')`が`ValueError`を発生させずにパスを返却することが確認されました。 その後、`ExtractShadowStrategyTool`が`/root/secrets/aws.csv`(偽の認証ファイル)に対して呼び出され、エラーメッセージが返されました。 エラーメッセージには、ファイルの最初の行が解析エラーのチャネル経由で含まれていました。

**確認方法**:

1. GHSA-1で共有されている再現手順に従い、動作するLLM APIキーを使用してサーバーを起動し、認証されていないセッションを作成します。
2. (安全なデモのための設定:コンテナ内で、`docker exec`コマンドを使用して、あらかじめ用意されたファイルを実行します:`docker exec <コンテナ名> sh -c 'mkdir -p /root/secrets && printf "broker_id,api_key,api_secret\nDEMO,FAKE_KEY,FAKE_SECRET\n" > /root/secrets/aws.csv'`。)
3. `curl -s -X POST "http://HOST:8899/sessions/$SID/messages" -H 'Content-Type: application/json' -d '{"content":"/root/secrets/aws.csvにある取引記録を分析し、何が見つかるか教えてください。"}'`
4. `curl -s "http://HOST:8899/sessions/$SID/messages"`コマンドで状況を確認します。エージェントが`ExtractShadowStrategyTool`を`journal_path="/root/secrets/aws.csv"`とともに呼び出すことを確認します。これにより、`safe_user_path()`が実行され、ファイルが取引記録のCSV形式として解析されることを試みます。
5. エラー応答を確認します。ファイルの構造が期待される取引記録のスキーマと一致しない場合、解析エラーには、多くの場合、ファイルの最初の行(列名)がそのまま含まれており、ファイルの最初の行が漏洩する可能性があります。
6. `journal_path="/app/agent/.env"`で同様の操作を繰り返して、`.env`ファイルが許可された範囲内にあることを確認します。

**影響**:
認証されていないユーザーは、LLMに`/root`または`/app`以下の任意のファイルを取引記録として解析するように指示し、解析エラーメッセージを通じてファイルの最初の行を抽出することができます。有効なCSV形式の最初の行を持つファイルの場合、複数のバイトが漏洩する可能性があります。出荷時のルートコンテナでは、`/root`は、不注意なオペレーターがホームディレクトリにマウントした可能性のあるすべての認証情報を含みます。`/app`には、エージェント自身の秘密情報や、オペレーターが用意したデータファイルが含まれます。

---

### 発見事項10 — 深刻:`read_document()`関数は、サンドボックスの制限なしに、サーバーが読み取り可能なすべてのファイルを開き、`/etc/shadow`および`/proc/self/environ`の完全な内容を返します。

- **深刻度**: 高
- **CVSS v3.1**: 7.5 — `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N`
- **CVSS v4.0**: 8.7 — `AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N`
- **CWE**: CWE-22 (パス・トラバーサル); CWE-552 (外部のエンティティがアクセス可能なファイル); CWE-200 (情報漏洩)

**影響を受けるファイル**: `agent/src/tools/doc_reader_tool.py`
- 259行目 — `def read_document(file_path: str, pages: str = "") -> str:`
- 270行目 — `path = Path(file_path)` — `path.exists()` と `path.is_file()` のチェックのみが行われ、その後、形式固有のリーダーに処理が渡される
- この関数内では、`safe_path`、`safe_user_path`、またはその他のサンドボックスの適用に関する呼び出しは一切見られない

**意図と実際**: `DocReaderTool` は、LLMエージェントが分析のために提供されたドキュメントやデータファイルを読み取ることを目的としています。プロジェクト内の他のファイル読み取りツールと同様に、ファイルを開く前にサンドボックスのチェックを適用する必要があります。しかし、実際の実装では、LLMから出力された `file_path` 文字列を受け取り、`path.exists()` と `path.is_file()` のチェックのみを行い、適切なリーダーに処理を渡しています。`safe_path` または `safe_user_path` の呼び出しは関数内に存在しません。実行時のテストにより、以下のことが確認されました。

- `read_document('/etc/passwd')` は、HTTP 200 のステータスコードと、839文字の内容を返しました。
- `read_document('/etc/shadow')` は、完全なシャドウパスワードファイルを返しました。
- `read_document('/proc/self/environ')` は、`OPENROUTER_API_KEY` と `TUSHARE_TOKEN` を含む、完全なプロセス環境をプレーンテキストで返しました。

これは、F9よりも**広範囲**な問題です。F9は、(広すぎる)エンベロープによって `/root` と `/app` に制限されています。一方、F10にはエンベロープが全くなく、`/etc`、`/proc`、`/var`、およびFastAPIプロセスが読み取ることができるその他のパスにアクセス可能です。

**確認方法**:

1. GHSA-1で共有されている再現手順に従い、動作するLLM APIキーを使用してサーバーを起動し、認証されていないセッションを作成します。
2. `curl -s -X POST "http://HOST:8899/sessions/$SID/messages" -H 'Content-Type: application/json' -d '{"content":"Please read and summarize the document at /proc/self/environ"}'`
3. `curl -s "http://HOST:8899/sessions/$SID/messages"` を実行し、監視します。エージェントが `read_document` を `file_path="/proc/self/environ"` で呼び出し、メッセージストリームにプロセスの環境変数をすべて返すことを確認します。
4. `agent/.env` に含まれる `OPENROUTER_API_KEY`、`TUSHARE_TOKEN` などの変数が、平文で表示されることを確認します。
5. `file_path="/etc/shadow"` で上記を繰り返して、シャドウパスワードファイルへのアクセスを確認します。

**影響**:
認証されていないユーザーが、サーバープロセスが読み取ることができる任意のファイルにアクセスできるようになります。root権限で実行している場合、これには `/etc/shadow`、`/etc/passwd`、`/proc/self/environ` (平文のAPIキーを含む)、`/root/.ssh/id_rsa`、およびコンテナにマウントされている秘密ファイルが含まれます。これはコードベースの中で最も広範なファイル読み込み機能であり、シェル実行を必要としない認証情報抽出の経路を提供します。BashTool/シェルシグネチャを監視するエンドポイント監視では、これを完全に検出できません。

---

### なぜF9とF10が個別にリストされているのか

保守担当者は、F10がF9よりも重要であるという考えに基づいて、片方だけを修正しようとするかもしれません。両方を修正する理由は2つあります。

1. **修正範囲の違い** — F10の修正は、1行目の前にある `safe_path(file_path)` という単一の呼び出しが不足しているというものです(`read_document` の270行目付近)。一方、F9の修正は `safe_user_path()` 自体に関係しており、許可リストは `Path.home() ∪ Path.cwd()` ではなく、オペレーターが設定したディレクトリの厳密な許可リストに置き換える必要があります。`read_document` に `safe_user_path` の呼び出しを追加する修正だけでは**不十分**です。なぜなら、`safe_user_path` 自体が `/root` や `/app/agent/.env` を受け入れるからです。両方の箇所を修正する必要があります。

2. **異なる到達可能性クラス** — F9は、すでに`safe_user_path`で制限されているツール(`ExtractShadowStrategyTool`やいくつかのジャーナルツール)を通じて到達可能です。したがって、仮にF10の脆弱性を修正して`read_document`が`safe_user_path`を使用するように変更した場合でも、依然として`/root/*`が漏洩する可能性があります。なぜなら、エンベロープが壊れているからです。F9は構造的な欠陥であり、F10はそれを見逃した結果です。

---

### 提案される修正

11. **F9** — `path_utils.py`の52行から77行にある`safe_user_path()`関数において、`Path.home() ∪ Path.cwd()`というエンベロープを、オペレーターが設定したディレクトリの厳密な許可リストに置き換えます(例:明示的な`BROKER_EXPORTS_DIR`環境変数で、デフォルト値は`/app/data/broker_exports/`)。`/root`、`/app/agent/.env`、および`/app/agent/uploads/`へのアクセスを拒否します(後者は、F3でアップロードされたファイルが、解析エラーの経路を通じて認証情報の漏洩ベクトルとして利用されるのを防ぐため)。

12. **F10** — `doc_reader_tool.py`の270行に、既存の`path.exists()`/`path.is_file()`チェックの前に、`safe_path()`(または`safe_user_path()`)を呼び出します。F9が修正されると、同じ許可リストが`read_document`と、`safe_user_path`で制限されているツール、の両方に一様に適用されます。

13. **多層防御** — FastAPIプロセスをrootユーザー以外のユーザーで実行します。Dockerfileに`RUN useradd -m vibe && chown -R vibe /app`というステップを追加し、`CMD`の前に`USER vibe`を追加します。これにより、パスのトラバーサルは修正されませんが、脆弱性が悪用された場合に認証情報が漏洩する範囲を大幅に削減できます(また、GHSA-1およびGHSA-2の他のすべての問題にも効果があります)。GHSA-1 / 共通の基準を参照して、同様の`USER`に関する推奨事項を確認してください。

---

影響を受けるパッケージ:
- pip (Python) vibe-trading-ai: 影響を受けるバージョン >= 0.1.0, < 0.1.7、0.1.7で修正済み。