diff --git a/app.py b/app.py index 7eb6611..9f22017 100644 --- a/app.py +++ b/app.py @@ -24,6 +24,7 @@ import subprocess import tempfile import threading import time +import urllib.request from functools import wraps from pathlib import Path from urllib.parse import urlparse @@ -48,6 +49,12 @@ LLM_KEY_PRESENT = bool(os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHR ENABLE_LLM_OPTION = os.environ.get("ENABLE_LLM_OPTION", "false").lower() == "true" ALLOW_LLM = LLM_KEY_PRESENT and ENABLE_LLM_OPTION +# Für die deutsche Klartext-Zusammenfassung wird direkt die OpenRouter-kompatible +# Chat-API angesprochen (unabhängig von SkillSpectors eigener --llm-Option). +OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") +OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://openrouter.ai/api/v1") +SUMMARY_MODEL = os.environ.get("SKILLSPECTOR_MODEL") or "openai/gpt-4o-mini" + GIT_URL_RE = re.compile(r"^https?://[^\s]+$") _scan_semaphore = threading.Semaphore(MAX_CONCURRENT_SCANS) @@ -200,6 +207,69 @@ def normalize_components(raw_components: list) -> list: return normalized +# --------------------------------------------------------------------------- # +# Deutsche Klartext-Zusammenfassung des Reports per LLM (OpenRouter-kompatibel) +# --------------------------------------------------------------------------- # +def generate_report_summary(result: dict) -> str | None: + if not OPENAI_API_KEY: + return None + + risk = result.get("risk_assessment") or {} + issues = result.get("issues") or result.get("findings") or [] + compact = { + "score": risk.get("score"), + "severity": risk.get("severity"), + "recommendation": risk.get("recommendation"), + "total_issues": len(issues), + "top_issues": [ + { + "severity": i.get("severity"), + "category": i.get("category") or i.get("type"), + "message": i.get("finding") or i.get("message") or i.get("description"), + "file": (i.get("location") or {}).get("file") or (i.get("location") or {}).get("path"), + } + for i in issues[:20] + ], + } + + prompt = ( + "Du bekommst das JSON-Ergebnis eines automatisierten Sicherheits-Scans für einen " + "AI-Agent-Skill. Schreibe eine kurze, laienverständliche Zusammenfassung auf Deutsch " + "für jemanden ohne Security-Hintergrund, als Fließtext in 2-3 kurzen Absätzen " + "(keine Überschriften, keine Code-Blöcke, keine Aufzählungszeichen):\n" + "Absatz 1: Gesamteinschätzung in 1-2 Sätzen – wie riskant ist es grob?\n" + "Absatz 2: Die wichtigsten Funde in einfachen Worten, ohne unerklärte Fachbegriffe.\n" + "Absatz 3: Konkrete Handlungsempfehlung (bedenkenlos nutzbar / mit Vorsicht " + "nutzbar / besser nicht installieren) und eine kurze Begründung warum.\n\n" + f"Scan-Ergebnis:\n{json.dumps(compact, ensure_ascii=False)}" + ) + + payload = json.dumps( + { + "model": SUMMARY_MODEL, + "messages": [{"role": "user", "content": prompt}], + "temperature": 0.3, + "max_tokens": 700, + } + ).encode("utf-8") + + req = urllib.request.Request( + f"{OPENAI_BASE_URL.rstrip('/')}/chat/completions", + data=payload, + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {OPENAI_API_KEY}", + }, + method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=60) as resp: + data = json.loads(resp.read().decode("utf-8")) + return data["choices"][0]["message"]["content"].strip() + except Exception: + return None + + # --------------------------------------------------------------------------- # # Design im Stil von software.obraasch.de: dunkler Header, orange Akzente, # helle Cards. @@ -307,7 +377,7 @@ a{color:var(--orange)} {% if allow_llm %}
{{ p }}
{% endfor %} +Die deutsche Zusammenfassung konnte nicht erstellt werden (KI-Anbieter nicht erreichbar oder Fehler). Der technische Bericht unten ist trotzdem vollständig.
+