/images/avatar.png

Ce site tourne sur un NUC Intel hébergé à la maison, derrière une connexion fibre standard. Il sert avant tout de terrain d’expérimentation pour tester des configurations serveur, des scripts d’automatisation, et des outils de sécurité open source.

Pas un site professionnel — un homelab : on casse des choses, on les répare, on apprend.

🌟 À ne pas manquer

Trois articles qui résument l’esprit du homelab :

📚 La documentation complète est dans Documentation et les scripts d’automatisation dans Scripts.

🛠️ Stack technique

OutilRôle
OpenRestyReverse proxy · nginx + LuaJIT · TLS 1.3
🔒 CrowdSecIDS/IPS communautaire collaboratif
🧱 CrowdSec AppSecWAF inline · OWASP CRS 4.x
☁️ CloudflareCDN · WAF · DNS · DDoS
🌐 HugoGénérateur de site statique
🤖 MCP Hugo ServerServeur MCP pour piloter ce site depuis une IA
📊 BetterStackMonitoring · Alertes · Logs

🐛 Vous avez trouvé une faille ?

Si vous découvrez un bug, une mauvaise configuration ou une faille de sécurité sur ce serveur, merci de me le signaler. Ce homelab est public et j’apprends de mes erreurs.

📨 Signalement responsable : www.arleo.eu/security.txt

Les signalements sont remerciés publiquement dans le Hall of Fame, ou restent anonymes sur demande.

Toute contribution à l’amélioration de la sécurité est la bienvenue.

Migration en cours
Le site migre progressivement de Grav CMS vers Hugo. Les anciennes URLs sont préservées, mais le rendu visuel évolue. Si vous voyez un bug, signalez-le.

Audit NUC : ModSecurity remplacé par CrowdSec AppSec

En bref

Un audit de la stack sécurité du NUC révèle une double inspection WAF redondante : ModSecurity + OWASP CRS chargent 11 872 règles en mémoire malgré SecRuleEngine Off, en parallèle d’un CrowdSec AppSec déjà actif et couvrant le même périmètre. Après suppression du module nginx ModSecurity et 5 autres correctifs ciblés, nginx passe de ~520 MB à ~27 MB PSS. Sécurité identique, empreinte divisée par 20.


🏗️ Architecture avant audit

La stack sécurité reposait sur six couches empilées :

hugo-mcp v2.0 : un plugin-system Python en 200 lignes

En bref

hugo-mcp v2.0.0 introduit un plugin-system Python qui permet à n’importe qui d’ajouter des hooks après chaque create_page / update_page / delete_page. 200 lignes de code pour le coeur, 3 plugins de production fournis (IndexNow, Google Indexing, Cloudflare). Ce post explique le design, les arbitrages, la sécurité, et montre comment écrire son propre plugin en 5 minutes.

Migration Grav → Hugo : deux ans de blog en un jour

TL;DR

Le 9 mai 2026, j’ai basculé arleo.eu de Grav (CMS PHP) vers Hugo (générateur de site statique Go) en une session. Bascule atomique (≈ 0 seconde de downtime), 22 articles legacy migrés sous /posts/ avec aliases SEO pour préserver les URLs Google indexées, monitoring BetterStack /ping intact pendant toute l’opération.

Le code et le script de migration sont open source : github.com/jmrGrav/grav-to-hugo-migration.

Sprint sécurité MCP : v1.9.0 et 10 chantiers de hardening

En bref

Le 9 mai 2026, j’ai livré en une session marathon les 10 chantiers du sprint sécurité MCP que j’avais annoncé en début de journée. hugo-mcp est désormais en v1.9.0 (GitHub Release), commit 1404f83 GPG-signé.

Voici le recap haute-volée + un zoom pédagogique sur 2 chantiers qui ont une vraie valeur en dehors de mon contexte précis : C2 token rotation et C6 TLS interne.

Recap des 10 chantiers

#ChantierImplémentation
C1Rate limitingslowapi, 60 req/min par IP
C2Token rotationtokens.json + token_mgr.py CLI
C3Audit logs JSONstructlog, événements machine-readable
C4Pydantic v2 stricteCreatePageArgs / UpdatePageArgs avec contraintes
C5bcrypt cost-12Tokens hashés en stockage
C6TLS NUC ↔ VMCert EC P-256, uvicorn SSL, proxy vérifie le cert
C7requirements.lockSHA-256 hashes via pip-compile --generate-hashes
C8Info disclosureDocs off, exception handler générique, proxy_hide_header
C9nginx WAFEnforcement POST + application/json sur /mcp, OWASP CRS actif
C10Backup DRbackup.sh GPG-chiffré, rétention 30 jours

Détails complets dans le CHANGELOG v1.9.0 et le commit 1404f83.

Post-mortem : Cloudflare bloquait les webhooks MCP

Ce qui s'est passé
  • Date : 9 mai 2026
  • Sévérité : P3
  • Statut : Résolu
  • Impact : Webhook GitHub bloqué en 403 par Cloudflare Bot Management (UA python-httpx) — fix via une règle Custom Rule à triple condition (host + path + IP)

En bref

Un endpoint webhook fraîchement déployé retournait systématiquement 403, sans jamais atteindre nginx ni le service applicatif — la cause était Cloudflare Bot Management, qui traite par défaut le trafic automatisé légitime comme suspect.

Le symptôme

Je viens de finaliser un endpoint webhook dans le hugo-mcp-proxy qui recevra des notifications de GitHub à chaque push sur le repo arleo.eu. Implementation propre : HMAC-SHA256, rate limiting, IPAddressAllow GitHub ranges côté systemd.

Test fonctionnel depuis un client externe :

$ curl -X POST https://mcp-hugo.arleo.eu/webhook/test \
    -H "Content-Type: application/json" \
    -d '{"test": true}'

Réponse : 403 Forbidden.

Curieux. Le service tourne, l’IP source de mon test est dans le whitelist, le HMAC est correct. Pourquoi 403 ?

Investigation côté serveur

Logs nginx côté NUC :

$ sudo tail -100 /var/log/nginx/mcp-hugo.access.log | grep webhook

Vide. Aucune requête n’arrive sur nginx.

Logs mcp-oauth-proxy :

$ sudo journalctl -u mcp-oauth-proxy -n 100 | grep webhook

Vide aussi. La requête n’arrive pas jusqu’au service.

Soit elle est bloquée par firewall avant nginx (CrowdSec ou ufw), soit en amont par Cloudflare.

La vérité côté Cloudflare

Post-mortem : mcp-installer régénérait les tokens

Ce qui s'est passé
  • Date : 9 mai 2026
  • Sévérité : P2
  • Statut : Résolu (v1.2.0)
  • Impact : mcp-installer régénérait tous les secrets OAuth à chaque relance, cassant les clients déjà enregistrés — fix via preflight check + flag explicite --force-rotate-tokens

En bref

Relancer le script d’installation mcp-installer sur un host déjà provisionné écrasait silencieusement tous les secrets OAuth en service, invalidant les clients déjà enregistrés comme Claude.ai.

Le bug

mcp-installer est un script bash que j’ai écrit pour automatiser l’installation du mcp-oauth-proxy (FastAPI + nginx + systemd) sur un nouveau host. Workflow standard : clone, run, c’est tout.

Sauf qu’en relançant le script sur un host déjà installé (par exemple pour mettre à jour la version), j’ai découvert un bug d’idempotence : tous les secrets étaient régénérés.

$ sudo ./install.sh
[+] Generating MCP_TOKEN...
[+] Generating CLIENT_ID...
[+] Generating CLIENT_SECRET...
[+] Generating TOKEN_SECRET...
[+] Writing /etc/mcp-oauth-proxy/.env...

Si tu as déjà un .env avec des tokens en service, l’installer les écrase. Tous les clients OAuth déjà enregistrés (Claude.ai dans mon cas) se retrouvent avec des credentials invalides. La connexion casse.

Pourquoi c’est arrivé

Le script utilisait cette logique :

MCP_TOKEN=$(openssl rand -hex 32)
CLIENT_ID=$(openssl rand -hex 16)
CLIENT_SECRET=$(openssl rand -hex 32)
TOKEN_SECRET=$(openssl rand -hex 32)

cat > /etc/mcp-oauth-proxy/.env <<EOF
MCP_TOKEN=$MCP_TOKEN
CLIENT_ID=$CLIENT_ID
CLIENT_SECRET=$CLIENT_SECRET
TOKEN_SECRET=$TOKEN_SECRET
EOF

Le pattern « génère puis écris » est correct pour une première install. Mais en relance, il ignore complètement le .env existant.

C’est l’erreur classique : l’auteur (moi) a testé le script uniquement sur un host fresh. Le mode “réinstall sur host existant” n’a jamais été testé.

Reproduction du bug

$ sudo ./install.sh
[OK] Installation complete
$ cat /etc/mcp-oauth-proxy/.env | head -1
MCP_TOKEN=<redacted-mcp-token-1>

$ sudo ./install.sh
[OK] Installation complete
$ cat /etc/mcp-oauth-proxy/.env | head -1
MCP_TOKEN=<redacted-mcp-token-2>   ← DIFFÉRENT

Test reproductible en 30 secondes. C’est presque comique que je ne l’aie pas vu plus tôt.

La leçon : “idempotent” est un contrat, pas un espoir

Un script d’installation doit être idempotent par défaut. C’est-à-dire : ./install.sh 1 fois, 2 fois, 5 fois → le système est dans le même état stable.

J’avais ce contrat dans ma tête. Je ne l’avais pas dans le code.

Le fix : pre-flight checks + force flag explicite

Hugo