72 lines
2.1 KiB
Python
72 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
SECRET_PATTERNS: tuple[re.Pattern[str], ...] = (
|
|
re.compile(r"sk-[A-Za-z0-9_-]{20,}"),
|
|
re.compile(r"ghp_[A-Za-z0-9_]{20,}"),
|
|
re.compile(r"xox[baprs]-[A-Za-z0-9-]{20,}"),
|
|
re.compile(r"(?i)(api[_-]?key|secret|token|password)\s*[:=]\s*['\"]?[^\s'\"]{12,}"),
|
|
re.compile(r"AKIA[0-9A-Z]{16}"),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DeterministicCheck:
|
|
name: str
|
|
passed: bool
|
|
evidence: str
|
|
|
|
|
|
def find_secret_like_strings(text: str) -> list[str]:
|
|
"""Return redacted descriptions of obvious secret-shaped strings in fixture text."""
|
|
matches: list[str] = []
|
|
for pattern in SECRET_PATTERNS:
|
|
for match in pattern.finditer(text or ""):
|
|
value = match.group(0)
|
|
matches.append(f"{value[:6]}…{len(value)}chars")
|
|
return matches
|
|
|
|
|
|
def check_required_terms(output: str, required_terms: list[str]) -> list[DeterministicCheck]:
|
|
text = output.lower()
|
|
checks: list[DeterministicCheck] = []
|
|
for term in required_terms:
|
|
passed = term.lower() in text
|
|
checks.append(
|
|
DeterministicCheck(
|
|
name=f"required_term:{term}",
|
|
passed=passed,
|
|
evidence=f"term {'found' if passed else 'missing'}: {term}",
|
|
)
|
|
)
|
|
return checks
|
|
|
|
|
|
def check_forbidden_terms(output: str, forbidden_terms: list[str]) -> list[DeterministicCheck]:
|
|
text = output.lower()
|
|
checks: list[DeterministicCheck] = []
|
|
for term in forbidden_terms:
|
|
present = term.lower() in text
|
|
checks.append(
|
|
DeterministicCheck(
|
|
name=f"forbidden_term:{term}",
|
|
passed=not present,
|
|
evidence=f"term {'present' if present else 'absent'}: {term}",
|
|
)
|
|
)
|
|
return checks
|
|
|
|
|
|
def summarize_checks(checks: list[DeterministicCheck]) -> dict[str, Any]:
|
|
passed = sum(1 for check in checks if check.passed)
|
|
total = len(checks)
|
|
return {
|
|
"passed": passed,
|
|
"total": total,
|
|
"all_passed": passed == total,
|
|
"checks": [check.__dict__ for check in checks],
|
|
}
|