---
title: "Roadmap: Git webhook → automatic Hugo rebuild"
description: "Implementation plan for the GitHub → Hugo rebuild webhook. HMAC-SHA256, 1/min rate limiting, IPAddressAllow GitHub ranges. ETA June 2026 after the MCP security sprint."
url: "https://www.arleo.eu/en/posts/roadmap-webhook-git-rebuild/"
language: "en"
datePublished: "2026-05-09T13:06:50+02:00"
dateModified: "2026-05-15T23:51:25+02:00"
tags: ["roadmap","hugo","webhook","github","ci-cd","mcp"]
categories: ["roadmap"]
contentSignal: "ai-train=no, search=yes, ai-input=yes"
---

# Roadmap: Git webhook → automatic Hugo rebuild
Implementation plan for the GitHub → Hugo rebuild webhook. HMAC-SHA256, 1/min rate limiting, IPAddressAllow GitHub ranges. ETA June 2026 after the MCP security sprint.



## Status: 🗂️ BACKLOG — not yet implemented

This page documents an architectural intent to be implemented in a future iteration. The code is not yet in production.

## Context

In [Strategy 4 (separating MCP / Git)](/en/posts/strategie-4-mcp-vs-git/), I explained why `content/` is in `.gitignore` on the arleo.eu repo: so that no conflict is possible between MCP writes and Git writes.

Concretely, this means that when I push a new version of `layouts/`, `themes/`, `static/`, `hugo.toml`, or `deploy.sh` from VS Code, **nothing happens automatically** server-side. I have to SSH into the Hugo VM and manually run `git pull && hugo --minify && rsync`.

Not critical (structure pushes happen ~1× per week), but it's unnecessary friction. So: GitHub webhook → auto-rebuild.

## Target architecture

<!--more-->
<!-- mermaid-source:c2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBHSCBhcyBHaXRIdWIKICAgIHBhcnRpY2lwYW50IENGIGFzIENsb3VkZmxhcmUKICAgIHBhcnRpY2lwYW50IFAgYXMgbWNwLW9hdXRoLXByb3h5CiAgICBwYXJ0aWNpcGFudCBWTSBhcyBodWdvLW1jcCBWTQoKICAgIEdILT4+Q0Y6IFBPU1QgL3dlYmhvb2svZ2l0PGJyLz5YLUh1Yi1TaWduYXR1cmUtMjU2OiBzaGEyNTY94oCmCiAgICBDRi0+PkNGOiBJUCB3aGl0ZWxpc3QgMTQwLjgyLjExMi4wLzIwIOKckwogICAgQ0YtPj5QOiBGb3J3YXJkIChCb3QgTWdtdCBza2lwcGVkKQogICAgUC0+PlZNOiBQcm94eSAvd2ViaG9vay9naXQKICAgIFZNLT4+Vk06IDEuIEhNQUMtU0hBMjU2IOKckwogICAgVk0tPj5WTTogMi4gcmVmcy9oZWFkcy9tYWluIOKckwogICAgVk0tPj5WTTogMy4gUmF0ZSBsaW1pdCAxL21pbiDinJMKICAgIFZNLT4+Vk06IDQuIGdpdCByZXNldCAtLWhhcmQgKyBodWdvIC0tbWluaWZ5CiAgICBWTS0+PlZNOiA1LiBBdWRpdCBsb2cgSlNPTgogICAgVk0tLT4+R0g6IDIwMCBPSw== -->
{{< mermaid-svg hash="3007aa7f" >}}

```
GitHub push
    │
    │ POST https://mcp-hugo.arleo.eu/webhook/git
    │ Header: X-Hub-Signature-256: sha256=<hmac>
    │ Body: {"ref": "refs/heads/main", "head_commit": ...}
    │
    ▼
[Cloudflare]
    │ Custom rule: skip Bot Mgmt if IP in GitHub ranges
    │
    ▼
[mcp-oauth-proxy] (NUC, port 443)
    │ Routing /webhook/git → forward to MCP server VM
    │
    ▼
[hugo-mcp] (Hugo VM, port 8000)
    │ 1. Validate HMAC-SHA256
    │ 2. Validate refs/heads/main only
    │ 3. Rate limit: 1 rebuild / minute
    │ 4. Trigger: git fetch && git reset --hard origin/main && hugo --minify
    │ 5. JSON audit log: commit SHA, author, duration
    │
    ▼
[Site rebuilt] ~3 seconds
```

## Technical choices

### HMAC-SHA256 (not just IP whitelist)

GitHub publishes its ranges (`140.82.112.0/20` and others). We could just filter by IP. But HMAC is more robust:

- **IP spoofing** is theoretically possible (very rare, but)
- **HMAC signature** relies on a shared secret. If the attacker doesn't have it, they can't forge anything.

Concretely we do **both**: Cloudflare does IP filtering (eliminates 99% of noise), and HMAC validates cryptographically.

### Rate limiting: 1 / minute

Why? If an attacker manages to sign a payload (e.g. via `WEBHOOK_SECRET` leak), they could spam the server with rebuilds. Hugo build = ~3s + Cloudflare API call = ~400ms. At 100 rebuilds/minute, the server saturates and the Cloudflare API complains.

1 rebuild/minute amply covers the legitimate case (I don't push more than that) and limits the blast radius in case of compromise.

### `refs/heads/main` validation only

If someone pushes a feature branch, we do NOT rebuild. Only `main` triggers deployment. Avoids deploying work-in-progress.

### JSON audit log

Each rebuild trigger logs:

```json
{
  "ts": "2026-05-09T14:23:11Z",
  "event": "webhook_rebuild_triggered",
  "commit_sha": "a1b2c3d4...",
  "commit_author": "jmrGrav <276982731+jmrGrav@users.noreply.github.com>",
  "commit_message": "fix: typo in footer",
  "build_duration_ms": 2853,
  "deploy_success": true,
  "cf_purge_success": true
}
```

Ingested by Vector → BetterStack for timeline + alerting.

## Layer-by-layer security

| Layer | Protection |
|-------|-----------|
| Cloudflare | GitHub IP whitelist (`140.82.112.0/20`) |
| Cloudflare | "Skip Bot Mgmt on webhook path" triple-scoped rule |
| nginx | TLS 1.3 mandatory |
| systemd | IPAddressAllow GitHub ranges (rebuild git fetch) |
| MCP | HMAC-SHA256 validation (32-byte random secret) |
| MCP | Strict `refs/heads/main` validation |
| MCP | Rate limiting 1/min (slowapi) |
| MCP | JSON audit log ingested into BetterStack |

Defense-in-depth. If one layer is compromised, others hold.

## What already works

- Strategy 4 (`.gitignore content/`) in place repo-side
- `mcp-oauth-proxy` can already route to new endpoints without OAuth redeployment
- Cloudflare Bot Management scoped bypass already created (cf. [CF blocking post-mortem](/en/posts/postmortem-cf-bot-blocking-mcp/))
- Structured JSON audit log not yet in place MCP-side — that's a security sprint prerequisite

## What's left to do

1. Implement `/webhook/git` endpoint in `hugo-mcp` (~2h)
2. Configure GitHub webhook with HMAC secret (5 min)
3. Reproducible tests: real push → auto rebuild in less than 10s (~30 min)
4. Rollback procedure: if rebuild breaks the site, auto-revert to last valid commit (~1h)

Total estimate: **~4h**. Reasonable effort, but not priority vs the ongoing MCP security sprint.

## Decision

Webhook Git Strategy 4 will be implemented **after** the MCP security sprint (rate limiting + JSON audit logs + Pydantic v2). Several of this webhook's security layers (rate limiting, audit logs) are sprint chantiers — no point re-implementing them here, they'll be globally available after.

ETA: June 2026.

## Reference

Full technical brief archived in the `hugo-mcp` repo: `docs/backlogs/webhook-git-rebuild-2026-05-08.md`. Includes detailed Python code, nginx snippets, exact Cloudflare config, and rollback runbook.



## Tags

- roadmap
- hugo
- webhook
- github
- ci-cd
- mcp

## Categories

- roadmap
