Files
bf-infra/docs/handoff-20260901-n8n-mcp-ssrf.md
Christoph Emsenhuber fde9f1535a n8n-mcp SSRF-Fix: permissive + in Portainer-Stack 174 gefaltet
- WEBHOOK_SECURITY_MODE=permissive → n8n-API-Anbindung (http://n8n:5678) wieder verbunden
  (n8n_health_check success:true); Blocker war der app-eigene SSRF-Guard, nicht Netz/Bearer.
- Waisen-Container aus homelab-cline/n8n-mcp-deploy in Portainer-Stack 174 (n8n) gefaltet;
  n8n/redis ohne Downtime, Bearer/Port/Volume erhalten. Orphan-Ordner entfernt.
- SSoT-Mirror docs/macmini-vlan20/stacks/ (gitignored) + Rollback-Backup.
- TP30 Posten #8: N8N_SMTP_PASS beim Stack-Read geleakt (5. Maskierungs-Panne) → Rotation fällig.
- INVENTORY + Handoff aktualisiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-01 21:04:11 +02:00

4.7 KiB

Handoff 2026-09-01 — n8n-mcp SSRF-Block ✅ GEFIXT + in Portainer-Stack 174 gefaltet

Kontext

Aus TP10 (Terpenfinder Auto-Refresh, Repo blaueblueten-n8n) ausgelagerter bf-infra-Task: der n8n-MCP war einsatzfähig, aber seine Anbindung an die n8n-API war blockiert. Diese Session hat den Blocker behoben und die Container-Definition sauber verortet.

Befund (Ausgangslage)

  • Netz war nie das Problem: aus dem n8n-mcp-Container erreichten beide Wege n8n — intern http://n8n:5678 → HTTP 200, öffentlich https://n8n.emsenhuber.net/healthz → HTTP 200.
  • Einziger Blocker = App-eigener SSRF-Guard von n8n-mcp (v2.57.1 → nach Redeploy 2.77.0): N8N_API_URL=http://n8n:5678 löst auf die private Docker-Bridge-IP 172.19.0.3 auf, und der Guard lehnte private IPs im Default-Modus strict ab (SSRF protection: Private IP addresses not allowed, connected:false). Der API-Client validiert N8N_API_URL über genau diesen Guard (/app/dist/services/n8n-api-client.js:104 → SSRFProtection.validateWebhookUrl).
  • SSoT-Drift (mit-gefixt): der laufende Container war ein Waise aus ~/dev/homelab-cline/n8n-mcp-deploy/docker-compose.yml (kein Git-Repo), und dieses File stimmte mit dem laufenden Container nicht überein (File: https://n8n.emsenhuber.net + Netz web; Container: http://n8n:5678 + Netz n8n_default). n8n+n8n-redis liefen dagegen im Portainer-Stack 174 (n8n, Endpoint 20 = macmini).

Fix (durchgeführt 2026-09-01)

Entscheidung (User): intern + permissive, und n8n-mcp in Portainer-Stack 174 falten.

  1. SSRF entschärft: WEBHOOK_SECURITY_MODE=permissive gesetzt → private/LAN-IP für N8N_API_URL=http://n8n:5678 erlaubt. Bleibt auf der Docker-Bridge, kein CF/Tunnel/Caddy- Umweg, keine 10-s-DNS-Falle, unabhängig von der Edge. Vertretbar, weil dieser Container ausschließlich mit dem trusted LAN-n8n redet.
  2. Waise beseitigt: orphan-Container entfernt (docker rm -f n8n-mcp), n8n-mcp als dritter Service in Stack 174 aufgenommen (neben n8n+redis, gemeinsames Default-Netz n8n_default, depends_on: n8n). Stack via Portainer-API redeployt (PUT /api/stacks/174?endpointId=20, prune:false).
  3. Erhalten geblieben: Bearer (AUTH_TOKEN==MCP_AUTH_TOKEN, len 64) unverändert → bestehende Clients (opencode http://10.40.20.167:3001/mcp) laufen weiter; Host-Port-Bind 10.40.20.167:3001:3000 → Caddy-Route n8n-mcp.emsenhuber.net (Caddyfile Z. 75) intakt; Daten-Volume als external wiederverwendet (n8n-mcp-deploy_n8n-mcp-data, nodes.db bleibt) — eigener N8N_API_KEY von n8n-mcp (≠ n8n) unverändert.

Verifikation (nicht behauptet — gemessen)

  • SSRFProtection.validateWebhookUrl("http://n8n:5678") im Container → valid:true, resolved:172.19.0.3, mode:permissive.
  • MCP-Tool n8n_health_check (voller Handshake) → {"success":true,"status":"ok", "apiUrl":"http://n8n:5678","mcpVersion":"2.77.0","upToDate":true}.
  • n8n+n8n-redis behielten Container-ID und StartedAt (2026-07-29) → kein n8n-Downtime beim Redeploy (nur der neue Service wurde erstellt).

SSoT / Ablage

  • Master der Stack-Definition = Portainer-Stack 174 (n8n, Endpoint 20, macmini).
  • Gitignored Repo-Mirror + Rollback: docs/macmini-vlan20/stacks/174-n8n.yml (neuer Stand) und …/174-n8n.orig-pre-mcp-fold-20260901.yml (Vorzustand). Neu gitignored: docs/macmini-vlan20/stacks.
  • INVENTORY-Eintrag mcp/INVENTORY.md (n8n-mcp) aktualisiert.

Offene Punkte

  • 🔴 TP30 Posten #8: beim Ausgeben des Stack-174-Files ist N8N_SMTP_PASS (Gmail-App-PW johnny.claw.0x00@gmail.com) ins Transkript geraten (Redaktionsmuster hatte PASSWORD, nicht bare PASS). Rotation fällig — Google App-Passwort widerrufen + Stack-174-Env nachziehen.
  • ⚠️ Toter Orphan: ~/dev/homelab-cline/n8n-mcp-deploy/ (kein Git) kann gelöscht werden — ein erneutes docker compose up dort würde sonst einen Namenskonflikt mit dem Stack-174-Container erzeugen. Nicht kritisch (nur bei Handstart).
  • ⚠️ Client-401 in THIS Claude-Code-Session: der n8n-mcp-Server kam in dieser Session mit AUTH_HEADER_REJECTED (401) hoch — das ist ein Client-Config-Thema (~/.claude.json-Bearer leer/falsch, vgl. globale CLAUDE.md „kein Shell → {env:} leer"), nicht der hier gefixte Server. Der Server antwortet LAN-direkt korrekt; opencode ist unbetroffen.

Auswirkung auf TP10

Keine für den Feature-Pfad: TP10 läuft über Cloudflare Worker Cron, nicht über n8n. Der Fix macht n8n-mcp aber wieder als Werkzeug nutzbar (n8n-Workflow-Validierung, künftig optional TP10-als-n8n-Workflow). Rückantwort ans zuweisende Projekt: ~/dev/blaueblueten-n8n/docs/rueckantwort-n8n-mcp-fix-20260901.md.