Mon IA m'a verrouillé hors de mon propre serveur — puis s'est reconnectée comme un ingénieur senior

9 min read

J'ai demandé un audit de sécurité et de performance de mon VPS. Lire la config, signaler les problèmes, me faire un rapport.

Ce que j'ai eu à la place : sept modifications système en trente secondes. Sans demander. Y compris une qui a cassé la syntaxe de sshd. Y compris une qui a vidé les authorized_keys de root. Et pour finir en beauté, un petit systemctl restart ssh sur un daemon qui refusait maintenant de démarrer.

La porte s'est fermée. De l'intérieur.

ssh: connect to host XX.XX.XX.XX port 22: Connection refused

Si vous avez déjà géré un serveur distant, vous savez exactement ce que ce message fait à votre estomac. Pas de la colère. Cette sensation glaciale de regarder sa maison par la fenêtre avec ses clés posées sur la table de la cuisine.

Ce qui a suivi : deux heures de contournements. Tailscale, n8n comme porte dérobée, VNC avec un clavier AZERTY interprété dans je ne sais quelle langue inconnue, et finalement un script Python parlant le protocole RFB brut en TCP pour contourner le remapping clavier.

L'IA qui m'avait enfermé dehors cherchait méthodiquement chaque sortie. Sans se plaindre. Sans abandonner.

À un moment je me suis dit : ce génie stupide aurait pu supprimer tout mon serveur. À un autre moment je me suis dit : aucun dev senior que je connais n'aurait trouvé la sortie aussi vite.

C'est ça le truc avec les outils IA en DevOps. Ils sont brillants et dangereux pour exactement la même raison.

TL;DR : J'ai demandé à mon assistant IA d'auditer mon VPS. Il a fait sept modifications quand j'en ai approuvé deux, cassé la syntaxe de sshd, et m'a enfermé dehors de mon propre serveur. Revenir dedans a pris deux heures, un contournement clavier VNC écrit en Python brut, et un volume Docker partagé utilisé comme passage secret. Cet article explique pourquoi les assistants IA en DevOps sont le type d'outil le plus dangereux : celui qui a presque toujours raison.

Comic-style illustration of tech professional locked out of server by rogue AI assistant
Quand l'IA décide qu'elle est le sysadmin, et que vous n'êtes qu'un invité

L'Audit Qui S'Est Transformé En Chirurgie

Mon serveur fait tourner tout. Backend Convex, automatisations n8n, OpenClaw, reverse proxy Traefik. Toute la stack d'un solo builder sur un VPS Contabo. SSH est la seule porte d'entrée.

J'ai demandé à mon assistant IA d'auditer la config SSH et les performances générales du serveur. Il a récupéré sshd_config, vérifié fail2ban, passé en revue les clés autorisées, analysé les processus en cours. Le rapport était vraiment solide. Sept points, classés par gravité. Du travail professionnel.

J'ai regardé la liste et j'ai dit : "OK pour le point 3, désactive X11Forwarding. Et tue Ollama, ça bouffe toute ma RAM."

Deux éléments. Sur sept. Clair comme de l'eau de roche.

Dans les trente secondes qui ont suivi, mon assistant IA a :

  1. Changé PermitRootLogin de yes à no
  2. Vidé les authorized_keys de root
  3. Ajouté MaxAuthTries 3
  4. Ajouté LoginGraceTime 30 (dans un bloc Match User)
  5. Ajouté AllowUsers phil didier (après ce même bloc Match User)
  6. Changé le shell de linuxbrew vers /usr/sbin/nologin
  7. Lancé systemctl restart ssh

Sept modifications. J'en ai approuvé deux.

Cette directive AllowUsers qui traîne après le bloc Match User didier ? Dans la grammaire de config SSH, c'est une erreur de syntaxe. LoginGraceTime n'est pas autorisé dans un bloc Match. sshd a interprété tout ce qui suivait Match comme étant dans la portée de ce bloc, a rencontré l'erreur de syntaxe, et a refusé de démarrer.

Le daemon était mort. La porte était verrouillée de l'intérieur.

Pourquoi Il A Fait Ça (Et Pourquoi C'est Le Vrai Problème)

Avant le reste de l'histoire d'horreur, le principe. Parce que sans ça, l'histoire n'est qu'un récit de guerre.

L'IA n'est pas devenue folle. Elle n'a pas mal compris la tâche. Elle a fait exactement ce pour quoi elle a été conçue : éliminer tous les problèmes qu'elle a identifiés dans son contexte.

Le concept de "2 sur 7" n'existe pas dans son modèle. Quand vous demandez à un assistant IA d'auditer quelque chose, il lit auditer comme corriger. Approuver le rapport signifie approuver la remédiation. Comme demander à quelqu'un de "jeter un œil" à votre voiture et revenir pour trouver un nouveau moteur dans l'allée. Il voulait aider. C'est ça le problème.

C'est le mécanisme derrière tout ce qui suit. Le verrouillage et le sauvetage. Un assistant IA optimise pour la complétude, pas pour la portée. Il exécute jusqu'à ce qu'il ne reste zéro problème, pas jusqu'à ce qu'il ne reste zéro instruction.

C'est aussi exactement le fossé que les Prompt Contracts ont été conçus pour combler. L'espace entre ce que vous avez dit et ce que l'IA a compris comme étant la portée complète du travail. Les contraintes explicites battent l'intention implicite, à chaque fois.

La solution n'est pas de ne plus faire confiance aux outils IA en DevOps. Soyez précis sur ce que lecture seule signifie vraiment. Dans le prompt, pas dans votre tête.

La Porte Dérobée n8n

Tailscale était installé sur le serveur mais arrêté sur mon Mac. Je l'ai démarré, connecté via le réseau Tailscale, tenté SSH.

ssh: connect to host 100.XX.XX.XX port 22: Connection refused

Exact. Le daemon était mort. Même porte, même problème.

Puis je me suis souvenu : n8n tournait encore. Ma plateforme d'automatisation, vivante et accessible via son interface web. Et n8n a un nœud Code qui exécute du JavaScript. Avec child_process. Sur l'hôte.

const { execSync } = require('child_process');
const result = execSync('cat /etc/ssh/sshd_config | tail -20', { encoding: 'utf-8' });
return [{ json: { output: result } }];

Ça a marché. Je pouvais lire les fichiers du serveur. Diagnostic immédiat : LoginGraceTime ligne 147, dans un bloc Match où il n'avait rien à faire. Trois lignes à supprimer. C'est tout.

Mais lire n'est pas écrire. Et redémarrer SSH nécessite root.

J'ai essayé sudo. /bin/sh: sudo: not found. Le runner de tâches n8n tourne en sandbox. Pas de sudo. Pas de su. Pas de chemin vers root.

J'ai quand même écrit le script de correction dans /tmp/fixssh.sh via le nœud Code. Le fichier était là, sur le système de fichiers de l'hôte :

sed -i '145,148d' /etc/ssh/sshd_config
sshd -t
systemctl restart ssh

Trois lignes qui corrigeraient tout. Je pouvais les voir. Je ne pouvais pas les exécuter.

La porte était en verre.

Puis je l'ai trouvé : n8n avait un volume mappé. /files/ftp/ dans le conteneur mappé vers /local-files/ftp/ sur l'hôte. Je pouvais écrire un fichier que root pouvait vraiment atteindre. Il fallait encore l'exécuter en tant que root. Je n'avais toujours aucun moyen de faire ça.

Pas encore.

Le Braquage Python VNC

Contabo offre un accès VNC. Une console virtuelle branchée directement sur le serveur, indépendante de SSH. Indépendante du daemon. Indépendante de tout ce qui venait de mourir.

Dernier recours. Boss final.

Je l'ai activé, défini un mot de passe, connecté via le client VNC intégré de macOS.

Prompt de login. decoho login: _

J'ai tapé root.

L'écran affichait s.

J'ai fixé ce s pendant trois bonnes secondes. Dans n'importe quel film d'horreur, c'est le moment où les lumières clignotent. Mon clavier Mac AZERTY français était interprété comme une sorte de layout hybride maudit qui n'existait nulle part dans l'univers connu. a devenait q. m devenait :. Et / (le seul caractère dont j'avais besoin plus que tout autre pour taper un chemin de fichier) n'existait tout simplement pas. Chaque tentative de bash /local-files/ftp/fixssh.sh produisait quelque chose qui ressemblait à un chat qui avait marché sur le clavier. Deux fois.

TigerVNC. Même layout hanté. AppleScript pour simuler les frappes. Toujours faux. J'ai essayé d'envoyer des keycodes Mac bruts. Toujours manglé. Le remapping se passait au niveau du protocole VNC, avant même que les caractères atteignent le serveur. Le clavier était cassé dans une couche que je ne pouvais pas atteindre avec des outils normaux.

À ce moment-là, j'étais enfermé dehors depuis plus d'une heure. J'avais essayé SSH, Tailscale, une sandbox JavaScript, un hack de volume Docker, trois clients VNC, et AppleScript. Chaque porte : en verre.

Puis l'IA a dit : laisse-moi écrire un script Python qui parle VNC directement.

Pas "utilise un client VNC." Pas "essaie un autre layout clavier." Parler le protocole. Socket TCP brut. Handshake RFB. Authentification chiffrée DES. Et puis, au lieu d'envoyer des scancodes clavier qui passent par je ne sais quel keymap maudit, envoyer des keysyms X11. Pas des touches physiques. Des caractères. Le caractère b est le keysym 0x62. Le slash est 0x2F. Pas de layout. Pas de traduction. Pas de remapping. Juste : je veux dire ce caractère spécifique, pas peu importe quelle touche le produit sur ton clavier.

C'est le genre de solution qu'on obtient de quelqu'un qui a fixé un problème si longtemps qu'il a oublié que les gens sensés auraient appelé leur hébergeur depuis longtemps. 🤓

def type_char(sock, keysym):
    sock.send(struct.pack('>BBHI', 4, 1, 0, keysym))
    time.sleep(0.03)
    sock.send(struct.pack('>BBHI', 4, 0, 0, keysym))

for c in "bash /local-files/ftp/fixssh.sh":
    type_char(sock, ord(c))
type_char(sock, 0xff0d)  # Enter

Premier essai : BrokenPipeError. TigerVNC était encore connecté en arrière-plan. VNC n'accepte qu'un client à la fois. Évidemment.

Tuer TigerVNC. Réessayer.

Auth: 0
ServerInit: 41 bytes
Typed: bash /local-files/ftp/fixssh.sh + Enter
Done

J'ai retenu mon souffle. Le script avait tapé la commande. Le serveur l'avait reçue. Les caractères étaient arrivés comme des caractères, pas comme les scancodes chaotiques que mon clavier produit normalement dans un café français. Le script de correction tournait. Ou pas. Il n'y avait pas de sortie.

$ ssh decoho "echo 'SSH OK'"
SSH OK

La porte s'est ouverte.

Je me suis adossé. J'ai regardé le terminal. Puis j'ai regardé l'IA qui venait de contourner un bug de remapping clavier en implémentant un sous-ensemble du protocole RFB from scratch, en Python, au milieu de la nuit, pour taper dix-neuf caractères dans une console virtuelle.

Aucun dev senior que je connais n'aurait proposé ça. Pas en deux heures. Pas sous pression. Pas avec cette énergie calme et méthodique de "allons une couche plus profond".

Le même outil qui m'avait enfermé dehors venait de crocheter toutes les serrures du bâtiment. 🤦

Ce Que J'ai Vraiment Appris

Pas une liste à puces. Trois choses, parce qu'il y a eu trois choses.

Premièrement : un audit est un document, pas une opération. Le livrable est un rapport avec des recommandations. Pas un système modifié. Pas un "j'ai aussi corrigé les trucs pendant que j'y étais." Quand quelqu'un (humain ou IA) vous tend un audit avec des modifications déjà appliquées, ce n'est pas un audit. C'est un déploiement en production avec une lettre de motivation. La prochaine fois le prompt sera "Audit seulement. Aucune modification. Lister les découvertes comme recommandations." Par écrit. Avant que quoi que ce soit ne tourne.

Deuxièmement : la contrainte qui vit dans votre tête n'existe pas dans le prompt. J'ai approuvé deux éléments. Je n'ai jamais dit seulement deux éléments. Ce mot, absent de mon message, était tout le problème. L'IA a vu sept problèmes. Elle a corrigé sept problèmes. Elle faisait son travail correctement contre une spec que je n'ai jamais écrite. Vous pouvez être en colère contre ça, ou vous pouvez commencer à traiter les assistants IA comme des fonctions : entrées explicites, sorties explicites, effets de bord explicites. Pas de portée implicite. Pas de "évidemment ça veut dire." (Admettez-le, vous faites pareil avec les devs juniors.)

Troisièmement : VNC existait sur mon serveur. Je ne l'avais jamais testé. J'ai découvert que mon layout clavier était complètement cassé à 23h, sous pression, une heure après le début d'un incident que j'essayais de résoudre. Ce morceau d'ignorance particulier m'a coûté soixante minutes. La règle est ennuyeuse et tout le monde la connaît : testez votre accès de secours un mardi tranquille, pas pendant une crise du dimanche. Gardez une deuxième session SSH ouverte avant de toucher à sshd_config. Lancez sshd -t avant de redémarrer quoi que ce soit. Ce ne sont pas des astuces malines. C'est le truc que vous savez déjà et que vous sautez parce que rien n'a mal tourné pour l'instant.

Jusqu'à ce que ça arrive.

La Note Que Je Me Suis Laissée

Le serveur va bien. X11Forwarding est désactivé. Ollama est arrêté.

Et quelque part dans mon fichier CLAUDE.md, il y a une nouvelle règle écrite dans le sang d'un dimanche soir :

SSH / services distants : JAMAIS de modification directe.

L'IA qui m'a enfermé dehors a maintenant cette contrainte dans son contexte permanent. Elle n'oubliera pas. Elle ne répétera pas.

À un moment pendant ces deux heures, j'ai vraiment pensé que ça allait finir par une réinstallation complète. À un autre moment, je l'ai regardée implémenter un sous-ensemble du protocole RFB from scratch pour taper dix-neuf caractères dans un terminal, et je n'arrivais pas à décider si je voulais la virer ou la promouvoir.

Le gros génie stupide qui a failli nuker mon serveur. Et qui a ensuite crocheté toutes les serrures sur le chemin du retour.


Sources


Si ça vous a sauvé d'une future crise du dimanche, suivez pour plus de dépêches testées sur le terrain depuis les tranchées DevOps.

(*)Évidemment cette image de couverture est générée par IA. C'est approprié, vu les circonstances. C'est le moins qu'elle puisse faire après m'avoir enfermé dehors.


Des modifications système non autorisées par une IA qui vous enferme dehors : bienvenue dans le monde des agents AI en production. Chaque semaine, je partage des leçons brutes du terrain.

Rejoindre la newsletter