fix(security): détecter les PIN Pronote littéraux

This commit is contained in:
2026-09-13 00:16:25 +02:00
parent f261fed1af
commit 894f5d137a
2 changed files with 83 additions and 8 deletions
+31 -8
View File
@@ -26,15 +26,15 @@ _TEXT_SUFFIXES = frozenset(
{".conf", ".ini", ".json", ".md", ".py", ".service", ".timer", ".toml", ".txt", ".yaml", ".yml"}
)
_LITERAL_SECRET_RE = re.compile(
r"(?ix)\b[a-z0-9_]*(?:api[_-]?key|access[_-]?token|auth(?:orization)?|icalsecurise|password|secret|token)"
r"\s*[:=]\s*['\"][^'\"\r\n]{3,}['\"]"
r"(?ix)\b[a-z0-9_]*(?:api[_-]?key|access[_-]?token|auth(?:orization)?|icalsecurise|password|pin|secret|token)"
r"\s*[:=]\s*['\"](?P<value>[^'\"\r\n]{3,})['\"]"
)
_UNQUOTED_SECRET_RE = re.compile(
r"(?ix)\b[a-z0-9_]*(?:api[_-]?key|access[_-]?token|auth(?:orization)?|icalsecurise|password|secret|token)"
r"\s*[:=]\s*[a-z0-9][a-z0-9._~+/-]{2,}"
r"(?ix)\b[a-z0-9_]*(?:api[_-]?key|access[_-]?token|auth(?:orization)?|icalsecurise|password|pin|secret|token)"
r"\s*[:=]\s*(?P<value>[a-z0-9][a-z0-9._~+/-]{2,})"
)
_URL_SECRET_RE = re.compile(
r"(?ix)[?&](?:api[_-]?key|access[_-]?token|auth(?:orization)?|icalsecurise|password|secret|token)"
r"(?ix)[?&](?:api[_-]?key|access[_-]?token|auth(?:orization)?|icalsecurise|password|pin|secret|token)"
r"=([^&#\s]{3,})"
)
_URL_PLACEHOLDER_RE = re.compile(
@@ -47,6 +47,13 @@ _URL_PLACEHOLDER_RE = re.compile(
r")$"
)
_EXTRA_NAMES = frozenset({"pronote_sync"})
_ASSIGNMENT_PLACEHOLDER_RE = re.compile(
r"(?ix)^(?:"
r"<(?:pin|secret|valeur|value|token|jeton)>|"
r"(?:change|replace|your)[_-]?(?:me|here|value|valeur|pin|password|secret)|"
r"(?:placeholder|example|local-not-required)"
r")$"
)
@dataclass(frozen=True)
@@ -68,6 +75,16 @@ CommandRunner = Callable[..., subprocess.CompletedProcess[str]]
ContentProvider = Callable[[Path], str | None]
def _is_assignment_placeholder(value: str) -> bool:
"""Indique si une valeur d'affectation est un placeholder documentaire.
:param value: Valeur extraite d'une affectation sensible.
:return: ``True`` si la valeur ne représente pas un secret réel.
:rtype: bool
"""
return _ASSIGNMENT_PLACEHOLDER_RE.fullmatch(value.strip()) is not None
def _is_candidate(path: Path) -> bool:
"""Indique si un chemin peut être analysé comme fichier texte.
@@ -188,9 +205,15 @@ def find_secrets(
for number, line in enumerate(content.splitlines(), start=1):
if _ALLOWLIST_MARKER in line:
continue
is_literal_secret = _LITERAL_SECRET_RE.search(line) or (
relative_path.suffix in _UNQUOTED_CONFIG_SUFFIXES
and _UNQUOTED_SECRET_RE.search(line)
literal_match = _LITERAL_SECRET_RE.search(line)
unquoted_match = (
_UNQUOTED_SECRET_RE.search(line)
if relative_path.suffix in _UNQUOTED_CONFIG_SUFFIXES
else None
)
is_literal_secret = any(
match is not None and not _is_assignment_placeholder(match.group("value"))
for match in (literal_match, unquoted_match)
)
if is_literal_secret:
findings.append(SecretFinding(relative_path, number, "affectation-litterale"))