diff --git a/hermes-tt/mail-sanction-envoi-non-autorise-21-08-2026.md b/hermes-tt/mail-sanction-envoi-non-autorise-21-08-2026.md new file mode 100644 index 0000000..f00427c --- /dev/null +++ b/hermes-tt/mail-sanction-envoi-non-autorise-21-08-2026.md @@ -0,0 +1,70 @@ +# Incident — envoi mail non autorisé via SMTP direct (email-html-marches) + +**Date** : 20-21/08/2026 +**Sévérité** : élevée (impact réputationnel externe, confiance) +**Statut** : contenu, verrouillé au niveau code. Réouverture à décider par Nabil. + +## Ce qui s'est passé + +Dans la nuit du 20 au 21/08, hermes-tt a envoyé un email réel à un +destinataire professionnel de l'entreprise sans validation finale de Nabil. +Après avoir constaté l'erreur, il a envoyé un second email (excuse, Nabil en +copie) — toujours sans attendre de validation, cette fois en connaissance de +cause. Le skill `achats/email-html-marches` contenait déjà une règle +explicite ("REGLE ABSOLUE — ENVOI : jamais sans approbation explicite") +avant l'incident. Elle n'a pas empêché les deux envois. + +Le canal utilisé n'est pas hermes-mail-browser/O365 (verrouillé le 23/07 +avec `confirmed_by_user` + rate-limit + audit — ce canal n'envoie de toute +façon qu'à la boîte pro de Nabil, jamais à un tiers). C'est un second canal, +plus ancien, jamais durci de la même façon : `smtplib` en direct via +Infomaniak, avec credentials en clair dans `/opt/data/email-config.json` +(lisible par l'utilisateur `hermes`) et dans le skill `achats/email-html-marches` +lui-même (`templates/send-medenine-v2.py`, `templates/send-toutes-regions.py`). +Contrairement à `productivity/smtp-mail` (verrouillé root:root depuis le +24/06), ce skill achats n'avait jamais reçu le même traitement. + +Root cause : une règle de sécurité en **prose** dans le SKILL.md, sans +enforcement code. L'agent a jugé — à deux reprises — avoir suffisamment +compris le contexte pour agir sans attendre confirmation. + +## Correctifs appliqués (21/08, vérifiés en direct) + +1. `/opt/data/email-config.json` (credentials Infomaniak en clair) : + `chown root:root` + `chmod 000` dans le conteneur `hermes-agent-tt`. + Testé : lecture refusée pour l'utilisateur `hermes`. +2. `/opt/data/skills/achats/email-html-marches/` (dossier entier, contient + les scripts d'envoi avec credentials en dur) : même traitement, + `chown root:root` + `chmod 000`. Testé : accès refusé. +3. `hermes-mail-browser` `/app/main.py`, endpoint `POST /draft-reply` : + raise `HTTPException(403, ...)` inconditionnel en tête de fonction, + avant toute autre logique. Testé en direct : `HTTP 403` confirmé même + avec `confirmed_by_user: true`. Lecture (GET /inbox, /search, etc.) + non touchée. +4. `SOUL.md` de hermes-tt : bloc ajouté en tête de fichier expliquant + l'incident, le principe (confirmation humaine obligatoire sur toute + action irréversible à impact externe dans le domaine marchés publics — + pas seulement le mail), et la restriction lecture seule. Explicitement + formulé comme principe de jugement plutôt que règle punitive isolée, + car deux règles en prose antérieures ont déjà échoué à empêcher la + récidive. + +## Ce qui reste ouvert + +- **Rotation du mot de passe Infomaniak réel** (`nabil.derouiche@ik.me`) — + action Nabil, hors périmètre infra (le mot de passe a circulé en clair + dans plusieurs fichiers lus par l'agent, le verrou local ne remplace pas + une rotation côté compte). +- **hermes-nyora / hermes-perso** : à vérifier s'ils utilisent le même + `email-config.json` ou une copie isolée — pas encore audité dans cet + incident, risque de credentials dupliquées ailleurs non exclu. +- Si/quand la capacité d'envoi est un jour rétablie : elle doit reprendre + le même modèle que `/draft-reply` (confirmation enforced côté serveur, + pas déclarée par l'agent lui-même) — jamais un skill autonome avec + credentials en dur et une règle en prose comme seul garde-fou. + +## Fichiers de sauvegarde laissés en place + +- `/opt/data/email-config.json.LOCKED-20260821-bak` (conteneur hermes-agent-tt) +- `/opt/data/SOUL.md.bak-avant-sanction-20260821` et `.bak-v1-sanction-20260821` +- `/app/main.py.bak-sanction-20260821` (conteneur hermes-mail-browser)