# セキュリティ設計書

## 概要

本ドキュメントは、OpenCodeのセキュリティアーキテクチャと実装について記述する。OpenCodeはAI駆動のコーディングアシスタントであり、ローカルマシン上で動作し、シェル実行、ファイル操作、Web アクセスなどの強力なツールへのアクセス権を持つエージェントシステムを提供する。本ドキュメントでは、認証・認可設計、通信セキュリティ、データセキュリティ、入出力対策について詳述する。

## 認証設計

### 認証方式

OpenCodeは以下の認証方式をサポートする。

#### 1. API Key認証
- 各AIプロバイダー（Anthropic、OpenAI、Google等）へのアクセスはAPIキーで認証
- キーは `~/.local/share/opencode/auth.json` にファイルパーミッション `0o600` で保存
- スキーマ定義: `packages/opencode/src/auth/index.ts`

```typescript
export const Api = z.object({
  type: z.literal("api"),
  key: z.string(),
})
```

#### 2. OAuth 2.0認証
- GitHub Copilot、OAuth対応プロバイダー向け
- リフレッシュトークン、アクセストークン、有効期限を管理
- MCP (Model Context Protocol) サーバー接続時にもOAuth認証をサポート

```typescript
export const Oauth = z.object({
  type: z.literal("oauth"),
  refresh: z.string(),
  access: z.string(),
  expires: z.number(),
  accountId: z.string().optional(),
  enterpriseUrl: z.string().optional(),
})
```

#### 3. Well-Known認証
- リモート設定サーバーからの認証をサポート
- `.well-known/opencode` エンドポイント経由で認証コマンドを取得・実行

#### 4. HTTP Basic認証（サーバーモード）
- サーバーモード有効時、`OPENCODE_SERVER_PASSWORD` 環境変数で認証を要求
- `basicAuth` ミドルウェアで実装（`packages/opencode/src/server/server.ts`）

### セッション管理

| 項目 | 設定値 | 備考 |
| --- | --- | --- |
| 認証情報保存場所 | `~/.local/share/opencode/auth.json` | OS標準のデータディレクトリ |
| ファイルパーミッション | `0o600` | 所有者のみ読み書き可能 |
| OAuthトークン有効期限 | プロバイダー依存 | `expires` フィールドで管理 |
| MCPトークン有効期限チェック | 自動 | `isTokenExpired()` で確認 |
| セッション固定化対策 | 該当なし | ローカル実行が前提のため |

## 認可設計

### 権限体系

| ロール | 権限 | 説明 |
| --- | --- | --- |
| ユーザー | 全機能 | ローカル実行のため実質的に全権限 |
| AIエージェント | 設定依存 | パーミッション設定により制限可能 |
| サーバークライアント | 認証依存 | Basic認証パスワード設定時のみアクセス可 |

### アクセス制御

OpenCodeは **Permission Next** システムにより、AIエージェントのツール実行を制御する。

#### パーミッションアクション
- `allow`: 常に許可
- `deny`: 常に拒否
- `ask`: ユーザーに確認を求める

#### 設定例（opencode.json）
```json
{
  "permission": {
    "edit": "allow",
    "bash": {
      "git *": "allow",
      "rm *": "deny",
      "*": "ask"
    },
    "external_directory": "ask"
  }
}
```

#### パーミッション評価ロジック
1. 設定ファイルからルールセットを構築
2. ツール実行時、パターンマッチングで該当ルールを検索
3. 最後にマッチしたルールのアクションを適用
4. マッチなしの場合はデフォルト `ask`

実装: `packages/opencode/src/permission/next.ts`

### 重要なセキュリティ注意事項

> **警告**: OpenCodeはエージェントをサンドボックス化しない。パーミッションシステムはエージェントの動作をユーザーに通知するためのUX機能であり、セキュリティ隔離を提供するものではない。真の隔離が必要な場合は、Docker コンテナまたはVM内でOpenCodeを実行すること。

## 通信セキュリティ

| 項目 | 対策 |
| --- | --- |
| HTTPS | 外部APIへの通信はHTTPS必須 |
| CORS | ローカルホスト、Tauriオリジン、`*.opencode.ai` のみ許可 |
| CSP | `script-src 'self' 'wasm-unsafe-eval'` 等のポリシー適用 |
| mDNS | オプトイン、ループバックアドレスでは無効化 |

### CORS設定詳細

```typescript
cors({
  origin(input) {
    if (input.startsWith("http://localhost:")) return input
    if (input.startsWith("http://127.0.0.1:")) return input
    if (input === "tauri://localhost" ||
        input === "http://tauri.localhost" ||
        input === "https://tauri.localhost") return input
    if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) return input
    if (_corsWhitelist.includes(input)) return input
    return
  },
})
```

### Content Security Policy

```
default-src 'self';
script-src 'self' 'wasm-unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
media-src 'self' data:;
connect-src 'self' data:
```

## データセキュリティ

### 暗号化

| 対象 | 暗号化方式 |
| --- | --- |
| 通信 | TLS（外部API呼び出し時） |
| 認証情報保存 | ファイルシステムパーミッション（0o600）によるアクセス制御 |
| MCP認証情報 | `mcp-auth.json` にファイルパーミッション 0o600 で保存 |

### 機密情報管理

#### 保存場所
- 認証情報: `~/.local/share/opencode/auth.json`
- MCP認証情報: `~/.local/share/opencode/mcp-auth.json`
- ログファイル: `~/.local/state/opencode/log/`

#### 保護メカニズム
1. **ファイルパーミッション**: 認証ファイルは `0o600` で作成
2. **環境変数サポート**: 設定ファイル内で `{env:VAR_NAME}` 形式で環境変数を参照可能
3. **ファイル参照**: 機密情報は `{file:path}` で外部ファイルから読み込み可能

## 入出力対策

| 脅威 | 対策 |
| --- | --- |
| パストラバーサル | `Filesystem.contains()` によるパス検証 |
| ディレクトリエスケープ | `Instance.containsPath()` による境界チェック |
| コマンドインジェクション | パーミッションシステムによるユーザー確認 |
| XSS | React/SolidJSの自動エスケープ |
| 入力検証 | Zodスキーマによるバリデーション |

### パストラバーサル対策の実装

```typescript
// packages/opencode/src/util/filesystem.ts
export function contains(parent: string, child: string) {
  return !relative(parent, child).startsWith("..")
}
```

#### テストケース
- `../../../etc/passwd` → 拒否
- `src/nested/../../../../../../../etc/passwd` → 拒否
- プロジェクト内パス → 許可

実装: `packages/opencode/test/file/path-traversal.test.ts`

### 入力バリデーション

Zodスキーマを使用した型安全なバリデーション:

```typescript
// API リクエストバリデーション
validator("param", z.object({
  providerID: z.string(),
}))
validator("json", Auth.Info)
```

## 監査ログ

| ログ種別 | 記録内容 | 保持期間 |
| --- | --- | --- |
| アプリケーションログ | リクエスト、エラー、デバッグ情報 | 直近10ファイル |
| セッションログ | AIとの対話履歴 | 無制限（手動削除まで） |
| パーミッション | 許可/拒否の記録 | セッション中 |

### ログ実装

```typescript
// packages/opencode/src/util/log.ts
export namespace Log {
  export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"])

  // ログファイルは自動クリーンアップ（直近10ファイルを保持）
  async function cleanup(dir: string) {
    const files = await Glob.scan("????-??-??T??????.log", { cwd: dir, absolute: true, include: "file" })
    if (files.length <= 5) return
    const filesToDelete = files.slice(0, -10)
    await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
  }
}
```

### リクエストログ

```typescript
// packages/opencode/src/server/server.ts
.use(async (c, next) => {
  const skipLogging = c.req.path === "/log"
  if (!skipLogging) {
    log.info("request", {
      method: c.req.method,
      path: c.req.path,
    })
  }
  // ...
})
```

## 備考

### セキュリティの脅威モデル

OpenCodeのセキュリティ設計は以下の前提に基づく:

1. **ローカル実行が前提**: OpenCodeは基本的にユーザーのローカルマシンで実行される
2. **サーバーモードはオプトイン**: サーバーモードを有効にする場合、ユーザーは認証設定の責任を負う
3. **パーミッションはUX機能**: エージェントのサンドボックス化は行わない

### スコープ外の脅威

| カテゴリ | 理由 |
| --- | --- |
| サーバーモード有効時のAPIアクセス | オプトイン機能であり期待される動作 |
| サンドボックスエスケープ | パーミッションシステムはサンドボックスではない |
| LLMプロバイダーのデータ取り扱い | 各プロバイダーのポリシーに従う |
| MCPサーバーの動作 | 信頼境界外の外部システム |
| 悪意のある設定ファイル | ユーザーが自身の設定を制御 |

### セキュリティ報告

セキュリティ問題を発見した場合は、GitHub Security Advisory経由で報告すること。

- 報告先: https://github.com/anomalyco/opencode/security/advisories/new
- エスカレーション: security@anoma.ly
