"""Detection/redaction basique de secrets avant envoi a un fournisseur IA externe : cles privees SSH, jetons Bearer, cles AWS, en-tetes Authorization, password=/token=/api_key=, et longues chaines ressemblant a un secret. Heuristique volontairement simple (comme la detection de commandes dangereuses) : mieux vaut avertir a tort sur un hash ou un identifiant anodin que laisser passer un vrai secret.""" from __future__ import annotations import re from dataclasses import dataclass _PATTERNS: list[tuple[str, re.Pattern]] = [ ("Cle privee (PEM/OpenSSH)", re.compile( r"-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----" )), ("Jeton Bearer", re.compile(r"\bBearer\s+[A-Za-z0-9\-_.=]{10,}", re.IGNORECASE)), ("Cle d'acces AWS", re.compile(r"\b(?:AKIA|ASIA)[0-9A-Z]{16}\b")), ("En-tete Authorization", re.compile(r"\bAuthorization\s*:\s*\S+", re.IGNORECASE)), ("Mot de passe (password=)", re.compile(r"\b(?:password|passwd|pwd)\s*[:=]\s*\S+", re.IGNORECASE)), ("Cle API (api_key=)", re.compile(r"\b(?:api[_-]?key|apikey)\s*[:=]\s*\S+", re.IGNORECASE)), ("Jeton (token=)", re.compile(r"\btoken\s*[:=]\s*\S+", re.IGNORECASE)), ("Chaine longue ressemblant a un secret", re.compile(r"\b[A-Za-z0-9_\-]{32,}\b")), ] @dataclass class DetectedSecret: label: str excerpt: str def detect_secrets(text: str) -> list[DetectedSecret]: """Renvoie les categories detectees (au plus une occurrence par categorie, pour un resume lisible), dans l'ordre des motifs (du plus specifique au plus generique).""" found: list[DetectedSecret] = [] for label, pattern in _PATTERNS: match = pattern.search(text) if match: excerpt = match.group(0) if len(excerpt) > 60: excerpt = excerpt[:57] + "..." found.append(DetectedSecret(label, excerpt)) return found def redact(text: str) -> str: redacted = text for _label, pattern in _PATTERNS: redacted = pattern.sub("[MASQUE]", redacted) return redacted