<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Hardening - Tag - arleo.eu</title><link>https://www.arleo.eu/en/tags/hardening/</link><description>Hardening - Tag - arleo.eu</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Thu, 28 May 2026 23:45:41 +0200</lastBuildDate><atom:link href="https://www.arleo.eu/en/tags/hardening/" rel="self" type="application/rss+xml"/><item><title>CSP A+ on Hugo + Cloudflare: from hash-based to origin allowlist, auto-monitoring and hardening</title><link>https://www.arleo.eu/en/posts/csp-a-plus-hugo-cloudflare-origin-allowlist/</link><pubDate>Thu, 28 May 2026 23:45:41 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/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="context">Context</h2>
<p>arleo.eu runs on Hugo (KVM VM) → OpenResty (NUC) → Cloudflare (CDN/WAF). Goal: <strong>A+ score on Mozilla Observatory</strong> with a strict CSP that resists edge-side injections.</p>
<p>The journey went through three strategies over a few weeks — nonces, hashes, then origin allowlist — before landing on a stable, automated solution.</p>
<hr>
<h2 id="act-1-why-hash-based-failed">Act 1: why hash-based failed</h2>
<p>The initial idea seemed solid: Hugo Pipes externalizes all JS with SRI, we list the hashes in the CSP, clean result. In practice, two problems made the approach impossible.</p>]]></description></item><item><title>NUC Security Audit: ModSecurity Removed, 500 MB Recovered</title><link>https://www.arleo.eu/en/posts/audit-securite-modsecurity-crowdsec/</link><pubDate>Thu, 14 May 2026 05:32:19 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/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="-tldr">⚡ TL;DR</h2>
<p>A security stack audit on the homelab NUC reveals <strong>redundant double WAF inspection</strong>: ModSecurity + OWASP CRS load 11,872 rules into memory despite <code>SecRuleEngine Off</code>, running in parallel with CrowdSec AppSec which already covers the same surface. After removing the ModSecurity nginx module and five other targeted fixes, nginx drops from <strong>~520 MB to ~27 MB PSS</strong>. Same security, memory footprint divided by 20.</p>
<hr>
<h2 id="-architecture-before-the-audit">🏗️ Architecture Before the Audit</h2>
<p>The security stack had six stacked layers:</p>]]></description></item><item><title>MCP security sprint delivered: v1.9.0, 10 chantiers, hardened ecosystem</title><link>https://www.arleo.eu/en/posts/sprint-securite-mcp-livre/</link><pubDate>Sat, 09 May 2026 18:44:12 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/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>On May 9, 2026, I delivered all 10 chantiers of the MCP security sprint that <a href="/en/posts/roadmap-sprint-securite-mcp/" rel="">I had announced earlier in the day</a> in a single marathon session. <code>hugo-mcp</code> is now at <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-signed.</p>
<p>Here&rsquo;s the high-level recap + a pedagogical deep-dive on 2 chantiers with real value beyond my specific context: <strong>C2 token rotation</strong> and <strong>C6 internal TLS</strong>.</p>
<h2 id="recap-of-10-chantiers">Recap of 10 chantiers</h2>
<table>
  <thead>
      <tr>
          <th>#</th>
          <th>Chantier</th>
          <th>Implementation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>C1</td>
          <td>Rate limiting</td>
          <td><code>slowapi</code>, 60 req/min per 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>JSON audit logs</td>
          <td><code>structlog</code>, machine-readable events</td>
      </tr>
      <tr>
          <td>C4</td>
          <td>Strict Pydantic v2</td>
          <td><code>CreatePageArgs</code> / <code>UpdatePageArgs</code> with constraints</td>
      </tr>
      <tr>
          <td>C5</td>
          <td>bcrypt cost-12</td>
          <td>Tokens hashed in storage</td>
      </tr>
      <tr>
          <td>C6</td>
          <td>NUC ↔ VM TLS</td>
          <td>EC P-256 cert, uvicorn SSL, proxy verifies the 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, generic exception handler, <code>proxy_hide_header</code></td>
      </tr>
      <tr>
          <td>C9</td>
          <td>nginx WAF</td>
          <td>POST + <code>application/json</code> enforcement on <code>/mcp</code>, OWASP CRS active</td>
      </tr>
      <tr>
          <td>C10</td>
          <td>Backup DR</td>
          <td><code>backup.sh</code> GPG-encrypted, 30-day retention</td>
      </tr>
  </tbody>
</table>
<p>Full details in the <a href="https://github.com/jmrGrav/hugo-mcp/blob/main/CHANGELOG.md" target="_blank" rel="noopener noreffer ">CHANGELOG v1.9.0</a> and commit <a href="https://github.com/jmrGrav/hugo-mcp/commit/1404f83" target="_blank" rel="noopener noreffer ">1404f83</a>.</p>]]></description></item><item><title>Roadmap: MCP Security Sprint — 4 domains, 10 chantiers (DELIVERED ✅)</title><link>https://www.arleo.eu/en/posts/roadmap-sprint-securite-mcp/</link><pubDate>Sat, 09 May 2026 13:07:33 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/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="status--delivered--sprint-closed-on-may-9-2026">Status: ✅ DELIVERED — sprint closed on May 9, 2026</h2>
<p><strong>Update May 9, 2026 (end of session)</strong>: all 10 chantiers delivered in a single marathon session, <strong>and all 3 coordinated releases published the same day</strong> (<code>hugo-mcp</code> v1.9.0, <code>mcp-oauth-proxy</code> v2.1.0, <code>mcp-installer</code> v1.3.0). Every component of the MCP ecosystem hardened in one day. Full technical recap: <a href="/en/posts/sprint-securite-mcp-livre/" rel="">MCP security sprint delivered: v1.9.0, 10 chantiers, hardened ecosystem</a>.</p>
<p>This page stays published as an archive — to show the trajectory of an announced sprint, then kept. All original content below is preserved.</p>
<hr>
<h2 id="original-status-pre-delivery">Original status (pre-delivery)</h2>
<p>This page publicly documented an ongoing hardening sprint on arleo.eu&rsquo;s MCP infrastructure. For security reasons, specific details of each chantier were not exposed until fixes were delivered (&ldquo;sec-first&rdquo; philosophy: don&rsquo;t publish an attack roadmap).</p>
<h2 id="why-this-transparency">Why this transparency</h2>
<p>I debated whether to publish this page. Arguments for transparency:</p>
<ul>
<li><strong>Public commitment</strong> = healthy pressure on yourself to deliver</li>
<li><strong>Documentation</strong> of a homelab whose goal is to learn and share</li>
<li><strong>Honesty</strong> with readers consuming other technical articles</li>
</ul>
<p>Arguments against:</p>
<ul>
<li><strong>Attack roadmap</strong>: if I precisely list what&rsquo;s not yet protected, I give clues to a patient attacker</li>
<li><strong>Artificial pressure</strong>: announcing a sprint then not delivering = worse than announcing nothing</li>
</ul>
<p>Adopted compromise: publish the <strong>direction</strong> and <strong>hardening domains</strong> without specifying what&rsquo;s weak today. Specific technical detail published on delivery.</p>
<h2 id="sprint-scope">Sprint scope</h2>
<p>The sprint covered 10 chantiers grouped into 4 domains:</p>
<h3 id="1-application-hardening-fastapi--pydantic">1. Application hardening (FastAPI + Pydantic)</h3>
<p>Reinforcement of MCP entry layers: strict input validation, unified error handling, no information leak in responses (stack traces, internal paths, lib versions).</p>
<h3 id="2-authentication-and-tokens">2. Authentication and tokens</h3>
<p>Refactor of MCP access token management: lifetime, rotation, revocation, hashing in storage. Allow cutting off a compromised client&rsquo;s access without redeploying the service.</p>
<h3 id="3-observability-and-audit">3. Observability and audit</h3>
<p>Integration of JSON structured logs ingested in BetterStack via Vector. Each MCP call must produce a traceable event: who, what, when, duration, status. Enables anomaly detection in near real-time.</p>
<h3 id="4-infrastructure-and-resilience">4. Infrastructure and resilience</h3>
<p>TLS for internal traffic between NUC and VM, dedicated ModSec rules on the <code>/mcp</code> path, disaster recovery runbook (token theft, server compromise, data loss).</p>
<h2 id="what-was-already-in-place-before-the-sprint">What was already in place before the sprint</h2>
<p>To not create false impressions, here are the layers <strong>already in place</strong> on infrastructure before the sprint (so out of scope):</p>
<ul>
<li>nginx + mandatory TLS 1.3 (Mozilla Modern config)</li>
<li>Cloudflare WAF + Bot Management + IP whitelist</li>
<li>ModSecurity + OWASP CRS 4.x on all vhosts</li>
<li>CrowdSec in WAF mode + Cloudflare bouncer</li>
<li>systemd hardening at level 1.7 (cf. <a href="/en/posts/hardening-systemd-mcp/" rel="">systemd hardening</a>)</li>
<li>HMAC validation on webhooks</li>
<li>Frontmatter Pydantic validation (1 chantier of 4 in the validation category)</li>
</ul>
<p>The sprint aimed to <strong>complete</strong> this base, not replace it.</p>
<h2 id="method">Method</h2>
<p>For each chantier:</p>
<ol>
<li>Implemented locally + unit tests</li>
<li>Validated on <code>mcp-test-vm</code> (pre-prod VM)</li>
<li>Deployed to prod only after passing tests</li>
<li>Structured JSON audit log for deployment traceability</li>
<li>Post-mortem or technical write-up published <strong>after</strong> delivery</li>
</ol>
<p>No direct prod deployment without pre-prod step.</p>
<h2 id="published-releases">Published releases</h2>]]></description></item><item><title>systemd hardening: taking a Python service from 9.6 to 1.7</title><link>https://www.arleo.eu/en/posts/hardening-systemd-mcp/</link><pubDate>Sat, 09 May 2026 13:02:29 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/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> is an underused tool. It scans your unit files and computes an exposure score from <strong>0.0 (UNSAFE)</strong> to <strong>10.0 (PERFECT)</strong>. Custom Python services often score around <strong>9.6</strong> by default — that&rsquo;s bad.</p>
<p>I took my <code>hugo-mcp</code> service (FastAPI exposing 7 MCP tools) from <strong>9.6 → 1.7</strong> without breaking a single feature. Here are the directives that actually matter, and the ones that are traps.</p>
<h2 id="initial-score">Initial score</h2>]]></description></item></channel></rss>