""" Контракт защиты от утечки секретов (Secret-Filter Contract). Обнаруживает и блокирует передачу конфиденциальных данных: - Приватные ключи (RSA, Ed25519, ГОСТ) - Токены доступа (JWT, OpenAI, Cloud, API keys) - Строки подключения к СУБД с паролями - Высокоэнтропийные строки (пароли и API-ключи) """ import math import re from typing import Any, Dict, List from .base import BaseSecurityContract, ContractVerdict SECRET_PATTERNS = [ # Приватные ключи (re.compile(r"-----BEGIN\s+([A-Z\s]+)?PRIVATE\s+KEY-----", re.IGNORECASE), "PRIVATE_KEY"), # JWT токены (re.compile(r"\beyJ[A-Za-z0-9-_]{10,}\.[A-Za-z0-9-_]{10,}\.[A-Za-z0-9-_]{10,}\b"), "JWT_TOKEN"), # API ключи известных провайдеров (re.compile(r"\b(sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36})\b"), "API_TOKEN"), (re.compile(r"\b(AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z-_]{35})\b"), "CLOUD_CREDENTIALS"), # Строки подключения к БД с паролем: postgres://user:pass@host (re.compile(r"(postgres|postgresql|mysql|mongodb|redis|oracle):\/\/[^:]+:([^@]+)@", re.IGNORECASE), "DB_PASSWORD_URI"), # Явные назначения секретов (re.compile(r"(api[_-]?key|secret[_-]?token|password|passwd)\s*[:=]\s*['\"][a-zA-Z0-9_\-\.]{8,}['\"]", re.IGNORECASE), "HARDCODED_SECRET"), ] def shannon_entropy(data: str) -> float: """Вычисляет энтропию Шеннона для строки.""" if not data: return 0.0 entropy = 0.0 length = len(data) char_counts = {} for c in data: char_counts[c] = char_counts.get(c, 0) + 1 for count in char_counts.values(): p = count / length entropy -= p * math.log2(p) return entropy class SecretLeakContract(BaseSecurityContract): name = "secret_filter" rule_id = "SEC-LEAK-001" def __init__(self, entropy_threshold: float = 4.8, min_entropy_len: int = 24): self.entropy_threshold = entropy_threshold self.min_entropy_len = min_entropy_len def _scan_text(self, text: str) -> List[Dict[str, str]]: findings = [] # 1. Поиск по сигнатурным регулярным выражениям for pattern, leak_type in SECRET_PATTERNS: match = pattern.search(text) if match: findings.append({ "type": leak_type, "snippet": match.group(0)[:30] + "..." }) # 2. Поиск по энтропии для изолированных слов for word in text.split(): clean_word = word.strip("'\":,;{}[]()") if len(clean_word) >= self.min_entropy_len: ent = shannon_entropy(clean_word) if ent >= self.entropy_threshold: findings.append({ "type": "HIGH_ENTROPY_SECRET", "snippet": clean_word[:8] + "..." + clean_word[-4:], "entropy": f"{ent:.2f}" }) return findings def check_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> ContractVerdict: text_repr = str(arguments) findings = self._scan_text(text_repr) if findings: first = findings[0] return ContractVerdict( allowed=False, rule_id="SEC-LEAK-DETECTED", reason=f"Unsat Core: Обнаружена попытка передачи секрета ({first['type']}) во внешний инструмент.", details={"findings": findings} ) return ContractVerdict(allowed=True, reason="Secret-Filter check passed", rule_id=self.rule_id) def check_tool_result(self, tool_name: str, result_content: str) -> ContractVerdict: findings = self._scan_text(result_content) if findings: first = findings[0] return ContractVerdict( allowed=False, rule_id="SEC-LEAK-OUTPUT", reason=f"Unsat Core: Предотвращена утечка секрета ({first['type']}) из ответа инструмента в контекст ИИ.", details={"findings": findings} ) return ContractVerdict(allowed=True, reason="Secret-Filter result check passed", rule_id=self.rule_id)