from __future__ import annotations import re from dataclasses import dataclass from typing import Pattern from .entity_types import EntityType @dataclass(frozen=True, slots=True) class PatternSpec: entity_type: EntityType pattern: Pattern[str] capture_group: str | None = None # Patterns intentionally target high-signal formats. Regex detection is not a # compliance guarantee and must be supplemented for a particular organization. PATTERNS: tuple[PatternSpec, ...] = ( PatternSpec( EntityType.API_KEY, re.compile( r"(?i)\b(?:sk-[a-z0-9_-]{16,}|rk-[a-z0-9_-]{16,}|ghp_[a-z0-9]{20,}|github_pat_[a-z0-9_]{20,}|" r"xox[baprs]-[a-z0-9-]{10,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{20,})\b" ), ), PatternSpec( EntityType.API_KEY, re.compile( r"(?is)\b(?:api[_ -]?key|access[_ -]?token|auth[_ -]?token|secret|password|passwd)\s*" r"[:=]\s*[\"']?(?P[A-Za-z0-9_./+=-]{12,})" ), capture_group="value", ), PatternSpec( EntityType.URL, re.compile(r"(?i)\bhttps?://[^\s<>\"']+"), ), PatternSpec( EntityType.EMAIL, # Allow sentence punctuation after an address; the candidate # normalizer removes it without allowing a partial domain match. re.compile( r"(?i)(?|\r\n]+\\)+[^\\/:*?\"<>|\r\n]+?\.[A-Za-z0-9]{1,10}(?![\w])" ), ), PatternSpec( EntityType.FILE_PATH, re.compile(r"(?\"']+/)+[^\s/<>\"']+"), ), PatternSpec( EntityType.CARD_NUMBER, # Keep separators between digits, but never consume whitespace after # the final digit. Otherwise replacement text can glue to the next # word: ``[REDACTED_CARD_NUMBER]и``. re.compile(r"(?\d{10,12})(?!\d)"), capture_group="value", ), PatternSpec( EntityType.IP_ADDRESS, re.compile( r"(?