Fix-Plan — Security-Audit 2026-05-15

24 Findings, sortiert nach Risk-Reduction-pro-Minute. Reihenfolge ist optimiert auf “groesster Schmerz zuerst weg, billige Fixes mitnehmen, Blocker fuer Friseur-Live raus”.

Status-Codes: OPEN / IN_PROGRESS / DONE / WONTFIX. Bei Erledigung in baseline.json fixed: true setzen.


Heute Abend — 90 Min, raeumt 3 von 5 CRITs ab

Block A — Token-Rotation (15 Min)

F003 — GitHub-PAT + ElevenLabs-Key aus ~/.claude.json rotieren

# 1. GitHub PAT revoke (Browser)
open https://github.com/settings/tokens
# alten gho_... finden, "Delete" — sofort tot
 
# 2. Neuen PAT anlegen mit Minimal-Scopes (repo, read:org)
# Wert in 1Password ablegen unter: "GitHub PAT — Claude Code dev (2026-05)"
 
# 3. ElevenLabs revoke (Browser)
open https://elevenlabs.io/app/settings/api-keys
# alten sk_... revoke, neuen anlegen, in 1Password ablegen
 
# 4. Beide neu in ~/.claude.json eintragen — ABER:
# Mittelfristig auf op-Wrapper migrieren (analog Hetzner-MCP).
# Heute Quick-Fix: neue Werte rein, Migration als F003b separat.

Verifikation: grep -E "gho_|sk_" ~/.claude.json zeigt neue Token-Praefixe. Alte Tokens (alt-Werte) ueber gh api user bzw ElevenLabs-Console testen → 401 erwartet.

Status: OPEN → setze auf DONE in baseline.json sobald beide neu.

Block B — mcp-whatsapp absperren (60 Min)

Reihenfolge: erst Notbremse via Cloudflare, dann sauberer Fix im Code.

F001 — /mcp ohne Auth → Cloudflare-Access davor (15 Min Notbremse)

1. Cloudflare Dashboard → Zero Trust → Access → Applications
2. "Add an application" → Self-hosted
   - Name: mcp-whatsapp /mcp
   - Subdomain: mcp-whatsapp.agenticventures.de
   - Path: /mcp/*
3. Policy: "Marvin only"
   - Action: Allow
   - Include: Email = hello@marvinkuehlmann.com (oder Google OAuth)
4. Save → testen: curl https://mcp-whatsapp.agenticventures.de/mcp ohne Cookie → 302 Redirect

Damit ist die Tuer zu, bevor Code-Fix kommt. Friseur-Bot bleibt benutzbar (Webhook geht weiter durch — siehe F002).

F002 — /webhook HMAC-SHA256-Validation (45 Min)

cd ~/source/mcps/mcp-whatsapp
 
# 1. APP_SECRET aus Meta Developer Console kopieren
#    https://developers.facebook.com/apps/<app-id>/settings/basic/
#    "App Secret" → "Show"
 
# 2. In AWS Secrets Manager ablegen
aws secretsmanager create-secret \
  --name mcp-whatsapp/META_APP_SECRET \
  --secret-string "<wert>" \
  --profile av-prod
 
# 3. CDK-Stack: secrets-Mapping erweitern (cdk/stack.ts)
#    Pattern siehe vf-hosted-Stack (ContainerImage.fromEcrRepository mit secrets:{...})
#    Variable: META_APP_SECRET
 
# 4. server.py — Webhook-Handler:
#    pip install (oder uv add) — hmac ist stdlib, kein neuer dep

Server-Patch (server.py:569 Bereich):

import hmac, hashlib, os
 
def verify_meta_signature(body: bytes, signature_header: str | None) -> bool:
    if not signature_header or not signature_header.startswith("sha256="):
        return False
    secret = os.environ["META_APP_SECRET"].encode()
    expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header)
 
@app.post("/webhook")
async def webhook(request: Request):
    body = await request.body()
    sig = request.headers.get("X-Hub-Signature-256")
    if not verify_meta_signature(body, sig):
        raise HTTPException(401, "invalid signature")
    # ... bisheriger Code

Zusaetzlich (F-Hardcode-Fallback):

# raus:
VERIFY_TOKEN = os.environ.get("WHATSAPP_WEBHOOK_VERIFY_TOKEN", "agentic-friseur-verify-2026")
# rein:
VERIFY_TOKEN = os.environ["WHATSAPP_WEBHOOK_VERIFY_TOKEN"]

Deploy: cdk deploy McpWhatsappStack --profile av-prod. Smoke-Test: Meta-Webhook-Tester schickt signed Payload → 200. curl mit falscher/keiner Signatur → 401.

Status nach Heute Abend: F001, F002, F003 → DONE. 2 CRITs offen (beide AWS-Auth).


Morgen — 60 Min, raeumt die anderen 2 CRITs ab

Block C — AWS Mgmt-Account haerten (45 Min)

F004 — Mgmt-Account Root-Keys + mkuehlmann MFA

# 1. Login Mgmt-Account Root via Console
#    https://signin.aws.amazon.com/console — Email + Pwd + (noch keine MFA)
#    -- Bei Login: MFA-Device direkt anlegen (Hardware-Key bevorzugt, sonst Authy/1Password)
 
# 2. Root Access Keys deaktivieren + loeschen
#    IAM → "Security credentials" (im Root-Menue oben)
#    Access Keys → falls vorhanden: "Make inactive" → "Delete"
 
# 3. IAM-User mkuehlmann MFA aktivieren
aws iam list-mfa-devices --user-name mkuehlmann --profile av-mgmt
# Wenn leer:
#    Console: IAM → Users → mkuehlmann → "Security credentials" → "Assign MFA device"
 
# 4. Alte Access-Keys von mkuehlmann pruefen + rotieren
aws iam list-access-keys --user-name mkuehlmann --profile av-mgmt
#    Wenn > 90 Tage alt: neuen Key anlegen, alten deaktivieren, nach 7 Tagen loeschen.
#    Idealerweise: User-Keys ganz raus, ueber Identity-Center-Role arbeiten.

F005 — Root-MFA fuer av-production, av-becker, mk-privat (15 Min)

Pro Account: Console-Login als Root → IAM → MFA-Device → Authenticator-Code scannen.

1. av-production: https://491085415349.signin.aws.amazon.com/console
2. av-becker: https://<account-id>.signin.aws.amazon.com/console
3. mk-privat: <id>.signin.aws.amazon.com/console

(Account-IDs aus intern/capabilities/aws/accounts.md.)

Status nach Morgen: 5/5 CRITs DONE.


Diese Woche — 4h, raeumt 7 HIGHs ab

Block D — Hetzner-Spur (45 Min)

F007 — DNSSEC + CAA aktivieren

1. agenticventures.de (Cloudflare-NS):
   - Cloudflare Dashboard → DNS → Settings → "Enable DNSSEC"
   - Cloudflare gibt DS-Record aus → bei Domain-Registrar (Hetzner-Domain-Robot
     oder wo agenticventures.de registriert ist) DS-Record eintragen
   - DNS → Records → CAA hinzufuegen:
       Name: @
       Tag: issue
       Value: letsencrypt.org
       Plus: issue: amazonaws.com  (fuer ACM-Cloudfront)
       Plus: iodef: mailto:hello@marvinkuehlmann.com

2. marvinkuehlmann.com (Route 53):
   aws route53 enable-hosted-zone-dnssec --hosted-zone-id <id> --profile mk-privat
   # → KMS-Key wird auto-erstellt, DS-Record kommt zurueck
   # DS-Record bei Domain-Registrar (Hetzner?) eintragen
   # Plus CAA-Records analog

Verifikation: dig +dnssec agenticventures.de SOA → AD-Flag gesetzt. dig CAA agenticventures.de → Records sichtbar.

F008 — pdf.agenticventures.de absichern

1. Stirling-Default-Login resetten:
   ssh root@av-tools-stirling-01.av (IP aus Hetzner-Console)
   docker exec stirling /bin/sh -c 'rm /configs/settings.yml'  # Reset Force-Login
   # oder neue settings.yml mit `security.loginAttempts.maxAttempts: 5` + neuem User

2. Cloudflare Access-App anlegen (analog F001):
   - Subdomain: pdf.agenticventures.de
   - Policy: Marvin-Email + ggf. Team-Mitglieder
   - Damit: kein direkter Stirling-Login mehr noetig, Cloudflare ist die Auth-Schicht

F009 — SSH-Firewall av-tools-stirling-01

# Aktuelle Firewall finden
# (via mcp__hetzner__list_firewalls + get_firewall)
 
# Ziel: SSH (22) nur von:
# - Marvins fixer IP (falls vorhanden) oder
# - Tailscale-CGNAT (100.64.0.0/10) — wenn Tailscale eingerichtet
# - Notfall-Bastion (falls vorhanden)
 
# Quick-Fix wenn keine fixe IP: SSH-Port von 22 auf hohen non-standard Port
# (security-by-obscurity, aber reduziert Bot-Scans um 99%) + fail2ban schaerfer

Block E — agents-platform + Vault (1h)

F006 — npm audit fix

cd ~/source/agents-platform
npm audit fix
npm run synth  # CDK noch buildbar?
git add package-lock.json
git commit -m "sec: npm audit fix — fast-uri CVE 7.5 via aws-cdk-lib"

F010 — .claude/settings.local.json Allowlist aufraeumen

cd ~/source/agentic-ventures
cat .claude/settings.local.json
# pruefen: Wildcards wie Bash(aws s3 *), Bash(aws lambda *), Bash(op item *) raus
# stattdessen: enge Patterns Bash(aws s3 ls), Bash(aws s3 cp) etc.
# globale skipDangerousModePermissionPrompt: true → false

Skill /fewer-permission-prompts kann hier helfen — laeuft analytisch ueber Transcripts und schlaegt enge Patterns vor.

Block F — Icking-Rebuild (2h)

F011 — /chat Rate-Limit verdrahten

# fastapi_app.py
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
 
@app.post("/chat")
@limiter.limit("20/minute")
async def chat(request: Request, ...):
    ...

Settings: RATE_LIMIT_CHAT="20/minute" aus env, default fallback. Test: 21 Calls von gleicher IP innerhalb 60s → 429.

F012 — cloudflared essential=true

// infra/icking-stack.ts — taskDef.addContainer fuer cloudflared
{
  essential: true,  // war false
  ...
}

Deploy. Smoke: kill cloudflared-Container → App-Container faellt mit, ECS startet Task neu, kein orphan reachable.


Naechste 2 Wochen — MEDs (5h Gesamt)

Reihenfolge nach Blast-Radius:

#FindingAufwandHinweis
F022Icking Prompt-Injection: User-Content + RAG-Hits2hAnti-Exfil-Klausel im System-Prompt, max_tokens-Cap 1024
F020GuardDuty Org-weit aktivieren30 MinDelegated Admin auf av-security oder av-prod
F021Access Analyzer pro Account30 MinOrg-Analyzer reicht — einmal anlegen, deckt alle Sub-Accounts
F019Secrets-Rotation in av-prod (25 Stueck)1hPro Secret pruefen ob Rotation moeglich (Lambda-Rotator vs manuell)
F014mcp-papierkram read-only-Variante1hZweiter MCP-Mount-Path /papierkram-readonly/mcp mit subset der Tools
F015mcp-gsuite Migration oauth2clientgoogle-auth2hCode-Patch + Re-OAuth-Flow
F016mcp-whatsapp Digest-Pinning15 MinImage-SHA in CDK statt :latest
F017mcp-whatsapp CI/Lint/gitleaks1hWorkflow analog mcp-vf-hosted
F018Vault pre-commit Visibility-Guard30 MinHook der bei visibility: internal Frontmatter git push origin main blockt — oder zumindest warnt
F013agents-platform dashboard-presignup Message generischer10 Min”Diese Email ist nicht autorisiert” → “Anmeldung fehlgeschlagen”
F023Icking Custom-VPCnicht jetztbeim naechsten Stack-Bump
F024Hetzner age-Key + Volume-LUKS-Pattern ADR1hVor erstem Industriekunden-Hetzner-Project

Nicht-Security-Doku-Drift mitnehmen (5 Min)

  • Buckets-Map ergaenzen: av-dashboard-data-*, av-dashboard-frontend-*, av-production-terraform-state, openwebui-vf-uploads-*intern/capabilities/aws/buckets.md
  • Apps-Inventar ergaenzen: inference-service-prod, presenton-vfintern/capabilities/apps/
  • mcp-vf-hosted DNS-Cutover-Status pruefen + im MCPs-Index korrigieren

Tracking

Wenn ein Finding erledigt:

  1. baseline.json editieren: "fixed": true setzen
  2. Commit-Message Format: sec(<scope>): fix <F-ID> — <kurztitel>
  3. Beim naechsten security-audit-biweekly-Lauf (1. Juni) wird das automatisch im Diff sichtbar

Eskalations-Pfade

  • Mgmt-Account-Aussperrung waehrend MFA-Setup: AWS-Support, Tier-Premium-Ticket (kann 24h dauern) — vor MFA-Setup Recovery-Email + Hardware-Key griffbereit.
  • mcp-whatsapp-Outage waehrend Cloudflare-Access-Setup: Friseur-Bot pausiert temporaer. Falls Pilot-Kunde aktiv testet: kurz Bescheid geben („sicherheits-update, ca. 30 Min”).
  • GitHub-PAT-Revoke bricht laufenden Skill: unwahrscheinlich aber moeglich. Neue PAT direkt nach Revoke anlegen, ~5 Min Lucke.

Verwandt