Roadmap: Git webhook → automatic Hugo rebuild

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), 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
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 secondsTechnical 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:
{
"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-proxycan already route to new endpoints without OAuth redeployment- Cloudflare Bot Management scoped bypass already created (cf. CF blocking post-mortem)
- Structured JSON audit log not yet in place MCP-side — that’s a security sprint prerequisite
What’s left to do
- Implement
/webhook/gitendpoint inhugo-mcp(~2h) - Configure GitHub webhook with HMAC secret (5 min)
- Reproducible tests: real push → auto rebuild in less than 10s (~30 min)
- 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.