# 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)