# 機能設計書 107-プライベートIPチェック

## 概要

本ドキュメントは、Next.jsのプライベートIPアドレス検出・保護機能の設計を記述する。IPアドレスがプライベートネットワークに属するかを判定し、SSRF（Server-Side Request Forgery）攻撃等の防御に使用される。

### 本機能の処理概要

与えられたIPアドレス文字列がプライベートネットワーク（ユニキャスト以外）に属するかを判定するユーティリティ関数である。IPv4、IPv6、IPv4マッピングIPv6アドレスに対応し、`ipaddr.js`ライブラリを使用してアドレスの範囲分類を行う。

**業務上の目的・背景**：Server ActionsやAPI Routes、Image Optimizerなどで外部URLへのリクエストが発生する場合、リクエスト先がプライベートIPアドレスでないことを確認する必要がある。SSRF攻撃では、攻撃者が内部ネットワーク上のサービスにサーバー経由でアクセスすることを試みるため、プライベートIPの検出は重要な防御層となる。

**機能の利用シーン**：Image Optimizerが外部画像を取得する際、取得先がプライベートIPアドレスでないことを検証する。また、サーバーサイドでの外部リクエスト実行時の安全性チェックに使用される。

**主要な処理内容**：
1. IPアドレス文字列のパース（IPv4/IPv6）
2. IPv6アドレスがIPv4マッピングの場合、IPv4アドレスに変換
3. アドレスの範囲分類（unicast/private/loopback/linkLocal/broadcast等）
4. unicast以外のアドレスをプライベートIPとして判定

**関連システム・外部連携**：Image Optimizer、サーバーサイドのHTTPリクエスト処理と連携する。

**権限による制御**：特に権限制御はない。サーバーサイドのリクエスト検証で自動的に使用される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | バックエンド処理専用。画面との直接的な関連はない |

## 機能種別

セキュリティ / バリデーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| ip | string | Yes | 検証対象のIPアドレス文字列 | IPv4形式、IPv6形式（ブラケット付き含む） |

### 入力データソース

- リクエスト先のIPアドレス（DNS解決後）
- リクエストヘッダーから取得されたIPアドレス

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result | boolean | プライベートIPの場合true、パブリック（unicast）の場合false |

### 出力先

呼び出し元関数への戻り値

## 処理フロー

### 処理シーケンス

```
1. isPrivateIp関数が呼び出される
   └─ IPアドレス文字列を受け取る
2. ブラケット除去
   └─ IPv6のブラケット表記[::1]からブラケットを除去
3. IPアドレスの有効性チェック
   └─ ipaddr.isValid()で判定、無効ならfalse返却
4. IPアドレスのパース
   └─ ipaddr.parse()でAddrオブジェクトに変換
5. IPv4マッピングIPv6の変換
   └─ IPv6かつisIPv4MappedAddress()の場合、toIPv4Address()で変換
6. 範囲分類の取得
   └─ addr.range()で範囲名を取得
7. unicast判定
   └─ 'unicast'以外はプライベートIPとして判定
```

### フローチャート

```mermaid
flowchart TD
    A[isPrivateIp] --> B{ブラケット付き?}
    B -->|Yes| C[ブラケット除去]
    B -->|No| D[次へ]
    C --> D
    D --> E{ipaddr.isValid?}
    E -->|No| F[false返却]
    E -->|Yes| G[ipaddr.parse]
    G --> H{IPv6?}
    H -->|Yes| I{IPv4マッピング?}
    H -->|No| J[range取得]
    I -->|Yes| K[toIPv4Address変換]
    I -->|No| J
    K --> J
    J --> L{range === 'unicast'?}
    L -->|Yes| M[false返却]
    L -->|No| N[true返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-107-1 | unicastのみパブリック | addr.range()が'unicast'の場合のみパブリックIPと判定 | すべてのIP判定 |
| BR-107-2 | IPv4マッピング変換 | IPv6がIPv4マッピングアドレスの場合、IPv4に変換してから判定 | IPv6アドレスの場合 |
| BR-107-3 | ブラケット除去 | `[::1]`形式のIPv6アドレスからブラケットを除去 | ブラケット付きの場合 |
| BR-107-4 | 無効IPはfalse | ipaddr.isValid()がfalseの場合、プライベートIPではないと判定 | 無効なIP文字列 |
| BR-107-5 | エラー時はfalse | パースやrange取得でエラーが発生した場合、falseを返却 | try-catch捕捉時 |

### 計算ロジック

`addr.range()`が返す範囲名の例:
- `unicast`: パブリックIPアドレス -> false
- `private`: プライベートIPアドレス（10.x, 172.16-31.x, 192.168.x） -> true
- `loopback`: ループバック（127.0.0.1, ::1） -> true
- `linkLocal`: リンクローカル（169.254.x） -> true
- `broadcast`: ブロードキャスト -> true
- `multicast`: マルチキャスト -> true
- `reserved`: 予約済み -> true

## データベース操作仕様

### 操作別データベース影響一覧

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | パースエラー | 不正なIPアドレス形式 | try-catchで捕捉しfalse返却 |
| - | バリデーションエラー | ipaddr.isValid()がfalse | false返却 |

### リトライ仕様

リトライは不要。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- 単純な文字列パースと比較のみで高速に動作
- `ipaddr.js`はコンパイル済みライブラリとして含まれている

## セキュリティ考慮事項

- SSRF攻撃防止の重要な防御層として機能
- IPv4マッピングIPv6（`::ffff:10.0.0.1`）によるバイパスに対応
- ブラケット表記（`[::1]`）によるバイパスに対応
- unicast以外のすべての範囲をプライベートとして扱う保守的な判定

## 備考

- `ipaddr.js`ライブラリは`next/dist/compiled/ipaddr.js`としてコンパイル済みで含まれている
- 本関数は20行程度の簡潔な実装だが、SSRF防御において重要な役割を担う

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

`ipaddr.js`ライブラリの型定義を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | is-private-ip.ts | `packages/next/src/server/is-private-ip.ts` | ipaddr.IPv4、ipaddr.IPv6の型、range()メソッドの戻り値 |

**読解のコツ**: `ipaddr.js`はIPv4とIPv6の2つのクラスを提供する。`kind()`メソッドで'ipv4'/'ipv6'を判別し、IPv6の`isIPv4MappedAddress()`でIPv4マッピングかを確認する。`range()`メソッドは事前定義された範囲名を返す。

#### Step 2: メイン関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | is-private-ip.ts | `packages/next/src/server/is-private-ip.ts` | isPrivateIp関数（3-20行目） |

**主要処理フロー**:
1. **4-6行目**: ブラケット付きIPv6のブラケット除去
2. **7-9行目**: `ipaddr.isValid(ip)`で有効性チェック
3. **11行目**: `ipaddr.parse(ip)`でアドレスオブジェクトに変換
4. **12-14行目**: IPv6でIPv4マッピングの場合、`toIPv4Address()`で変換
5. **15行目**: `addr.range()`で範囲分類を取得
6. **16行目**: `range !== 'unicast'`でプライベート判定

### プログラム呼び出し階層図

```
Image Optimizer / サーバーサイドリクエスト処理
    │
    └─ isPrivateIp(ip)
           │
           ├─ ipaddr.isValid(ip)
           ├─ ipaddr.parse(ip)
           ├─ addr.kind() === 'ipv6'?
           │      └─ addr.isIPv4MappedAddress()?
           │             └─ addr.toIPv4Address()
           └─ addr.range()
                  └─ 'unicast' / 'private' / 'loopback' / etc.
```

### データフロー図

```
[入力]                [処理]                           [出力]

IP文字列 ──────────> ブラケット除去 ──────────────────> boolean
"10.0.0.1"            │
"[::1]"               ├─ isValid() チェック
"::ffff:10.0.0.1"     ├─ parse() アドレスオブジェクト化
                      ├─ IPv4マッピング変換
                      └─ range() !== 'unicast' ────> true (プライベート)
                                                     false (パブリック)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| is-private-ip.ts | `packages/next/src/server/is-private-ip.ts` | ソース | プライベートIP判定のメイン実装 |
| ipaddr.js (compiled) | `packages/next/src/compiled/ipaddr.js` | 依存ライブラリ | IPアドレスのパース・範囲分類ライブラリ |
| image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | ソース | isPrivateIpの主な呼び出し元 |
