diff --git a/backend/open_webui/routers/skills.py b/backend/open_webui/routers/skills.py index 7d2daabf8e..9c268d8dcc 100644 --- a/backend/open_webui/routers/skills.py +++ b/backend/open_webui/routers/skills.py @@ -1,4 +1,5 @@ import logging +import re from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Request, status @@ -180,6 +181,13 @@ async def create_new_skill( form_data.id = form_data.id.lower().replace(' ', '-') + # The id goes into /id/{id}/... paths, so anything outside the slug charset is unreachable once stored. + if not re.fullmatch(r'[a-z0-9_-]+', form_data.id): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.DEFAULT('Invalid skill ID'), + ) + existing = await Skills.get_skill_by_id(form_data.id, db=db) if existing is not None: raise HTTPException( diff --git a/src/lib/components/workspace/Skills.svelte b/src/lib/components/workspace/Skills.svelte index fdc6f2a465..1395b56350 100644 --- a/src/lib/components/workspace/Skills.svelte +++ b/src/lib/components/workspace/Skills.svelte @@ -21,7 +21,7 @@ deleteSkillById, toggleSkillById } from '$lib/apis/skills'; - import { capitalizeFirstLetter, parseFrontmatter, formatSkillName } from '$lib/utils'; + import { capitalizeFirstLetter, parseFrontmatter, formatSkillName, slugify } from '$lib/utils'; import TagInput from '$lib/components/common/Tags/TagInput.svelte'; import Tooltip from '../common/Tooltip.svelte'; @@ -306,7 +306,7 @@ const displayName = formatSkillName(rawName); sessionStorage.skill = JSON.stringify({ name: displayName, - id: fm.name || '', + id: slugify(rawName), description: fm.description || '', content: mdContent, is_active: true, diff --git a/src/lib/components/workspace/Skills/SkillEditor.svelte b/src/lib/components/workspace/Skills/SkillEditor.svelte index 894107bc19..06495ac2f5 100644 --- a/src/lib/components/workspace/Skills/SkillEditor.svelte +++ b/src/lib/components/workspace/Skills/SkillEditor.svelte @@ -39,7 +39,6 @@ const fm = parseFrontmatter(content); if (fm.name && !name) { name = formatSkillName(fm.name); - id = fm.name; } if (fm.description && !description) { description = fm.description; @@ -52,6 +51,7 @@ return; } loading = true; + if (!edit) id = slugify(id); await onSubmit({ id,