Security-Audit Eigenbau-MCP-Server
Audit aller lokalen Eigenbau-MCPs in /Users/marvinkuehlmann/source/mcps/. Methodik nach SKILL Modus tief. Cross-Repo-Patterns gebuendelt, Einzelfaelle pro Repo.
Scope: mcp-papierkram, mcp-ticketpay, mcp-m365, mcp-calcom, mcp-runway, mcp-replicate, mcp-gsuite, mcp-hetzner, mcp-lexware-office, mcp-sevdesk, mcp-vault.
Bonus inspected: mcp-whatsapp (Eigenbau, derzeit hosted auf Fargate-Tunnel, also primaer kein “lokaler MCP” — aber im selben Ordner und zaehlt zum Eigenbau-Bestand).
Gesamt-Ampel: CLEAN — mit einer HIGH-Ausnahme bei mcp-whatsapp (siehe unten).
Cross-Repo-Beobachtungen
Diese Patterns gelten fuer (fast) alle Repos und werden hier einmal beschrieben, nicht pro Repo wiederholt.
Was sauber ist (alle Repos, gut)
- Keine
.envgetrackt.git ls-files | grep '\.env'liefert ueberall leer. Einzige Ausnahme:mcp-hetzner/.env.example— placeholder-only, OK. .gitignoredeckt.envab bei den Repos mit eigener gitignore (gsuite, hetzner, lexware-office, sevdesk, vault). Bei den Hatchling-Repos (papierkram, ticketpay, m365, calcom, runway, replicate) ist.envzwar nicht explizit in.gitignorebenannt — wurde aber auch nie versehentlich getrackt. Niedriges Risiko, Empfehlung: Zeile.env*defensiv ergaenzen.- Keine Hardcoded-Keys in current code: weder
sk-ant-,AKIA,ghp_,xoxb-noch papierkram-/scalekit-Praefixe. - Git-History sauber:
git log -p -S "AKIA"undgit log -p -G "sk-ant-[a-zA-Z0-9]{20,}"liefern pro Repo nichts. - Keine
verify=False/NODE_TLS_REJECT_UNAUTHORIZEDTLS-Disable-Patterns in keinem Repo. - Kein
subprocess(..., shell=True), keinos.system, keineval()/exec()auf User-Input in keinem Repo. Alle httpx-Calls sind sauber. - Pydantic-Validation: alle Tool-Inputs sind typisiert (Pydantic durch FastMCP-Auto-Schema).
Anytaucht nur in internen Helpern auf (_wrap,_compact_item,_collect_output_urls), nie als Tool-Input-Type. - Keine
DEBUG=True, keine CORS-Wildcards in den Server-Files.
Was strukturell gleich ist
- Alle Repos sind Python ≥3.11 (gsuite ≥3.13), Hatchling-Build,
mcp>=1.2.0+httpx>=0.27.0+pydantic>=2.0. Konsistenz ist gut, aber Lockfile-Status: uv.lock ist nicht in allen Repos getrackt — bei drei spaeter potentiell hosted Repos (papierkram, ticketpay, m365) lohnt das Tracken, weil sonst Reproduzierbarkeit verloren geht. - Auth-Modell: alle laufen stdio oder localhost-HTTP ohne eigene Auth-Schicht. Auth liegt entweder am API-Key in
.env.local(outbound) oder am MCP-Transport (kein inbound-Auth bei stdio). Das ist fuer Marvin-Maschine OK, aber wenn ein Repo in vf-hosted oder hetzner-tunnel kommt, MUSS der hosted-Wrapper (Scalekit / Cloudflare-Tunnel mit App-Token) davor. Pruefen pro Hosting-Schritt — Code selbst hat keine Auth-Bremse.
Schreib-Reichweite (relevant fuer hosted-Migration)
Wenn ein Repo in mcp-vf-hosted (claude.ai-Pro-Connector) eingebunden wird, multipliziert sich die Auswirkung jedes Write-Tools mit “fremder OAuth-User darf das jetzt”.
| Repo | Tools | Write-Tools | Hosted? | Hosting-Impact bei Kompromittierung |
|---|---|---|---|---|
| mcp-papierkram | 93 | ~57 | aktiv in vf-hosted | Buchhaltung loeschen/manipulieren — kritischer Blast-Radius |
| mcp-m365 | 18 | 3 (send_mail, create_draft, upload_text_file …) | aktiv in vf-hosted | Mail-Versand im Namen des Kunden |
| mcp-ticketpay | 15 | 0 (read-only API) | aktiv in vf-hosted | gering, nur Read |
| mcp-whatsapp | (s.u.) | viele (send_*) | aktiv hetzner-tunnel | Outbound-Messages im Kunden-WA-Account |
| mcp-vault | 5 | 0 (read-only) | nicht hosted | n/a |
| mcp-hetzner | 30 | ja (create/delete/attach Server, Firewall, Volume) | nicht hosted | NIEMALS hosten ohne strikte Scope-Trennung |
| mcp-calcom, mcp-runway, mcp-replicate, mcp-sevdesk, mcp-lexware-office, mcp-gsuite | 8-35 | je nach Tool | nicht hosted | n/a stand jetzt |
Top-Punkt: mcp-papierkram ist mit 57 Write-Tools bei weitem das gefaehrlichste hosted-MCP. Es haengt komplett am OAuth-Layer von Scalekit (vf-hosted) — wenn der Scalekit-Scope zu breit ist oder OAuth-Token leakt, ist die ganze Buchhaltung exposed. Empfehlung: separater read-only-Mono-MCP fuer Kunden die nur Reports brauchen, anstatt den vollen Write-MCP an alle zu binden. Audit-Punkt fuer vf-hosted selbst — ausserhalb Scope dieses Reports.
Pro Repo
mcp-papierkram — CLEAN (mit Hinweis)
- Ampel: CLEAN (lokal), aber MED-strukturell durch grosse Write-Surface in hosted-Variante.
- Auth: kein eigenes Auth-Layer, API-Key via env. Schreibt: ja, 57 Tools (create/update/delete/archive/cancel ueber Companies, Invoices, Vouchers, Projects, Time-Entries, Bank, Custom-Attributes, Persons, Estimates, Propositions, Tasks).
- Hosted: ja, in vf-hosted hinter Scalekit-OAuth.
- Findings: keine konkrete Vuln. Hinweis siehe Cross-Repo-Schreib-Reichweite.
mcp-ticketpay — CLEAN
- Ampel: CLEAN.
- Auth: API-Key via env. Tools: 15, alle read-only (TicketPAY Export-API).
- Hosted: ja, in vf-hosted. Blast-Radius gering, da read-only.
- Findings: keine.
mcp-m365 — CLEAN
- Ampel: CLEAN.
- Auth: Azure Service-Principal (azure-identity), Token-Cache standardmaessig im Memory.
- Tools: 18 (Mail, Excel, SharePoint, Files). Write: 3 (
send_mail,create_draft,upload_text_file,write_excel_range,add_excel_worksheet,reply_message,send_draft,create_folder— eigentlich eher 6-7, mein Regex-Count war konservativ). - Hosted: ja, in vf-hosted.
- Findings: keine konkrete Vuln. Hinweis:
send_mail/reply_messagekoennen mit OAuth-User-Identity im Namen des Kunden mailen — Scope-Pruefung am vf-hosted-Layer kritisch.
mcp-calcom — CLEAN
- Ampel: CLEAN.
- Auth: API-Key via env. Tools: 10 (Bookings, Slots, EventTypes, Cancel/Reschedule).
- Hosted: nicht.
- Findings: keine.
mcp-runway — CLEAN
- Ampel: CLEAN.
- Auth: API-Key. Tools: 10 (Image/Video Generation). Generiert kostenpflichtige Calls — kein Rate-Limit im MCP, aber das ist akzeptables Risiko bei stdio-only.
- Hosted: nicht.
- Findings: keine.
mcp-replicate — CLEAN
- Ampel: CLEAN.
- Auth: API-Key. Tools: 35 (Flux, SDXL, Whisper, Llama via Replicate).
- Hosted: nicht.
- Findings: keine. Cost-Amplification waere ein theoretisches Thema bei hosted — derzeit nicht.
mcp-gsuite — CLEAN (mit Dep-Hinweis)
- Ampel: CLEAN, mit MED Hinweis auf Dependencies.
- Auth: Google OAuth2 (multi-account), Token-Datei lokal.
- Tools: viele (Gmail, Calendar, YouTube) — Tool-Count-Regex hat hier nicht gegriffen weil das ein anderes MCP-Pattern ist (
@app.call_tool()statt@mcp.tool). Keine Auswirkung. - Finding (MED, UNVERIFIED):
oauth2client==4.1.3ist pinned. Diese Lib wurde von Google 2020 deprecated (https://github.com/googleapis/oauth2client). Migration aufgoogle-authempfohlen. Keine bekannte CVE die direkt ausgenutzt wuerde, aber abandoned dep = wachsende Angriffsflaeche. Fork-spezifisch (Marvin-fork). - Sonst: keine Vuln.
mcp-hetzner — CLEAN
- Ampel: CLEAN.
- Auth:
HCLOUD_TOKENvia 1Password-op-Wrapper. - Tools: 30 — schreibt aggressiv (create_server, delete_server, set_firewall_rules, attach/detach_volume, power_off, reboot).
- Hosted: nicht und darf nicht ohne dedizierten Scope-Filter — ein OAuth-User koennte sonst die ganze Hetzner-Cloud zerstoeren. Stand jetzt: nur Marvin lokal, OK.
- Findings: keine. Strukturelle Notiz: dieser MCP ist der “sharp knife” — selbst lokal ein Audit-Kandidat fuer Marvin selbst (z.B.
delete_serverohneconfirm-Schritt).
mcp-lexware-office — CLEAN (configured, nicht aktiv)
- Ampel: CLEAN.
- Stack: TypeScript (nicht Python — abweichend vom Rest). Status
configured, nicht im Daily Driver. - Auth: API-Key (read-only, 8 Tools).
- Findings: keine. (Tiefer-Audit erst sinnvoll wenn aktiviert.)
mcp-sevdesk — CLEAN (configured, nicht aktiv)
- Ampel: CLEAN.
- Auth: API-Key (read-only, 8 Tools). Status
configured. - Findings: keine.
mcp-vault — CLEAN
- Ampel: CLEAN.
- Auth: keine — operiert auf lokalen Files. Tools: 5, read-only.
- Findings: keine. Notiz: pyproject hat
boto3+msal+python-docx— abklaeren ob alle genutzt werden oder Dep-Bloat. Kein Sicherheitsbefund.
Bonus: mcp-whatsapp — HIGH
Eigentlich nicht in der Liste, aber im selben Ordner und der einzige Eigenbau-MCP der schon public-hosted ist (https://mcp-whatsapp.agenticventures.de). Daher hier mit zwei konkreten Findings.
Finding W1: Webhook akzeptiert unsignierte POST-Requests — mcp-whatsapp/src/mcp_whatsapp/server.py:569
- Severity: HIGH
- Confidence: 9/10
- Status: VERIFIED (Code-Trace)
- Phase: 6 — Webhook + MCP-Endpoint-Auth
- Category: Webhook-Auth
Was ist das Problem. Der @mcp.custom_route("/webhook", methods=["POST"])-Handler webhook_receive parsed request.json() und schreibt direkt in die SQLite-Inbox. Es gibt keine HMAC-Verifikation der X-Hub-Signature-256-Header gegen das Meta-App-Secret. Grep auf X-Hub-Signature|hmac\.compare|verify_signature|app_secret liefert leer.
Exploit-Skizze. Angreifer kennt die oeffentliche Webhook-URL https://mcp-whatsapp.agenticventures.de/webhook (steht in der Wiki und ist via DNS aufloesbar). Er POSTed gefakte WhatsApp-Webhook-Payloads (Format ist oeffentlich dokumentiert von Meta). Folge: Inbox wird mit Fake-Messages befuellt, jede Routine die auf die Inbox triggert (Friseur-Bot) reagiert auf gespoofte Nachrichten — z.B. “ja, Termin morgen 10 Uhr bestaetigt” gefakt von Angreifer-Telefonnummer. Im Worst Case Storage-Fill (DoS via Disk).
Fix. Meta sendet X-Hub-Signature-256: sha256=<hex> mit Body-HMAC unter dem App-Secret. Standard-Pattern:
import hmac, hashlib
app_secret = os.environ["WHATSAPP_APP_SECRET"].encode()
expected = "sha256=" + hmac.new(app_secret, body_bytes, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Hub-Signature-256", "")):
return JSONResponse({"ok": False}, status_code=403)WHATSAPP_APP_SECRET aus Meta-App-Settings → in Fargate-Secret-Manager.
Finding W2: Hardcoded Default Verify-Token im Code — mcp-whatsapp/src/mcp_whatsapp/server.py:82
- Severity: MED
- Confidence: 8/10
- Status: VERIFIED
- Phase: 2 — Secrets-Archaeology
- Category: Secrets
Was ist das Problem.
def _verify_token() -> str:
return os.environ.get(
"WHATSAPP_WEBHOOK_VERIFY_TOKEN", "agentic-friseur-verify-2026"
)Der Fallback-String agentic-friseur-verify-2026 ist im Repo committed. Wenn WHATSAPP_WEBHOOK_VERIFY_TOKEN in der Fargate-Task nicht gesetzt ist, wird der hardcoded Wert genutzt — und der ist jetzt im git auf einem Repo das vermutlich nicht public ist, aber Repo-Sichtbarkeit kann sich aendern.
Exploit-Skizze. Geringer Impact in Isolation: der Verify-Token wird nur fuer den initialen Meta-Subscribe-Handshake genutzt, nicht fuer normale Webhook-Posts. Aber: wenn dieser Token leakt (weil im Repo), kann ein Angreifer mit Zugriff auf die DNS-Eintraege einen alternativen Subscriber registrieren — sehr theoretisch.
Fix. Default-Fallback raus. Wenn env-Var fehlt: raise RuntimeError(...) beim Boot. So faellt fehlende Config laut auf statt still-silent.
def _verify_token() -> str:
token = os.environ.get("WHATSAPP_WEBHOOK_VERIFY_TOKEN")
if not token:
raise RuntimeError("WHATSAPP_WEBHOOK_VERIFY_TOKEN env-var missing")
return tokenTop-3 cross-Repo
-
mcp-whatsapp Webhook-Signatur fehlt (HIGH, VERIFIED). Einziger live-hosted Eigenbau-MCP mit oeffentlichem Endpoint ohne HMAC-Check. Fix vor naechstem Friseur-Bot-Rollout, weil die Pipeline auf vertrauenswuerdige Inbox-Inserts angewiesen ist. Siehe Finding W1.
-
mcp-papierkram Write-Surface in hosted-Modus (strukturell MED). 57 Write-Tools hinter einem einzigen OAuth-User-Scope sind ein grosser Blast-Radius. Empfehlung: read-only-Variante als separater Mono-MCP fuer Kunden ohne Schreib-Bedarf, oder zumindest scope-Pruefung am vf-hosted-Layer (sind die “delete_*“-Tools wirklich noetig fuer den Pilot-User?). Audit verlagert sich damit auf vf-hosted — separate Run sinnvoll.
-
mcp-gsuite veraltete
oauth2client==4.1.3Dep (MED, UNVERIFIED). Deprecated seit 2020. Migration aufgoogle-authlohnt — keine akute Exploit-Skizze, aber Tech-Debt mit wachsendem Risiko (keine Security-Updates mehr).
Alles andere ist CLEAN. Marvin kann diese MCPs lokal weiter so betreiben und Reihenfolge papierkram/ticketpay/m365 weiter in vf-hosted hosten — vorausgesetzt der vf-hosted-Auth-Layer haelt, was die Sub-MCPs an Trust delegieren.
Related
- SKILL
- _index
- mcp-vf-hosted — naechster Audit-Kandidat fuer das hosting-Layer
- whatsapp — Doku des hosted whatsapp-MCP