71 lines
3.9 KiB
Markdown
71 lines
3.9 KiB
Markdown
# 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)
|