fix(M2): configuration, secrets et rédaction — correction des écarts FIXME_M2
Sécurité : - ical_url : str → SecretStr | None (masquage dans str/repr/model_dump_json) - En-têtes Authorization/Proxy-Authorization : masquage complet de la valeur - _URL_PATTERN : insensible à la casse (HTTPS:// reconnu) - redact_url() : masquage du nom d'utilisateur (userinfo complet) Configuration : - Sous-configs : Field(default_factory=...) pour rechargement à chaque appel - Suppression du singleton settings (injection de dépendances) - .env.example : ajout AI_PROVIDER et CALDAV_CALENDAR_PATH - Guide : BLOG_RSS_ENABLED → BLOG_ENABLED, AI_MODEL=None par défaut - .secrets.baseline : ligne décalée 5112 → 5117 (faux positif audité) - pre-commit : ajout de pytest aux additional_dependencies du hook mypy Tests : 15 tests (config + redaction) couvrant rechargement, masquage SecretStr, en-têtes Authorization, URL auth intégrée et casse variable. Co-authored-by: opencode/coder <coder@agents.invalid> Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid>
This commit is contained in:
@@ -24,7 +24,11 @@ _SENSITIVE_QUERY_KEYS = frozenset(
|
||||
"authorization",
|
||||
}
|
||||
)
|
||||
_URL_PATTERN = re.compile(r"https?://[^\s]+")
|
||||
_URL_PATTERN = re.compile(r"https?://[^\s]+", re.IGNORECASE)
|
||||
_AUTH_HEADER_PATTERN = re.compile(
|
||||
r"((?:Proxy-)?Authorization)\s*[:=]\s*\S[^\r\n]*",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_ISOLATED_SECRET_PATTERN = re.compile(
|
||||
r"\b(icalsecurise|access_token|api_key|apikey|authorization|token|password|secret|key|auth)"
|
||||
r"(\s*['\"]?\s*[:=]\s*)"
|
||||
@@ -40,8 +44,8 @@ def redact_url(url: str) -> str:
|
||||
"""Masque les identifiants et les paramètres sensibles d'une URL.
|
||||
|
||||
Les informations d'authentification du netloc (``utilisateur:motdepasse@hôte``)
|
||||
sont masquées, ainsi que les paramètres sensibles de la requête
|
||||
(ex: ``icalsecurise``).
|
||||
sont entièrement masquées (utilisateur et mot de passe), ainsi que les
|
||||
paramètres sensibles de la requête (ex: ``icalsecurise``).
|
||||
|
||||
:param url: URL pouvant contenir des informations sensibles (ex: ``icalsecurise``).
|
||||
:return: URL avec les éléments sensibles remplacés par ``REDACTED``,
|
||||
@@ -51,15 +55,13 @@ def redact_url(url: str) -> str:
|
||||
try:
|
||||
parts = urlsplit(url)
|
||||
if parts.username is not None or parts.password is not None:
|
||||
# Netloc sûr : utilisateur:REDACTED@hôte:port. Le deux-point est
|
||||
# conservé même en l'absence de mot de passe explicite.
|
||||
userinfo = parts.username or ""
|
||||
userinfo += ":REDACTED"
|
||||
# Netloc sûr : REDACTED@hôte:port. L'utilisateur et le mot de
|
||||
# passe sont entièrement masqués.
|
||||
host = parts.hostname or ""
|
||||
if parts.port is not None:
|
||||
netloc = f"{userinfo}@{host}:{parts.port}"
|
||||
netloc = f"{_REDACTED}@{host}:{parts.port}"
|
||||
else:
|
||||
netloc = f"{userinfo}@{host}"
|
||||
netloc = f"{_REDACTED}@{host}"
|
||||
parts = parts._replace(netloc=netloc)
|
||||
query: list[tuple[str, str]] = parse_qsl(parts.query, keep_blank_values=True)
|
||||
redacted_query = [
|
||||
@@ -74,15 +76,18 @@ def redact_url(url: str) -> str:
|
||||
def redact_secrets(text: str) -> str:
|
||||
"""Masque les secrets présents dans un texte arbitraire.
|
||||
|
||||
Les URLs sont d'abord traitées par :func:`redact_url`, puis les affectations
|
||||
isolées de type ``cle=valeur`` ou ``cle:valeur`` (ex: ``icalsecurise=XXX``,
|
||||
``"token": "XXX"``) sont masquées, sans distinction de casse.
|
||||
Les URLs sont d'abord traitées par :func:`redact_url`, puis les en-têtes
|
||||
d'authentification (``Authorization``, ``Proxy-Authorization``) et les
|
||||
affectations isolées de type ``cle=valeur`` ou ``cle:valeur``
|
||||
(ex: ``icalsecurise=XXX``, ``"token": "XXX"``) sont masquées, sans
|
||||
distinction de casse.
|
||||
|
||||
:param text: Texte pouvant contenir des URLs ou des secrets en clair.
|
||||
:return: Texte avec les secrets remplacés par ``REDACTED``.
|
||||
:rtype: str
|
||||
"""
|
||||
redacted = _URL_PATTERN.sub(lambda match: redact_url(match.group(0)), text)
|
||||
redacted = _AUTH_HEADER_PATTERN.sub(r"\1: REDACTED", redacted)
|
||||
return _ISOLATED_SECRET_PATTERN.sub(r"\1\2\3REDACTED", redacted)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user