/images/avatar.png

Break things. Fix them. Learn.

This site runs on an Intel NUC hosted at home, behind a standard fiber connection. Its main purpose is to serve as an experimentation ground for testing server configurations, automation scripts, and open source security tools.

Not a professional website — a homelab: we break things, fix them, and learn.

Migration in progress
The site is gradually migrating from Grav CMS to Hugo. Old URLs are preserved, but the visual rendering is evolving. If you spot a bug, report it.

🛠️ Tech stack

ToolRoleLink
🔒 CrowdSecCommunity IDS/IPSDashboard
☁️ CloudflareCDN · WAF · DNS · DDoSDashboard
📊 BetterStackMonitoring · Alerts · LogsStatus page
🌐 HugoStatic site generatorgohugo.io
🛡️ ModSecurityLocal WAF · OWASP CRS 4.xOWASP CRS
nginxReverse proxy · TLS 1.3nginx.org

🌟 Don’t miss

Three articles that capture the spirit of this homelab:

📚 Full documentation is in Documentation and automation scripts in Scripts.

🐛 Found a vulnerability?

If you discover a bug, misconfiguration, or security vulnerability on this server, please report it. This homelab is public and I learn from my mistakes.

📨 Responsible disclosure: www.arleo.eu/security.txt

Any contribution to improving security is welcome.

Hugo SEO: Googlebot 404s, noindex aliases and sitemap normalization

Context

Google Search Console was reporting four categories of issues on arleo.eu:

  • Googlebot 404s: /fr/tag/cloudflare, /en/tag/nginx, /fr/tag/javascript… URLs with /fr/ prefix or singular /tag/ never served by nginx
  • 16 “Excluded by noindex tag” pages: all redirect pages generated by aliases: in Hugo frontmatter
  • Robots tag: noodp hardcoded in the LoveIt theme
  • FR/EN sitemap: 104 vs 105 URLs — a duplicate FR tag and two missing tags

Act 1: Hugo aliases → nginx 301 redirects

Why Hugo generates noindex pages

Hugo generates aliases: frontmatter entries as static HTML files:

Hugo: freezing Mermaid diagrams to static dark/light SVGs

Problem

Mermaid diagrams on arleo.eu were rendering client-side via cdn.jsdelivr.net. Three concrete consequences:

  1. CSP constraintscript-src cdn.jsdelivr.net and worker-src cdn.jsdelivr.net become mandatory (Mermaid v11 uses Web Workers for its parsers).
  2. Render flash — the diagram appears after JS execution, creating a visible delay.
  3. Dark theme ignored — Mermaid initialized the SVG in light mode even when the site theme was dark.

The solution: generate SVGs at build time with mmdc, outside any browser context.

CSP A+ on Hugo + Cloudflare: from hash-based to origin allowlist, auto-monitoring and hardening

Context

arleo.eu runs on Hugo (KVM VM) → OpenResty (NUC) → Cloudflare (CDN/WAF). Goal: A+ score on Mozilla Observatory with a strict CSP that resists edge-side injections.

The journey went through three strategies over a few weeks — nonces, hashes, then origin allowlist — before landing on a stable, automated solution.


Act 1: why hash-based failed

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.

Postmortem — CrowdSec AppSec: Heuristic False Positive on Sonarr/Radarr

Summary

On May 25, 2026 at around 10:02 PM (local time), Sonarr and Radarr became completely inaccessible from the home IP (82.XX.XX.XX), returning 403 on every URL including /login. The service was fully operational. Initial suspicion fell on the day’s crowdsec-cf-sync refactor deployment — the real cause was a CrowdSec AppSec heuristic false positive.


Timeline

Time (local)Event
~10:00 PMSonarr browser session cookie expired
10:02:31 PMBrowser loads Sonarr library → attempts to fetch 20+ /MediaCover/*.jpg simultaneously
10:02:31 PMSonarr returns 302 → /login for each image (invalid session)
10:02:34 PMSignalR WebSocket connection succeeds (101) via access_token in URL
~10:05 PMCrowdSec AppSec triggers heuristic rule http-probing: burst of failed requests from same IP
10:11:37 PMAll requests from 82.XX.XX.XX return 403 — cs_reason=heuristic in nginx logs
10:13:34 PMEven /login is blocked — IP cannot authenticate

Root Cause

CrowdSec AppSec maintains an in-memory heuristic state, separate from LAPI decisions. When the browser simultaneously tries to load many resources and receives 302/403 from the upstream application (Sonarr), AppSec interprets the burst of failures as aggressive probing (http-probing) and blocks the source IP.

CrowdSec AppSec + OpenResty: Modern WAF Without ModSecurity

After years running ModSecurity + OWASP CRS on nginx, I migrated arleo.eu to a more modern stack: CrowdSec AppSec on OpenResty. The result is a tighter inline WAF architecture — better integrated, easier to maintain, and fully coherent with the rest of the security stack.

Why Drop ModSecurity?

ModSecurity v2 is in maintenance mode. Managing OWASP CRS rules on classic nginx generates friction: frequent false positives, logs that are hard to correlate with CrowdSec, and a configuration spread across multiple tools with no unified view.

SRI on Hugo: automated hashes, auto-update and BetterStack alerting

Why SRI?

When your site loads resources from a third-party CDN — FontAwesome, Mermaid, Animate.css — you’re trusting an external party you have no control over. If jsdelivr.net gets compromised, or if a supposedly immutable version is silently mutated, your site can become an attack vector.

Subresource Integrity (SRI) solves this cleanly: every <link> or <script> tag carries an integrity="sha256-…" attribute that the browser verifies before executing the resource. If the hash doesn’t match, the browser blocks the load.

Hugo