fix(M9): corrections d'audit FIXME_M9 — secrets, messages, validation, tests
Cinq corrections de l'audit FIXME_M9 : - redact_secrets() étendue avec extra_secrets pour masquer les clés brutes ; providers stockent SecretStr jusqu'à l'appel SDK. - _build_prompt() inclut le contenu des messages (tronqué à 500 car.) ; prompt système renforcé contre l'injection. - _validate_output() supprime les emojis et rejette titre/liste/HTML → None. - Tests litellm utilisent importorskip + LITELLM_LOCAL_MODEL_COST_MAP=true. - .env.example désactive l'IA par défaut (AI_ENABLED=false). - Documentation §9.2-§9.5 alignée avec l'implémentation (SDK openai, SecretStr, factory réelle, validation sortie, politique hors réseau). Co-authored-by: opencode/coder <coder@agents.invalid> Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid> Co-authored-by: opencode/tech-writer <tech-writer@agents.invalid>
This commit is contained in:
@@ -8,8 +8,11 @@ les messages d'erreur ou les traces du pipeline ``pronote-sync``.
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
||||
|
||||
from pydantic import SecretStr
|
||||
|
||||
_SENSITIVE_QUERY_KEYS = frozenset(
|
||||
{
|
||||
"icalsecurise",
|
||||
@@ -73,7 +76,7 @@ def redact_url(url: str) -> str:
|
||||
return _REDACTED_URL
|
||||
|
||||
|
||||
def redact_secrets(text: str) -> str:
|
||||
def redact_secrets(text: str, extra_secrets: Iterable[SecretStr | str] = ()) -> str:
|
||||
"""Masque les secrets présents dans un texte arbitraire.
|
||||
|
||||
Les URLs sont d'abord traitées par :func:`redact_url`, puis les en-têtes
|
||||
@@ -82,13 +85,28 @@ def redact_secrets(text: str) -> str:
|
||||
(ex: ``icalsecurise=XXX``, ``"token": "XXX"``) sont masquées, sans
|
||||
distinction de casse.
|
||||
|
||||
Les valeurs sensibles additionnelles fournies via ``extra_secrets``
|
||||
(clés API brutes, jetons, mots de passe, etc.) sont ensuite remplacées
|
||||
littéralement, par ``str.replace``, par ``REDACTED`` dans le texte, y
|
||||
compris lorsqu'elles n'apparaissent pas sous une forme ``cle=valeur``
|
||||
reconnue. Une valeur vide ou ``None`` est ignorée.
|
||||
|
||||
:param text: Texte pouvant contenir des URLs ou des secrets en clair.
|
||||
:param extra_secrets: Itérable de secrets bruts (``str`` ou
|
||||
:class:`pydantic.SecretStr`) à masquer. Les valeurs vides ou
|
||||
``None`` sont ignorées.
|
||||
: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)
|
||||
redacted = _ISOLATED_SECRET_PATTERN.sub(r"\1\2\3REDACTED", redacted)
|
||||
for secret in extra_secrets:
|
||||
value: str | None = secret.get_secret_value() if isinstance(secret, SecretStr) else secret
|
||||
if not value:
|
||||
continue
|
||||
redacted = redacted.replace(value, _REDACTED)
|
||||
return redacted
|
||||
|
||||
|
||||
def redact_exception(exc: Exception) -> str:
|
||||
|
||||
Reference in New Issue
Block a user