File size: 1,378 Bytes
41016fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from __future__ import annotations

import re
from pathlib import Path

EMAIL_RE = re.compile(r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", re.IGNORECASE)
TOKEN_PATTERNS = [
    re.compile(r"\b(?:sk|hf|ghp|xox[baprs])_[A-Za-z0-9_-]{16,}\b"),
    re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
    re.compile(r"(?i)\b(?:api[_ -]?key|access[_ -]?token|secret[_ -]?key)\s*[:=]\s*['\"]?[A-Za-z0-9_\-/.+=]{12,}"),
]
PHONE_RE = re.compile(r"(?<!\d)(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)\d{3}[-.\s]?\d{4}(?!\d)")

TEXT_EXTENSIONS = {".txt", ".md", ".json", ".csv", ".yaml", ".yml", ".log"}
MAX_SCAN_BYTES = 2 * 1024 * 1024


def scan_text(text: str) -> list[str]:
    findings: list[str] = []
    if EMAIL_RE.search(text):
        findings.append("possible_email")
    if PHONE_RE.search(text):
        findings.append("possible_phone")
    if any(pattern.search(text) for pattern in TOKEN_PATTERNS):
        findings.append("possible_secret_or_token")
    return findings


def scan_file(path: str | Path) -> list[str]:
    file_path = Path(path)
    findings = scan_text(file_path.name)
    if file_path.suffix.lower() in TEXT_EXTENSIONS and file_path.stat().st_size <= MAX_SCAN_BYTES:
        try:
            findings.extend(scan_text(file_path.read_text(encoding="utf-8", errors="ignore")))
        except OSError:
            findings.append("scan_error")
    return sorted(set(findings))