# セキュリティ仕様

**文書番号:** SPEC-007  
**バージョン:** 1.0  
**作成日:** 2025-11-03  
**最終更新:** 2025-11-03

---

## 1. 概要

本文書は、Tree-sitter Analyzerのセキュリティ仕様を記述する。パストラバーサル防止、入力検証、ReDoS攻撃対策などの実装を明確化する。

---

## 2. セキュリティアーキテクチャ

```
┌─────────────────────────────────────────┐
│       SecurityValidator                  │
│   (統合セキュリティ検証)                  │
│  ┌──────────────────────────────────┐  │
│  │  ProjectBoundaryManager          │  │
│  │  - プロジェクト境界制御          │  │
│  │  - パス検証                      │  │
│  └──────────────────────────────────┘  │
│  ┌──────────────────────────────────┐  │
│  │  RegexSafetyChecker              │  │
│  │  - ReDoS攻撃防止                │  │
│  │  - 正規表現安全性チェック        │  │
│  └──────────────────────────────────┘  │
│  ┌──────────────────────────────────┐  │
│  │  InputSanitizer                  │  │
│  │  - 入力サニタイゼーション        │  │
│  │  - XSS/SQLインジェクション防止   │  │
│  └──────────────────────────────────┘  │
└─────────────────────────────────────────┘
```

---

## 3. パストラバーサル防止

### 3.1 脅威モデル

**攻撃例:**
```python
# 攻撃者が提供したパス
malicious_path = "../../../etc/passwd"

# 脆弱なコード
with open(malicious_path) as f:
    content = f.read()  # システムファイル読み取り
```

---

### 3.2 ProjectBoundaryManager

```python
class ProjectBoundaryManager:
    """プロジェクト境界管理"""
    
    def __init__(self, project_root: str):
        self.project_root = Path(project_root).resolve()
    
    def is_within_project(self, file_path: str) -> bool:
        """ファイルパスがプロジェクト内か検証"""
        try:
            resolved_path = Path(file_path).resolve()
            return resolved_path.is_relative_to(self.project_root)
        except (ValueError, OSError):
            return False
    
    def validate_path(self, file_path: str) -> tuple[bool, str]:
        """パス検証"""
        # 空文字チェック
        if not file_path or not file_path.strip():
            return False, "Empty path"
        
        # ヌルバイトチェック
        if '\x00' in file_path:
            return False, "Null byte in path"
        
        # 絶対パスチェック
        if os.path.isabs(file_path):
            return False, "Absolute path not allowed"
        
        # Windowsドライブレターチェック
        if len(file_path) >= 2 and file_path[1] == ':':
            return False, "Windows drive letter not allowed"
        
        # パストラバーサルチェック
        if '..' in Path(file_path).parts:
            return False, "Path traversal detected"
        
        # プロジェクト境界チェック
        if not self.is_within_project(file_path):
            return False, "Path outside project boundary"
        
        return True, ""
```

---

### 3.3 SecurityValidator

```python
class SecurityValidator:
    """統合セキュリティ検証"""
    
    def __init__(self, project_root: str):
        self.boundary_manager = ProjectBoundaryManager(project_root)
        self.regex_checker = RegexSafetyChecker()
    
    def validate_file_path(
        self,
        file_path: str,
        project_root: str | None = None
    ) -> tuple[bool, str]:
        """ファイルパス検証"""
        root = project_root or self.boundary_manager.project_root
        
        # 基本検証
        if not file_path:
            return False, "File path cannot be empty"
        
        # プロジェクト境界検証
        return self.boundary_manager.validate_path(file_path)
    
    def sanitize_input(self, user_input: str) -> str:
        """ユーザー入力サニタイゼーション"""
        if not user_input:
            return ""
        
        # HTML特殊文字のエスケープ
        sanitized = user_input.replace('<', '&lt;')
        sanitized = sanitized.replace('>', '&gt;')
        sanitized = sanitized.replace('"', '&quot;')
        sanitized = sanitized.replace("'", '&#x27;')
        sanitized = sanitized.replace('&', '&amp;')
        
        return sanitized
```

---

## 4. ReDoS攻撃防止

### 4.1 ReDoS（Regular Expression Denial of Service）

**脅威:**  
悪意ある正規表現パターンで、指数関数的なバックトラック を引き起こし、CPU資源を枯渇させる攻撃。

**危険なパターン例:**
```python
# ❌ 危険: 指数関数的バックトラック
r"(.+)+"
r"(a+)+"
r"(a|a)*"
r"(a|ab)*"
```

---

### 4.2 RegexSafetyChecker

```python
class RegexSafetyChecker:
    """正規表現安全性チェッカー"""
    
    def __init__(self):
        self.max_complexity = 100
        self.max_length = 1000
        
        # 危険なパターン
        self.dangerous_patterns = [
            r'(.+)+',           # ネストした量指定子
            r'(.*)*',
            r'(.+)*',
            r'(a+)+',
            r'(a*)*',
            r'(a|a)*',          # 重複する選択肢
            r'(a|ab)*',
        ]
    
    def check_safety(self, pattern: str) -> tuple[bool, str]:
        """正規表現の安全性をチェック"""
        # 長さチェック
        if len(pattern) > self.max_length:
            return False, f"Pattern too long: {len(pattern)} chars"
        
        # 危険なパターンチェック
        for dangerous in self.dangerous_patterns:
            if re.search(dangerous, pattern):
                return False, f"Dangerous pattern detected: {dangerous}"
        
        # 複雑度チェック
        complexity = self.analyze_complexity(pattern)
        if complexity['complexity_score'] > self.max_complexity:
            return False, f"Pattern too complex: {complexity['complexity_score']}"
        
        return True, ""
    
    def analyze_complexity(self, pattern: str) -> dict:
        """正規表現の複雑度を分析"""
        metrics = {
            'length': len(pattern),
            'quantifiers': len(re.findall(r'[+*?{]', pattern)),
            'groups': len(re.findall(r'\(', pattern)),
            'alternations': len(re.findall(r'\|', pattern)),
            'character_classes': len(re.findall(r'\[', pattern)),
            'complexity_score': 0
        }
        
        # 複雑度スコア計算
        metrics['complexity_score'] = (
            metrics['length'] * 0.1 +
            metrics['quantifiers'] * 2 +
            metrics['groups'] * 1.5 +
            metrics['alternations'] * 3 +
            metrics['character_classes'] * 1
        )
        
        return metrics
```

---

## 5. 入力サニタイゼーション

### 5.1 XSS防止

```python
def sanitize_html(input_str: str) -> str:
    """HTML特殊文字をエスケープ"""
    html_escape_table = {
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#x27;',
        '&': '&amp;',
    }
    
    return ''.join(html_escape_table.get(c, c) for c in input_str)
```

---

### 5.2 SQLインジェクション防止

```python
def sanitize_sql_identifier(identifier: str) -> str:
    """SQLIdentifierをサニタイズ"""
    # 英数字とアンダースコアのみ許可
    if not re.match(r'^[a-zA-Z0-9_]+$', identifier):
        raise SecurityError("Invalid SQL identifier")
    
    # キーワードチェック
    sql_keywords = ['SELECT', 'DROP', 'DELETE', 'INSERT', 'UPDATE']
    if identifier.upper() in sql_keywords:
        raise SecurityError("SQL keyword not allowed")
    
    return identifier
```

---

## 6. タイムアウト制御

### 6.1 長時間実行の防止

```python
import signal

class TimeoutError(Exception):
    pass

def timeout_handler(signum, frame):
    raise TimeoutError("Operation timed out")

def analyze_with_timeout(file_path: str, timeout_seconds: int = 30):
    """タイムアウト付き解析"""
    # タイムアウト設定
    signal.signal(signal.SIGALRM, timeout_handler)
    signal.alarm(timeout_seconds)
    
    try:
        result = analyze_file(file_path)
        signal.alarm(0)  # タイムアウトキャンセル
        return result
    except TimeoutError:
        logger.error(f"Analysis timed out for {file_path}")
        return create_error_result(file_path, "Timeout")
```

---

## 7. セキュリティテスト

### 7.1 パストラバーサルテスト

```python
def test_path_traversal_prevention():
    """パストラバーサル攻撃のテスト"""
    validator = SecurityValidator('/project/root')
    
    attack_vectors = [
        '../../../etc/passwd',
        '..\\..\\..\\Windows\\System32',
        'src/../../../etc/passwd',
        '/etc/passwd',
        'C:\\Windows\\System32',
    ]
    
    for vector in attack_vectors:
        is_valid, error = validator.validate_file_path(vector)
        assert not is_valid, f"Should block: {vector}"
        assert error != "", f"Should have error message: {vector}"
```

---

### 7.2 ReDoSテスト

```python
def test_redos_prevention():
    """ReDoS攻撃のテスト"""
    checker = RegexSafetyChecker()
    
    dangerous_patterns = [
        r'(.+)+',
        r'(.*)*',
        r'(a+)+',
        r'(a|a)*',
    ]
    
    for pattern in dangerous_patterns:
        is_safe, error = checker.check_safety(pattern)
        assert not is_safe, f"Should block: {pattern}"
        assert error != "", f"Should have error message: {pattern}"
```

---

## 8. セキュリティベストプラクティス

### 8.1 最小権限の原則

- ✅ 必要最小限のファイルアクセス
- ✅ プロジェクト境界の厳格な制御
- ✅ 読み取り専用モードでの解析

---

### 8.2 深層防御（Defense in Depth）

1. **入力検証**: すべての外部入力を検証
2. **境界チェック**: プロジェクト範囲外へのアクセス防止
3. **サニタイゼーション**: 危険な文字列のエスケープ
4. **タイムアウト**: 長時間実行の防止
5. **エラーハンドリング**: 情報漏洩の防止

---

### 8.3 セキュアコーディングガイドライン

```python
# ✅ 良い例
def secure_file_read(file_path: str, project_root: str):
    validator = SecurityValidator(project_root)
    
    # 1. パス検証
    is_valid, error = validator.validate_file_path(file_path)
    if not is_valid:
        raise SecurityError(f"Invalid path: {error}")
    
    # 2. 実パス取得
    resolved_path = Path(project_root) / file_path
    resolved_path = resolved_path.resolve()
    
    # 3. 境界チェック
    if not resolved_path.is_relative_to(Path(project_root).resolve()):
        raise SecurityError("Path outside project")
    
    # 4. ファイル読み取り
    with open(resolved_path, 'r') as f:
        return f.read()

# ❌ 悪い例
def insecure_file_read(file_path: str):
    with open(file_path, 'r') as f:  # パス検証なし！
        return f.read()
```

---

## 9. 既知の脆弱性と対策

| 脆弱性タイプ | 対策 | 実装箇所 |
|------------|------|---------|
| **パストラバーサル** | ProjectBoundaryManager | SecurityValidator |
| **ReDoS** | RegexSafetyChecker | SecurityValidator |
| **XSS** | HTMLエスケープ | sanitize_input() |
| **情報漏洩** | エラーメッセージの一般化 | エラーハンドラー |
| **DoS（Denial of Service）** | タイムアウト制御 | timeout_handler() |

---

## 10. セキュリティ監査ログ

### 10.1 ログ記録

```python
import logging

security_logger = logging.getLogger('security')

def log_security_event(event_type: str, details: dict):
    """セキュリティイベントをログ記録"""
    security_logger.warning(
        f"Security Event: {event_type}",
        extra={
            'event_type': event_type,
            'timestamp': datetime.now().isoformat(),
            'details': details
        }
    )

# 使用例
def validate_with_logging(file_path: str):
    is_valid, error = validator.validate_file_path(file_path)
    
    if not is_valid:
        log_security_event('path_validation_failure', {
            'path': file_path,
            'error': error,
            'user': get_current_user()
        })
    
    return is_valid, error
```

---

## 11. 改訂履歴

| バージョン | 日付 | 変更内容 | 承認者 |
|-----------|------|---------|--------|
| 1.0 | 2025-11-03 | 初版作成 | aisheng.yu |
| 1.1 | 2025-01-XX | 実装コードとの照合・確認<br>- SecurityValidator: 実装と一致を確認<br>- ProjectBoundaryManager: 実装と一致を確認<br>- RegexSafetyChecker: 実装と一致を確認<br>- 全体的に実装コードと文書が一致していることを検証 | GitHub Copilot |

---

**最終更新:** 2025-11-03  
**管理者:** aisheng.yu  
**連絡先:** aimasteracc@gmail.com
