<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title>Security - Balise - arleo.eu</title>
        <link>https://www.arleo.eu/tags/security/</link>
        <description>Security - Balise - arleo.eu</description>
        <generator>Hugo -- gohugo.io</generator><language>fr</language><lastBuildDate>Thu, 28 May 2026 23:40:12 &#43;0200</lastBuildDate><atom:link href="https://www.arleo.eu/tags/security/" rel="self" type="application/rss+xml" /><item>
    <title>CSP A&#43; sur Hugo &#43; Cloudflare : de hash-based à origin allowlist, monitoring auto et durcissement</title>
    <link>https://www.arleo.eu/posts/csp-a-plus-hugo-cloudflare-origin-allowlist/</link>
    <pubDate>Thu, 28 May 2026 23:40:12 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/csp-a-plus-hugo-cloudflare-origin-allowlist/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/csp-a-plus-hugo-cloudflare-origin-allowlist-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="contexte">Contexte</h2>
<p>arleo.eu tourne sur Hugo (VM KVM) → OpenResty (NUC) → Cloudflare (CDN/WAF). Objectif : score <strong>A+ sur Mozilla Observatory</strong> avec une CSP stricte qui résiste aux injections côté edge.</p>
<p>Le parcours a traversé trois stratégies en quelques semaines — nonces, hashes, puis origin allowlist — avant d&rsquo;aboutir à une solution stable et automatisée.</p>
<hr>
<h2 id="acte-1--pourquoi-hash-based-a-échoué">Acte 1 : pourquoi hash-based a échoué</h2>
<p>L&rsquo;idée de départ semblait solide : Hugo Pipes externalise tout le JS avec SRI, on liste les hashes dans la CSP, résultat propre. En pratique, deux problèmes ont rendu l&rsquo;approche impossible.</p>]]></description>
</item>
<item>
    <title>CrowdSec AppSec &#43; OpenResty : WAF moderne sans ModSecurity</title>
    <link>https://www.arleo.eu/posts/crowdsec-appsec-openresty/</link>
    <pubDate>Mon, 18 May 2026 00:07:11 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/crowdsec-appsec-openresty/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/crowdsec-appsec-openresty-featured.jpg" referrerpolicy="no-referrer">
            </div><p>Après des années avec ModSecurity + OWASP CRS sur nginx, j&rsquo;ai migré arleo.eu vers une stack plus moderne : <strong>CrowdSec AppSec sur OpenResty</strong>. Le résultat est une architecture WAF inline mieux intégrée, plus facile à maintenir, et plus cohérente avec le reste de la stack de sécurité.</p>
<h2 id="pourquoi-abandonner-modsecurity-">Pourquoi abandonner ModSecurity ?</h2>
<p>ModSecurity v2 est en fin de vie active. Le maintien des règles OWASP CRS sur nginx classique génère de la friction : faux positifs fréquents, logs difficiles à corréler avec CrowdSec, et une configuration dispersée entre plusieurs outils sans vision unifiée.</p>]]></description>
</item>
<item>
    <title>SRI sur Hugo : hashes automatiques, auto-update et alerting BetterStack</title>
    <link>https://www.arleo.eu/posts/sri-cdn-hugo-automate/</link>
    <pubDate>Sun, 17 May 2026 00:00:00 &#43;0000</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/sri-cdn-hugo-automate/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/sri-cdn-hugo-automate-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="pourquoi-le-sri-">Pourquoi le SRI ?</h2>
<p>Quand votre site charge des ressources depuis un CDN tiers — FontAwesome, Mermaid, Animate.css — vous faites confiance à un tiers sur lequel vous n&rsquo;avez aucun contrôle. Si jsdelivr.net est compromis ou si une version « immuable » est mutée silencieusement, votre site peut devenir vecteur d&rsquo;attaque.</p>
<p>Le <strong>Subresource Integrity</strong> (SRI) résout ça simplement : chaque balise <code>&lt;link&gt;</code> ou <code>&lt;script&gt;</code> porte un attribut <code>integrity=&quot;sha256-…&quot;</code> que le navigateur vérifie avant d&rsquo;exécuter la ressource. Si le hash ne correspond pas, le navigateur bloque le chargement.</p>]]></description>
</item>
<item>
    <title>CSP Hash sur Hugo : migrer du nonce vers le hash pour préserver le cache CDN</title>
    <link>https://www.arleo.eu/posts/csp-hash-hugo/</link>
    <pubDate>Sat, 16 May 2026 00:00:00 &#43;0000</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/csp-hash-hugo/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/csp-hash-hugo-featured.png" referrerpolicy="no-referrer">
            </div>Le nonce CSP et le cache CDN sont incompatibles par design. Sur un site Hugo statique, le hash SHA-256 est l&rsquo;approche native : il est calculé au build, reste stable entre les requêtes, et laisse Cloudflare cacher le HTML.]]></description>
</item>
<item>
    <title>Audit sécurité NUC : ModSecurity supprimé, 500 MB récupérés</title>
    <link>https://www.arleo.eu/posts/audit-securite-modsecurity-crowdsec/</link>
    <pubDate>Thu, 14 May 2026 05:31:23 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/audit-securite-modsecurity-crowdsec/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/audit-securite-modsecurity-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-en-bref">⚡ En bref</h2>
<p>Un audit de la stack sécurité du NUC révèle une <strong>double inspection WAF redondante</strong> : ModSecurity + OWASP CRS chargent 11 872 règles en mémoire malgré <code>SecRuleEngine Off</code>, en parallèle d&rsquo;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 <strong>~520 MB à ~27 MB PSS</strong>. Sécurité identique, empreinte divisée par 20.</p>
<hr>
<h2 id="-architecture-avant-audit">🏗️ Architecture avant audit</h2>
<p>La stack sécurité reposait sur six couches empilées :</p>]]></description>
</item>
<item>
    <title>Sprint sécurité MCP livré : v1.9.0, 10 chantiers, écosystème durci</title>
    <link>https://www.arleo.eu/posts/sprint-securite-mcp-livre/</link>
    <pubDate>Sat, 09 May 2026 18:42:54 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/sprint-securite-mcp-livre/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/sprint-securite-mcp-livre-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p>Le 9 mai 2026, j&rsquo;ai livré en une session marathon les 10 chantiers du sprint sécurité MCP que <a href="/posts/roadmap-sprint-securite-mcp/" rel="">j&rsquo;avais annoncé en début de journée</a>. <code>hugo-mcp</code> est désormais en <strong>v1.9.0</strong> (<a href="https://github.com/jmrGrav/hugo-mcp/releases/tag/v1.9.0" target="_blank" rel="noopener noreffer ">GitHub Release</a>), commit <code>1404f83</code> GPG-signé.</p>
<p>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 : <strong>C2 token rotation</strong> et <strong>C6 TLS interne</strong>.</p>
<h2 id="recap-des-10-chantiers">Recap des 10 chantiers</h2>
<table>
  <thead>
      <tr>
          <th>#</th>
          <th>Chantier</th>
          <th>Implémentation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>C1</td>
          <td>Rate limiting</td>
          <td><code>slowapi</code>, 60 req/min par IP</td>
      </tr>
      <tr>
          <td>C2</td>
          <td>Token rotation</td>
          <td><code>tokens.json</code> + <code>token_mgr.py</code> CLI</td>
      </tr>
      <tr>
          <td>C3</td>
          <td>Audit logs JSON</td>
          <td><code>structlog</code>, événements machine-readable</td>
      </tr>
      <tr>
          <td>C4</td>
          <td>Pydantic v2 stricte</td>
          <td><code>CreatePageArgs</code> / <code>UpdatePageArgs</code> avec contraintes</td>
      </tr>
      <tr>
          <td>C5</td>
          <td>bcrypt cost-12</td>
          <td>Tokens hashés en stockage</td>
      </tr>
      <tr>
          <td>C6</td>
          <td>TLS NUC ↔ VM</td>
          <td>Cert EC P-256, uvicorn SSL, proxy vérifie le cert</td>
      </tr>
      <tr>
          <td>C7</td>
          <td>requirements.lock</td>
          <td>SHA-256 hashes via <code>pip-compile --generate-hashes</code></td>
      </tr>
      <tr>
          <td>C8</td>
          <td>Info disclosure</td>
          <td>Docs off, exception handler générique, <code>proxy_hide_header</code></td>
      </tr>
      <tr>
          <td>C9</td>
          <td>nginx WAF</td>
          <td>Enforcement POST + <code>application/json</code> sur <code>/mcp</code>, OWASP CRS actif</td>
      </tr>
      <tr>
          <td>C10</td>
          <td>Backup DR</td>
          <td><code>backup.sh</code> GPG-chiffré, rétention 30 jours</td>
      </tr>
  </tbody>
</table>
<p>Détails complets dans le <a href="https://github.com/jmrGrav/hugo-mcp/blob/main/CHANGELOG.md" target="_blank" rel="noopener noreffer ">CHANGELOG v1.9.0</a> et le commit <a href="https://github.com/jmrGrav/hugo-mcp/commit/1404f83" target="_blank" rel="noopener noreffer ">1404f83</a>.</p>]]></description>
</item>
<item>
    <title>Roadmap : Sprint sécurité MCP — 4 domaines, 10 chantiers (LIVRÉ ✅)</title>
    <link>https://www.arleo.eu/posts/roadmap-sprint-securite-mcp/</link>
    <pubDate>Sat, 09 May 2026 12:47:59 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/roadmap-sprint-securite-mcp/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/roadmap-sprint-securite-mcp-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="statut---livré--sprint-clos-le-9-mai-2026">Statut : ✅ LIVRÉ — sprint clos le 9 mai 2026</h2>
<p><strong>Mise à jour 9 mai 2026 (fin de session)</strong> : les 10 chantiers sont livrés en une session marathon, et <strong>les 3 releases coordonnées sont publiées</strong> (<code>hugo-mcp</code> v1.9.0, <code>mcp-oauth-proxy</code> v2.1.0, <code>mcp-installer</code> v1.3.0). Tout l&rsquo;écosystème MCP est durci dans la même journée. Recap technique complet : <a href="/posts/sprint-securite-mcp-livre/" rel="">Sprint sécurité MCP livré : v1.9.0, 10 chantiers, écosystème durci</a>.</p>
<p>Cette page reste publiée à fins d&rsquo;archive — pour montrer la trajectoire d&rsquo;un sprint annoncé puis tenu. Tout le contenu original ci-dessous est préservé.</p>
<hr>
<h2 id="statut-original-avant-livraison">Statut original (avant livraison)</h2>
<p>Cette page documentait publiquement un sprint de hardening en cours sur l&rsquo;infrastructure MCP d&rsquo;arleo.eu. Pour des raisons de sécurité, les détails précis des chantiers n&rsquo;étaient pas exposés tant que les correctifs n&rsquo;étaient pas livrés (philosophie &ldquo;sec-first&rdquo; : ne pas publier de roadmap d&rsquo;attaque).</p>
<h2 id="pourquoi-cette-transparence">Pourquoi cette transparence</h2>
<p>J&rsquo;ai débattu de publier ou non cette page. Arguments pour la transparence :</p>
<ul>
<li><strong>Engagement public</strong> = pression saine sur soi-même pour livrer</li>
<li><strong>Documentation</strong> d&rsquo;un homelab dont l&rsquo;objectif est apprendre et partager</li>
<li><strong>Honnêteté</strong> vis-à-vis des lecteurs qui consomment les autres articles techniques</li>
</ul>
<p>Arguments contre :</p>
<ul>
<li><strong>Roadmap d&rsquo;attaque</strong> : si je liste précisément ce qui n&rsquo;est pas encore protégé, je donne des indices à un attaquant patient</li>
<li><strong>Pression artificielle</strong> : annoncer un sprint puis ne pas livrer = pire que ne rien annoncer</li>
</ul>
<p>Compromis adopté : publier la <strong>direction</strong> et les <strong>domaines de hardening</strong> sans préciser ce qui est faible aujourd&rsquo;hui. Détail technique précis publié à la livraison.</p>
<h2 id="domaines-couverts-par-le-sprint">Domaines couverts par le sprint</h2>
<p>Le sprint couvrait 10 chantiers regroupés en 4 domaines :</p>
<h3 id="1-hardening-applicatif-fastapi--pydantic">1. Hardening applicatif (FastAPI + Pydantic)</h3>
<p>Renforcement des couches d&rsquo;entrée du MCP : validation stricte des inputs, gestion d&rsquo;erreurs unifiée, pas de leak d&rsquo;information dans les responses (stack traces, paths internes, versions de libs).</p>
<h3 id="2-authentification-et-tokens">2. Authentification et tokens</h3>
<p>Refonte de la gestion des tokens d&rsquo;accès au MCP : durée de vie, rotation, révocation, hashing en stockage. Permettre de couper l&rsquo;accès d&rsquo;un client compromis sans redéployer le service.</p>
<h3 id="3-observabilité-et-audit">3. Observabilité et audit</h3>
<p>Intégration de logs structurés JSON ingérés dans BetterStack via Vector. Chaque appel MCP doit produire un événement traçable : qui, quoi, quand, durée, statut. Permet la détection d&rsquo;anomalies en quasi-temps réel.</p>
<h3 id="4-infrastructure-et-résilience">4. Infrastructure et résilience</h3>
<p>TLS pour le trafic interne entre le NUC et la VM, règles ModSec dédiées au path <code>/mcp</code>, runbook de récupération en cas de désastre (vol de tokens, compromise serveur, perte de données).</p>
<h2 id="ce-qui-était-déjà-en-place-avant-le-sprint">Ce qui était déjà en place avant le sprint</h2>
<p>Pour ne pas créer de fausse impression, voici les couches <strong>déjà en place</strong> sur l&rsquo;infrastructure avant le sprint (donc hors scope) :</p>
<ul>
<li>nginx + TLS 1.3 obligatoire (Mozilla Modern config)</li>
<li>Cloudflare WAF + Bot Management + IP whitelist</li>
<li>ModSecurity + OWASP CRS 4.x sur tous les vhosts</li>
<li>CrowdSec en mode WAF + bouncer Cloudflare</li>
<li>systemd hardening niveau 1.7 (cf. <a href="/posts/hardening-systemd-mcp/" rel="">hardening systemd</a>)</li>
<li>HMAC validation sur les webhooks</li>
<li>Frontmatter validation Pydantic (1 chantier sur 4 dans la catégorie validation)</li>
</ul>
<p>Le sprint visait à <strong>compléter</strong> cette base, pas à la remplacer.</p>
<h2 id="méthode">Méthode</h2>
<p>Pour chaque chantier :</p>
<ol>
<li>Implémenté en local + tests unitaires</li>
<li>Validé sur <code>mcp-test-vm</code> (VM de pré-prod)</li>
<li>Déployé sur prod uniquement après tests réussis</li>
<li>Audit log structuré JSON pour traçabilité du déploiement</li>
<li>Article post-mortem ou write-up technique publié <strong>après</strong> livraison</li>
</ol>
<p>Aucun déploiement direct prod sans étape pré-prod.</p>
<h2 id="releases-publiées">Releases publiées</h2>]]></description>
</item>
<item>
    <title>Hardening systemd : passer un service Python de 9.6 à 1.7</title>
    <link>https://www.arleo.eu/posts/hardening-systemd-mcp/</link>
    <pubDate>Sat, 09 May 2026 12:40:00 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/hardening-systemd-mcp/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/hardening-systemd-mcp-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p><code>systemd-analyze security</code> est un outil sous-utilisé. Il scanne tes unit files et calcule un score d&rsquo;exposition de <strong>0.0 (UNSAFE)</strong> à <strong>10.0 (PERFECT)</strong>. Les services Python custom sortent souvent autour de <strong>9.6</strong> par défaut — c&rsquo;est mauvais.</p>
<p>J&rsquo;ai fait passer mon service <code>hugo-mcp</code> (FastAPI exposant 7 tools MCP) de <strong>9.6 → 1.7</strong> sans casser la moindre fonctionnalité. Voici les directives qui comptent vraiment, et celles qui sont des pièges.</p>
<h2 id="le-score-initial">Le score initial</h2>
<div class="code-block code-line-numbers open" data-start="0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ sudo systemd-analyze security hugo-mcp
</span></span><span class="line"><span class="cl">→ Overall exposure level <span class="k">for</span> hugo-mcp.service: 9.6 UNSAFE 😨</span></span></code></pre></div></div>
<p>9.6 sur 10. Le service tourne en root équivalent, peut faire <code>setuid()</code>, peut allouer de la mémoire <code>RWX</code>, voit tous les processus du système, peut écrire partout, peut faire des syscalls arbitraires.</p>
<p>Pour un MCP qui édite du contenu Markdown et déclenche un build Hugo, c&rsquo;est démesuré.</p>]]></description>
</item>
<item>
    <title>De 46 hashes à zéro : migration CSP vers les nonces dynamiques</title>
    <link>https://www.arleo.eu/posts/csp-nonce/</link>
    <pubDate>Wed, 15 Apr 2026 13:22:00 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/csp-nonce/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/csp-nonce-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-en-bref">⚡ En bref</h2>
<p>La CSP initiale listait 46 hashes SHA-256 pour couvrir les scripts et styles inline — ingérable, fragile, et à la limite des 4 096 caractères de Cloudflare. Cette migration vers des <strong>nonces dynamiques</strong> réduit la CSP à ~600 caractères, supprime toute maintenance manuelle des hashes, et ajoute un reporting structuré des violations dans BetterStack.</p>
<p>Le plugin Grav utilisé est disponible sur GitHub :
🔌 Plugin : <a href="https://github.com/jmrGrav/csp-nonce" target="_blank" rel="noopener noreffer ">jmrGrav/grav-plugin-csp-nonce</a></p>]]></description>
</item>
</channel>
</rss>
