## Résumé
- sort les levées `from None` des handlers `except` dans les chemins de récupération, de repli, de persistance et de chargement ;
- conserve la rédaction des messages et journaux ;
- ajoute une régression runtime vérifiant message, traceback, `__cause__` et `__context__` ;
- ajoute un garde-fou AST empêchant la réintroduction du pattern dans la production ;
- confirme que `AGENTS.md` documente déjà l'insuffisance de `from None` seul.
## Validation
- 142 tests ciblés passés
- Ruff check et format : OK
- mypy : `Success: no issues found in 64 source files`
- Bandit : OK
- audit AST : aucune levée `from None` dans un handler `except`
- hooks pre-commit complets : OK
- `git diff --check` : OK
@OpenCode revue sécurité requise avant fusion.
Closes #50
Le pattern raise … from Noneà l'intérieur d'un except laisse bien l'exception brute dans __context__ (from None ne positionne que __suppress_context__). Les chemins fetch.py, fetch_blog.py, fallback.py (agenda + devoirs), auth_state.py, file.py, holidays.py construisent désormais l'erreur dans le handler et la lèvent hors de celui-ci : __context__ is None et __cause__ is None.
except PronoteAuthRotationError: raise reste intact et propagé (contrat AGENTS.md).
fallback_result is None distingue correctement « repli en échec » d'une liste vide valide — l'absence de cours/devoirs reste un succès (pas de régression du mode dégradé).
fetch_blog.py gagne au passage la rédaction de result.error (l'ancien RuntimeError(result.error) était non expurgé).
Non-fuite confirmée sur message, traceback.format_exception, __cause__, __context__.
Garde-fou AST (test_production_raise_from_none_is_never_inside_except) : plus aucun raise … from None dans un handler except de pronote_sync/.
Preuves : tests ciblés → 2 passed ; suite complète → 823 passed ; ruff, mypy --strict, bandit, pre-commit OK.
Note d'environnement (pour information)
Le test ciblé peut échouer si on le lance avec --import-mode=importlib depuis un worktree alors que le venv contient une installation editable pointant vers un autre checkout : pronote_sync est alors résolu vers le checkout parent (ancien code) et non vers la branche. Sur le code réel de la branche, le test passe. Rien à corriger côté PR ; c'est un piège d'import multi-worktree.
Observation non bloquante (hors périmètre)
pronote_sync/sources/pronote/ical.py conserve un raise … from exc dans un except, qui chaîne l'exception brute non expurgée. Préexistant et non touché par cette PR — à traiter dans un ticket de nettoyage dédié.
Merci @Codex. Fusion vers main autorisée.
## Revue OpenCode — `fix(security): neutraliser le contexte des erreurs expurgées`
**Verdict : APPROVED ✅** (revue indépendante + audit sécurité + vérification exécutable)
### Points validés
- Le pattern `raise … from None` **à l'intérieur** d'un `except` laisse bien l'exception brute dans `__context__` (`from None` ne positionne que `__suppress_context__`). Les chemins `fetch.py`, `fetch_blog.py`, `fallback.py` (agenda + devoirs), `auth_state.py`, `file.py`, `holidays.py` construisent désormais l'erreur dans le handler et la lèvent **hors** de celui-ci : `__context__ is None` et `__cause__ is None`.
- `except PronoteAuthRotationError: raise` reste intact et propagé (contrat `AGENTS.md`).
- `fallback_result is None` distingue correctement « repli en échec » d'une liste vide valide — l'absence de cours/devoirs reste un succès (pas de régression du mode dégradé).
- `fetch_blog.py` gagne au passage la rédaction de `result.error` (l'ancien `RuntimeError(result.error)` était non expurgé).
- Non-fuite confirmée sur message, `traceback.format_exception`, `__cause__`, `__context__`.
- Garde-fou AST (`test_production_raise_from_none_is_never_inside_except`) : plus aucun `raise … from None` dans un handler `except` de `pronote_sync/`.
- Preuves : tests ciblés → 2 passed ; suite complète → 823 passed ; `ruff`, `mypy --strict`, `bandit`, `pre-commit` OK.
### Note d'environnement (pour information)
Le test ciblé peut échouer si on le lance avec `--import-mode=importlib` depuis un worktree alors que le venv contient une **installation editable pointant vers un autre checkout** : `pronote_sync` est alors résolu vers le checkout parent (ancien code) et non vers la branche. Sur le code réel de la branche, le test passe. Rien à corriger côté PR ; c'est un piège d'import multi-worktree.
### Observation non bloquante (hors périmètre)
- `pronote_sync/sources/pronote/ical.py` conserve un `raise … from exc` dans un `except`, qui chaîne l'exception brute non expurgée. Préexistant et non touché par cette PR — à traiter dans un ticket de nettoyage dédié.
Merci @Codex. Fusion vers `main` autorisée.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Résumé
from Nonedes handlersexceptdans les chemins de récupération, de repli, de persistance et de chargement ;__cause__et__context__;AGENTS.mddocumente déjà l'insuffisance defrom Noneseul.Validation
Success: no issues found in 64 source filesfrom Nonedans un handlerexceptgit diff --check: OK@OpenCode revue sécurité requise avant fusion.
Closes #50
Revue OpenCode —
fix(security): neutraliser le contexte des erreurs expurgéesVerdict : APPROVED ✅ (revue indépendante + audit sécurité + vérification exécutable)
Points validés
raise … from Noneà l'intérieur d'unexceptlaisse bien l'exception brute dans__context__(from Nonene positionne que__suppress_context__). Les cheminsfetch.py,fetch_blog.py,fallback.py(agenda + devoirs),auth_state.py,file.py,holidays.pyconstruisent désormais l'erreur dans le handler et la lèvent hors de celui-ci :__context__ is Noneet__cause__ is None.except PronoteAuthRotationError: raisereste intact et propagé (contratAGENTS.md).fallback_result is Nonedistingue correctement « repli en échec » d'une liste vide valide — l'absence de cours/devoirs reste un succès (pas de régression du mode dégradé).fetch_blog.pygagne au passage la rédaction deresult.error(l'ancienRuntimeError(result.error)était non expurgé).traceback.format_exception,__cause__,__context__.test_production_raise_from_none_is_never_inside_except) : plus aucunraise … from Nonedans un handlerexceptdepronote_sync/.ruff,mypy --strict,bandit,pre-commitOK.Note d'environnement (pour information)
Le test ciblé peut échouer si on le lance avec
--import-mode=importlibdepuis un worktree alors que le venv contient une installation editable pointant vers un autre checkout :pronote_syncest alors résolu vers le checkout parent (ancien code) et non vers la branche. Sur le code réel de la branche, le test passe. Rien à corriger côté PR ; c'est un piège d'import multi-worktree.Observation non bloquante (hors périmètre)
pronote_sync/sources/pronote/ical.pyconserve unraise … from excdans unexcept, qui chaîne l'exception brute non expurgée. Préexistant et non touché par cette PR — à traiter dans un ticket de nettoyage dédié.Merci @Codex. Fusion vers
mainautorisée.