From 0f13a4f18dd19278eb0d3656a44ed2420967f960 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Wed, 15 Jul 2026 15:07:44 +0200 Subject: [PATCH] Skills-View: Text-Endpoint matcht jetzt Kategorie-Skills ({skill_id:path}) Kategorie-Skills (z. B. autonomous-ai-agents/hermes-agent) haben einen Schraegstrich in der ID. Die Route /api/eigenleben/skill/{skill_id} nahm nur EIN Pfadsegment -> Request fiel in den SPA-Fallback (index.html, Status 200), die UI zeigte "(Konnte den Skill-Text nicht laden.)". Live bewiesen: skill/wartung = JSON, skill/autonomous-ai-agents%2Fhermes-agent = HTML. Fix: :path-Konverter in der Route; dazu im Service "."/".."-Segmente explizit abweisen (das Zeichen-Set der bestehenden Validierung liesse ".." als Segment durch, bisher irrelevant, mit :path sauber dicht). Backend-only, kein dist-Rebuild noetig. Co-Authored-By: Claude Fable 5 --- backend/routers/eigenleben.py | 7 ++++++- backend/services/eigenleben.py | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/routers/eigenleben.py b/backend/routers/eigenleben.py index c9304cd..ed7efcb 100644 --- a/backend/routers/eigenleben.py +++ b/backend/routers/eigenleben.py @@ -12,7 +12,12 @@ def get_overview() -> dict: return eigenleben.overview() -@router.get("/eigenleben/skill/{skill_id}") +# :path — Kategorie-Skills haben einen Schrägstrich in der ID (autonomous-ai-agents/ +# hermes-agent). Als normales {skill_id}-Segment matcht die Route dann nicht, der Request +# fällt in den SPA-Fallback (index.html, Status 200) und die UI zeigt „Konnte den +# Skill-Text nicht laden". Die Pfad-Validierung (max. eine Ebene, keine . /..) macht +# services.eigenleben.skill_text. +@router.get("/eigenleben/skill/{skill_id:path}") def get_skill(skill_id: str) -> dict: text = eigenleben.skill_text(skill_id) if text is None: diff --git a/backend/services/eigenleben.py b/backend/services/eigenleben.py index 37da32f..a91a072 100644 --- a/backend/services/eigenleben.py +++ b/backend/services/eigenleben.py @@ -157,6 +157,10 @@ def skill_text(skill_id: str) -> str | None: """Voller SKILL.md-Text („wie funktioniert der Skill") — max. Kategorie/Name, keine Pfad-Tricks.""" if not re.fullmatch(r"[\w.-]+(/[\w.-]+)?", skill_id or ""): return None + # Das Zeichen-Set oben erlaubt „." und „.." als Segment — seit die Route ein + # :path-Parameter ist, hier explizit raus (kein Klettern über SKILLS_DIR hinaus). + if any(seg in (".", "..") for seg in skill_id.split("/")): + return None md = SKILLS_DIR / skill_id / "SKILL.md" if not md.is_file(): return None