Security-Audit — agents-platform

Scope: /Users/marvinkuehlmann/source/agents-platform Methodik: SKILL.md Phasen 1–5 + 9 (Attack-Surface, Secrets, Dependencies, CI/CD, Infra/CDK, OWASP-Mini), Modus tief. Stand: clean Working-Tree, branch main, letzter commit 0930503 feat: DashboardStack + GitHub-Aktivitaet-Modul deployed.

Gesamtbewertung: 8/10 — solides Security-Setup. Eine HIGH (transitive npm CVE), zwei niedrigprioritaere Code-Hygiene-Themen, sonst Lehrbuch.


Top-3 Findings

1. HIGH — fast-uri transitive Dependency mit zwei CVEs (CVSS 7.5)

Location: node_modules/aws-cdk-lib/node_modules/fast-uri@<=3.1.1 Refs: GHSA-q3j6-qgpj-74h6 (path traversal via percent-encoded dot segments), GHSA-v39h-62p7-jpjc (host confusion via percent-encoded authority delimiters)

npm audit meldet beide HIGH-Findings ueber aws-cdk-lib. Build-time only — fast-uri laeuft nicht in der Lambda-Runtime, sondern nur beim cdk synth/cdk deploy. Realer Angriffsvektor: minimal (lokale Dev-Maschine + CI), aber die Fix-Verfuegbarkeit ist trivial.

Fix: npm audit fix (haengt automatisch aws-cdk-lib Minor-Bump dran). Danach npm run synth ausfuehren um sicherzustellen dass kein Drift in den Templates entstand.

2. MEDIUM — Pre-Sign-Up-Lambda leakt Whitelist-Mechanismus via Fehlermeldung

Location: lambdas/dashboard-presignup/main.py:28

raise Exception(f"Diese Email ist nicht autorisiert.")

Cognito propagiert die exception-message zurueck zum Client (Hosted UI). Ein Angreifer der versucht sich mit einer fremden Google-Identitaet einzuloggen sieht direkt dass es eine Whitelist gibt — Information-Leak ueber das Auth-Modell. Nicht kritisch (Username-Enumeration ist via Cognito-Hosted-UI ohnehin nicht moeglich dank preventUserExistenceErrors), aber unnoetig informativ.

Zusaetzlich: Wenn ALLOWED_EMAIL env-var leer ist, wird ebenfalls eine sprechende Fehlermeldung geworfen ("Dashboard ist temporaer geschlossen (Konfigurationsfehler)") — gibt Hinweis auf Misconfiguration.

Fix:

raise Exception("Sign-up disabled.")

Generisch halten. Im CloudWatch-Log (intern) bleibt die detaillierte Info via log.warning(...) erhalten.

3. LOW — Bedrock Invoke-Permission mit Wildcard-Region

Location: infra/lib/agent-construct.ts:184-187

resources: [
  'arn:aws:bedrock:eu-*::foundation-model/anthropic.claude-haiku-4-5-*',
  `arn:aws:bedrock:eu-*:${this.role.env.account}:inference-profile/eu.anthropic.claude-haiku-4-5-*`,
],

eu-* matcht aktuell eu-central-1, eu-west-*, eu-north-*, eu-south-*. Bedrock-EU-Inference-Profile laufen aktuell nur ueber eu-central-1 (siehe Vault intern/wissen/entscheidungen/bedrock-eu-image-gen-limitation.md). Wildcard ist Future-Proofing, expandiert aber den Permission-Scope.

Fix: eu-* durch eu-central-1 ersetzen. Wenn spaeter andere EU-Regions dazukommen, explizit erweitern.


Phasen-Detail

Phase 1 — Attack-Surface

  • Eingang: EventBridge-Crons (3 Stacks, kein User-Input), Cognito Pre-Sign-Up Trigger (Google IdP), Lambda Direct-Invoke (manuell durch Marvin via AWS-CLI).
  • Ausgang: Bedrock (Haiku 4.5), Telegram Bot API, Gmail API, Calendar API, GitHub REST (PAT), Papierkram MCP (https://mcp-vf.agenticventures.de/mcp), Secrets Manager, S3 (av-finanzen, av-dashboard-frontend, av-dashboard-data), STS AssumeRole (mgmt + mk-privat).
  • Cognito-Surface: Hosted UI mit Google IdP + Pre-Sign-Up-Whitelist (single email). Sauber.
  • Kein API-Gateway (Sprint 1.5), kein Public-Endpoint ausser CloudFront-Distribution (statische Dashboard-Files + JSON-Daten, OAC, alles ueber HTTPS-Redirect).
  • Bewertung: Schmal, sauber definiert.

Phase 2 — Secrets

  • Git history: git ls-files zeigt nur secrets.py (Wrapper-Code, kein Secret). git log -p ueber *.env*/*secret* zeigt nur die Wrapper-Datei. Keine accidentally committeten Tokens.
  • .gitignore: deckt .env, .env.*, __pycache__, .venv. OK.
  • In-Code: alle Secrets via secrets.get("agent-platform/<name>") aus Secrets Manager. Kein hardcodetes Token, kein Plaintext-Env-Var fuer Secrets.
  • Telegram chat_id als Env-Var (8257793678) — nicht sensibel (Code-Kommentar). OK.
  • unsafeUnwrap() Google client_id in dashboard-stack.ts:442 — wird in CFN-Template gebakt. Acceptable (client_id ist per OAuth-Spec public).
  • Bewertung: clean.

Phase 3 — Dependencies / Supply Chain

  • package-lock.json: vorhanden, committed. Reproduzierbare Builds.
  • npm audit: 1 HIGH (siehe Finding #1), 0 critical/medium/low/moderate.
  • Python-Layer: keine requirements.txtagentic_common nutzt nur stdlib + boto3 + urllib3 (beides Lambda-preinstalled). Kein pip-bundle = kein Supply-Chain-Risiko via PyPI.
  • uvx/pip Smoke-Test-Code in den Modulen ist __main__-guarded, laeuft nicht in Lambda.
  • Bewertung: minimal Surface, ein bekannter HIGH-CVE der trivial fixbar ist.

Phase 4 — CI/CD

  • Keine .github/workflows/ vorhanden. Kein pull_request_target, keine unpinned third-party Actions. CI = nicht existent (Deploy nur lokal via npm run deploy).
  • cdk deploy --all --require-approval never in package.json Script — bewusst, fuer Marvins Solo-Workflow OK, aber Vorsicht: wenn der Befehl irgendwann in Automation laeuft, sollte --require-approval broadening rein (warnt bei IAM-/Security-Group-Changes).
  • Bewertung: kein Angriff-Vektor weil nicht existent.

Phase 5 — Infra / CDK

  • IAM-Wildcards: grep -E '"\*"' infra/lib/*.ts → keine Treffer fuer actions:["*"] oder resources:["*"]. Alle Policies sind least-privilege:
    • Bedrock: scoped auf Haiku 4.5 model+inference-profile (Wildcard nur in Region, siehe Finding #3).
    • S3: scoped auf av-finanzen-eu-central-1 Bucket + /* (read+write fuer dieses eine Bucket).
    • KMS: scoped auf den av-finanzen Key-ARN.
    • SecretsManager: scoped auf die 3 spezifischen Secret-ARNs.
    • STS:AssumeRole: scoped auf die 2 spezifischen Cross-Account-Role-ARNs.
  • S3-Buckets (alle 3): blockPublicAccess: BLOCK_ALL, enforceSSL: true, encryption: S3_MANAGED, removalPolicy: RETAIN. dataBucket zusaetzlich versioned: true mit 30-Tage-noncurrent-Lifecycle. Lehrbuch.
  • CloudFront: OAC (Origin Access Control, neu, nicht legacy OAI), REDIRECT_TO_HTTPS, PRICE_CLASS_100. SPA-Fallback errorResponses (404+403 → index.html) auf Distribution-Level — minor Schoenheitsfehler (Finding #4 unten als LOW gelistet), nicht security-kritisch.
  • Cognito: passwordPolicy 12+ Zeichen, alle Klassen required, MFA optional, preventUserExistenceErrors: true, generateSecret: false (public client), nur authorizationCodeGrant, implicitCodeGrant: false. Pre-Sign-Up-Lambda als Defense-in-Depth zur Whitelist. Solide.
  • Lambda-Konfig: alle ARM64 (Graviton, billiger + getrennt von x86-CVEs), PYTHON_3_12 (Support bis 2028), Log-Retention 30 Tage, Timeouts knapp (3–10 Min), Memory 128–1024 MB. Kein VPC (kein VPC-noetig — alle Calls gehen ueber Internet zu Google/Telegram/Bedrock).
  • EventBridge-Rules: Schedule-only, kein offener Pattern-Listener — keine Lambda kann von beliebigen AWS-Events getriggert werden.
  • Bewertung: sehr gut.

Phase 9 — OWASP / Code-Patterns

  • Input-Validation: EventBridge-Events sind AWS-trusted, Cognito-Events sind Cognito-trusted. Kein User-Input aus HTTP-Requests (kein API-Gateway). event.get("force_run") is True in daily-briefing ist sicher — Lambda-Direct-Invoke ist auf IAM beschraenkt.
  • SSRF: Lambda macht Outbound-Requests an fixe URLs (Telegram API, Gmail API, Calendar API, GitHub API, Papierkram MCP). Keine User-supplied URLs.
  • Command-Injection: keine subprocess/os.system/shell=True in Lambda-Code. Bedrock-Prompt-Injection waere theoretisch moeglich ueber Mail-Subjects/Snippets (briefing.py:75), aber Output ist via JSON-Schema-Tool-Use erzwungen — kein freier Text der dann irgendwo executed wird. Worst-Case: Briefing-Output unpassend formatiert. Acceptable.
  • Path-Traversal: GitHub-Client baut Pfade aus ACTION_ITEMS_PATH/BRIEFING_ARCHIVE_DIR Env-Vars (operator-controlled). OK.
  • Token-Logging: traceback.format_exc()[:500] in beleg-pipeline/main.py:99 koennte theoretisch Stack-Frames mit Secrets enthalten, aber die Secrets werden nicht als lokale Variablen mit erkennbarem Namen gehalten — bot_token = secrets.get(...) ist lokal und kommt nicht in Stack-Traces vor solange kein Exception innerhalb des Telegram-Calls geworfen wird. Akzeptabel.
  • Bewertung: sauber.

Weitere LOW / INFO Findings

  1. LOW — CloudFront errorResponses 403/404 → index.html auch fuer /api/data/*. dashboard-stack.ts:379-383 setzt errorResponses Distribution-weit. Eine 404 auf /api/data/nonexistent.json liefert index.html mit HTTP 200 zurueck. Frontend muss damit umgehen. Nicht security-kritisch, aber confused-deputy-Potenzial. Fix: per-behavior errorResponses oder CloudFront Function.

  2. INFO — unsafeUnwrap() of Google client_id (dashboard-stack.ts:442) — bewusst per Code-Kommentar. OAuth-client_id ist per Spec public, OK.

  3. INFO — Cross-Account-Trust-Policies nicht im Repo verifizierbar. Code setzt korrekt die Source-Role als Principal beim AssumeRole-Call. Die Trust-Policies auf der empfangenden Seite (mk-privat, mgmt) muessen separat verifiziert werden (Repo agentic-ventures/intern/capabilities/aws/). Siehe docs/migration-cloudflare-zu-lambda.md fuer den Cross-Account-Setup.

  4. INFO — GitHub-PAT mit Vault-Write-Permission. agent-platform/github-pat schreibt in marvin-khl/agentic-ventures. Bei Kompromittierung: vollstaendiger Vault-Write moeglich. Mitigation: Fine-Grained-PAT (contents:write + pull_requests:read nur fuer dieses eine Repo). Status-Check ob PAT bereits fine-grained ist: nicht aus dem Code erkennbar. Empfehlung: dokumentieren in intern/capabilities/agents-platform.md.

  5. INFO — cdk deploy --all --require-approval never in package.json:scripts.deploy. Bei Solo-CLI-Usage OK; bei kuenftiger CI-Integration auf broadening umstellen damit IAM-/SG-Changes Bestaetigung verlangen.

  6. INFO — MGMT_ACCOUNT_ID Konstante im Code (infra/bin/app.ts:16). Account-ID ist nicht sensitiv (kommt sowieso in jeden ARN), aber wenn das Repo public wird oder geleakt: Account-IDs sind ein Recon-Vorteil fuer Angreifer (gezieltes IAM-Probing, Cross-Account-CVE-Targeting). Aktuell privates Repo — OK. Bei Open-Source: in Env-Var auslagern.

  7. INFO — Telegram-Token + GitHub-PAT in derselben Lambda-Role. Bei Lambda-Compromise (z.B. via maliciously crafted Bedrock-Response, theoretisch) kann der Angreifer beide Secrets ziehen. Inhaerent zur Architektur — Mitigation waere Permission-Split pro Funktion, aber bei Marvins Solo-Setup overkill. Acceptable.


Action-Items (priorisiert)

  1. Sofort (5 Min): npm audit fix ausfuehren, npm run synth validieren, committen. → Schliesst HIGH-CVE.
  2. Naechste Edit-Session (5 Min): dashboard-presignup/main.py:28 Fehlermeldung generisch machen. Bedrock-Region-Wildcard eu-*eu-central-1 in agent-construct.ts:184-187.
  3. Bei naechstem Commit am Dashboard-Stack: errorResponses 403/404 auf default-behavior beschraenken (per-behavior config).
  4. Dokumentation: in intern/capabilities/agents-platform.md Note dass GitHub-PAT fine-grained sein muss (single repo, contents:write + pull_requests:read).

Quellen / Methodik

  • Skill: SKILL Phasen 1–5 + 9
  • Tools: npm audit, git grep, git log -p, manuelle CDK-Code-Review
  • Out-of-scope (nicht hier auditiert): Trust-Policies auf mgmt/mk-privat Accounts, MCP mcp-vf.agenticventures.de (eigener Audit), hostende Cloudflare-/Fargate-Setup.