# セキュリティ設計書

## 概要

本ドキュメントは、Decap CMS（旧 Netlify CMS）のセキュリティ設計について記述する。Decap CMS はクライアントサイドで動作する静的サイト向け Git ベース CMS であり、認証・認可はバックエンドの Git プロバイダ（GitHub, GitLab, Bitbucket, Azure DevOps）および Netlify Identity / GoTrue サービスに委譲する設計となっている。CMS 本体はサーバーレス SPA として動作し、パスワードの直接保存やデータベースを持たない。

## 認証設計

### 認証方式

Decap CMS は複数のバックエンドに対応し、それぞれ異なる認証方式を提供する。

| バックエンド | 認証方式 | 実装ファイル |
| --- | --- | --- |
| GitHub | OAuth（Netlify Authenticator / PKCE） | `packages/decap-cms-lib-auth/src/netlify-auth.js`, `packages/decap-cms-lib-auth/src/pkce-oauth.js` |
| GitLab | PKCE OAuth / Implicit OAuth / Netlify Authenticator | `packages/decap-cms-lib-auth/src/pkce-oauth.js`, `packages/decap-cms-lib-auth/src/implicit-oauth.js` |
| Bitbucket | Implicit OAuth | `packages/decap-cms-lib-auth/src/implicit-oauth.js` |
| Azure DevOps | Implicit OAuth（Azure AD） | `packages/decap-cms-lib-auth/src/implicit-oauth.js` |
| Git Gateway (Netlify) | GoTrue Identity Service (JWT) | `packages/decap-cms-backend-git-gateway/src/implementation.ts` |
| AWS Cognito Proxy | Proxy 認証 | プロキシバックエンド実装 |

#### PKCE OAuth フロー（推奨）

RFC 7636 に準拠した PKCE（Proof Key for Code Exchange）フローを実装している。

- **コード検証子**: `window.crypto.getRandomValues()` による128文字のランダム文字列生成（`pkce-oauth.js:19`）
- **コードチャレンジ**: SHA-256 ハッシュによるチャレンジ生成（`pkce-oauth.js:6-31`）
- **一時保存**: コード検証子は `sessionStorage`（キー: `decap-cms-pkce-verifier-code`）に保存し、トークン交換後に即座に削除（`pkce-oauth.js:37,186`）
- **CSRF 防止**: State パラメータに nonce を含めて検証（`pkce-oauth.js:115-117`）

#### Implicit OAuth フロー

旧方式の Implicit Grant フローも引き続きサポートしている。

- State パラメータによる CSRF 対策（`implicit-oauth.js`）
- URL ハッシュからのトークン除去によるブラウザ履歴への露出防止（`implicit-oauth.js:51`）
- nonce 検証による認証完了確認（`implicit-oauth.js:56-59`）

#### Netlify Authenticator（PostMessage 方式）

- `window.postMessage` API を用いたウィンドウ間通信による認証（`netlify-auth.js`）
- **オリジン検証**: メッセージ受信時に `e.origin === this.base_url` を検証（`netlify-auth.js:44,55`）
- リフレッシュトークンのエンドポイント提供（`netlify-auth.js:132-162`）

#### GoTrue / Netlify Identity

- GoTrue JS ライブラリ（`gotrue-js@^0.9.24`）を利用した JWT ベースの認証
- JWT デコードに `jwt-decode@^3.0.0` ライブラリを使用
- アクセストークンおよび ID トークンからユーザーメタデータ（email, full_name 等）を抽出（`PKCEAuthenticationPage.js:22-38`）

### セッション管理

| 項目 | 設定値 | 備考 |
| --- | --- | --- |
| トークン保存先 | メモリ内（Redux Store） | ブラウザリロード時に再認証が必要 |
| PKCE コード検証子保存先 | `sessionStorage` | キー: `decap-cms-pkce-verifier-code`、トークン交換後に削除 |
| nonce 保存先 | `sessionStorage` | キー: `decap-cms-auth`、検証後に削除 |
| セッション有効期限 | Git プロバイダのトークン有効期限に依存 | PKCE フローではリフレッシュトークンによる更新が可能 |
| セッション固定化対策 | OAuth フローごとに新規 nonce を生成 | UUID ベースの nonce 生成（`utils.js:3-6`） |
| localStorage 使用内容 | UI 状態のみ（プレビュー表示、スクロール同期等） | 機密情報は保存しない |

## 認可設計

### 権限体系

| ロール | 権限 | 説明 |
| --- | --- | --- |
| Admin | 全操作 | CMS の全機能にアクセス可能。バックエンド設定変更を含む |
| Editor | コンテンツ編集・公開 | エディトリアルワークフローにおけるコンテンツの作成・編集・レビュー・公開 |
| Contributor（Open Authoring） | コンテンツ提案 | Fork ベースの PR ワークフローによるコンテンツ提案のみ。直接公開は不可 |

### アクセス制御

Decap CMS のアクセス制御は以下の複数のレイヤーで構成される。

1. **Git プロバイダレベル**: リポジトリの読み取り・書き込み権限は Git プロバイダ（GitHub, GitLab 等）の権限設定に委譲。OAuth スコープにより制限（例: GitHub では `public_repo` または `repo`）

2. **エディトリアルワークフロー**: ワークフローステータス管理（`editorialWorkflow.ts:45-68`）
   - DRAFT → REVIEW → APPROVED → PUBLISHED の状態遷移
   - 各ステータスの遷移権限を制御
   - `selectAllowDeletion()` リデューサーによる削除権限チェック

3. **Open Authoring（GitHub）**: Fork ベースの PR ワークフロー（`AuthenticationPage.js:48-75`）
   - 未認証ユーザーも Fork 経由でコンテンツ提案が可能
   - Fork の存在確認と承認フローの実施
   - 直接リポジトリへの書き込みは不可

4. **Git Gateway ロール制御**: JWT の `app_metadata.roles` からロールを抽出（`implementation.ts:339`）

## 通信セキュリティ

| 項目 | 対策 |
| --- | --- |
| HTTPS 強制 | OAuth フロー開始前にプロトコルチェックを実施。`https:` 以外はエラー（`utils.js:16-24`）。`localhost` / `127.0.0.1` は開発用に例外許可 |
| HSTS | CMS 本体での設定なし。デプロイ先のホスティングサービスまたは Web サーバーで設定する必要あり |
| API リクエストヘッダー | `Authorization: token ${token}` 形式で認証トークンを送信（`API.ts:284-296`）。`Content-Type: application/json; charset=utf-8` |
| リクエストタイムアウト | 60秒のタイムアウト設定。`AbortController` による中断制御（`unsentRequest.js:13-31`） |
| PostMessage オリジン検証 | `e.origin === this.base_url` によるオリジン検証（`netlify-auth.js:44,55`） |
| リダイレクト URI | `document.location.origin + document.location.pathname` に固定。リダイレクト URI インジェクション防止（`pkce-oauth.js:111`） |
| 外部リンク安全対策 | `rel="noopener noreferrer"` 属性付与による `window.opener` アクセス防止 |

## データセキュリティ

### 暗号化

| 対象 | 暗号化方式 |
| --- | --- |
| 通信 | TLS/HTTPS（ホスティング環境に依存） |
| 保存データ | Git プロバイダのストレージ暗号化に委譲（CMS 側では暗号化処理なし） |
| OAuth コードチャレンジ | SHA-256（PKCE フロー、`pkce-oauth.js:6-31`） |
| Slack Webhook 署名検証 | HMAC-SHA256（`functions/publish.js:4-32`） |

### 機密情報管理

CMS はクライアントサイドアプリケーションであるため、以下の方針で機密情報を管理する。

- **認証トークン**: メモリ内（Redux Store）のみに保持し、永続ストレージには保存しない
- **PKCE コード検証子**: `sessionStorage` に一時保存し、トークン交換完了後に即座に削除
- **サーバーサイド機密情報**: サーバーレス関数（`functions/publish.js`）では環境変数を使用
  - `SLACK_SIGNING_SECRET`: Slack Webhook 署名検証用
  - `GITHUB_TOKEN`: GitHub API アクセス用
  - `ALLOWED_USERS`: アクセス制御用ユーザーリスト
- **Slack 署名検証**: タイミングセーフ比較（`crypto.timingSafeEqual()`）を使用してタイミング攻撃を防止（`publish.js:22`）
- **タイムスタンプ検証**: 5分以上前のリクエストを拒否するリプレイ攻撃対策（`publish.js:7-9`）

## 入出力対策

| 脅威 | 対策 |
| --- | --- |
| XSS | DOMPurify（`^3.2.6`）による HTML サニタイズ。Markdown プレビューにおいて `sanitize_preview` フラグが有効な場合に `DOMPurify.sanitize(html)` を実行（`MarkdownPreview.js:4,26`）。React の自動エスケープ機能も併用 |
| SQL インジェクション | 該当なし。Decap CMS はデータベースを使用せず、Git リポジトリをデータストアとして利用 |
| CSRF | nonce ベースの CSRF 対策を実装。UUID による nonce 生成 → `sessionStorage` 保存 → OAuth コールバック時に検証（`utils.js:3-14`）。PKCE フローでは State パラメータに nonce を埋め込み（`pkce-oauth.js:115-117`） |
| ファイル名インジェクション | `sanitize-filename@^1.6.1` によるファイル名サニタイズ（`decap-cms-core/package.json:69`） |
| URL インジェクション | `encodeURIComponent()` によるクエリパラメータのエンコード（`API.ts:312`） |
| 設定インジェクション | AJV（`^8.12.0` / `^8.17.1`）による JSON Schema バリデーション。正規表現パターンによるフィールド検証（`configSchema.js:1-80`） |

## 監査ログ

| ログ種別 | 記録内容 | 保持期間 |
| --- | --- | --- |
| Git コミット履歴 | コンテンツの変更履歴（作成者、日時、変更内容） | Git リポジトリの保持期間に依存 |
| エディトリアルワークフロー | ステータス遷移（DRAFT → REVIEW → APPROVED → PUBLISHED） | Redux ストアのライフサイクル（セッション中のみ） |
| Slack 通知ログ | 公開イベントの詳細（`console.log` によるログ出力、`functions/publish.js:36-49`） | サーバーレス関数のログ保持期間に依存 |
| Git プロバイダ監査ログ | API アクセスログ、リポジトリ操作ログ | 各 Git プロバイダの設定に依存（GitHub Audit Log, GitLab Audit Events 等） |

**注意**: CMS 本体には専用のセキュリティイベントロギング機構は実装されていない。セキュリティ監査ログについては、Git プロバイダおよびホスティングサービスの監査機能に依存する。

## 備考

### セキュリティ関連の依存ライブラリ

| パッケージ | バージョン | 用途 |
| --- | --- | --- |
| `dompurify` | ^3.2.6 | HTML/XSS サニタイズ |
| `jwt-decode` | ^3.0.0 | JWT トークン解析 |
| `gotrue-js` | ^0.9.24 | Netlify Identity クライアント |
| `sanitize-filename` | ^1.6.1 | ファイル名サニタイズ |
| `ajv` | ^8.12.0 / ^8.17.1 | JSON Schema バリデーション |
| `js-base64` | ^3.0.0 | Base64 エンコード/デコード |

### 既知の注意点

1. **nonce 削除のバグ**: `utils.js:12` にて、nonce を `sessionStorage` から削除すべきところを `localStorage.removeItem` で削除している。機能上の影響は限定的だが修正が望ましい
2. **DOMPurify の条件付き適用**: Markdown プレビューのサニタイズは `sanitize_preview` フラグが `true` の場合のみ有効。デフォルトでは無効であるため、CMS 設定で明示的に有効化する必要がある
3. **レート制限の不在**: クライアントサイド・サーバーサイドともに認証試行のレート制限が実装されていない。ブルートフォース攻撃対策は Git プロバイダ側の制限に依存する
4. **CSP 設定の不在**: CMS 本体に Content Security Policy の設定はない。デプロイ先の Web サーバーで適切な CSP ヘッダーを設定する必要がある
