Dateien nach "/" hochladen
This commit is contained in:
1 parent
929c3f8c95
commit
c8e055b7d9
2 files changed
+125
-1
No files matched your search
@@ -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 %}
|
||||
<div class="checkbox-row">
|
||||
<input type="checkbox" name="use_llm" id="use_llm">
|
||||
<label for="use_llm">LLM-Analyse aktivieren (sendet Dateiinhalte an den konfigurierten KI-Anbieter)</label>
|
||||
<label for="use_llm">Ausführlichen Bericht mit deutschen Erklärungen erstellen (nutzt KI über den konfigurierten Anbieter, sendet Dateiinhalte extern)</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit">Scan starten</button>
|
||||
@@ -315,6 +385,16 @@ a{color:var(--orange)}
|
||||
</div>
|
||||
|
||||
{% if result %}
|
||||
{% if summary_paragraphs %}
|
||||
<div class="card">
|
||||
<h3 class="sev-heading">Verständliche Zusammenfassung</h3>
|
||||
{% for p in summary_paragraphs %}<p>{{ p }}</p>{% endfor %}
|
||||
</div>
|
||||
{% elif summary_failed %}
|
||||
<div class="card">
|
||||
<p class="notice">Die deutsche Zusammenfassung konnte nicht erstellt werden (KI-Anbieter nicht erreichbar oder Fehler). Der technische Bericht unten ist trotzdem vollständig.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card">
|
||||
<div class="score-row">
|
||||
<span class="score-num">{{ result.risk_assessment.score }}/100</span>
|
||||
@@ -446,6 +526,13 @@ def index():
|
||||
if report_path.exists():
|
||||
result = json.loads(report_path.read_text())
|
||||
raw_issues = result.get("issues") or result.get("findings") or []
|
||||
|
||||
summary_text = None
|
||||
summary_failed = False
|
||||
if use_llm:
|
||||
summary_text = generate_report_summary(result)
|
||||
summary_failed = summary_text is None
|
||||
|
||||
return render_template_string(
|
||||
PAGE,
|
||||
allow_llm=ALLOW_LLM,
|
||||
@@ -453,6 +540,8 @@ def index():
|
||||
issues_by_severity=normalize_issues(raw_issues),
|
||||
components=normalize_components(result.get("components", [])),
|
||||
severity_labels=SEVERITY_LABELS_DE,
|
||||
summary_paragraphs=[p for p in (summary_text or "").split("\n\n") if p.strip()],
|
||||
summary_failed=summary_failed,
|
||||
result_json=json.dumps(result, indent=2, ensure_ascii=False),
|
||||
error=None,
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user