feat(skills): add skill deletion endpoint
- Add DELETE /api/v1/skill/remove endpoint - Add validate_skill_name() for path traversal protection - Include path normalization and security checks - Prevent deletion of official skills (user skills only) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ac8782e1a7
commit
68a4578554
@ -50,6 +50,18 @@ def validate_bot_id(bot_id: str) -> str:
|
|||||||
return bot_id
|
return bot_id
|
||||||
|
|
||||||
|
|
||||||
|
def validate_skill_name(skill_name: str) -> str:
|
||||||
|
"""验证 skill_name 格式,防止路径遍历攻击"""
|
||||||
|
if not skill_name:
|
||||||
|
raise HTTPException(status_code=400, detail="skill_name 不能为空")
|
||||||
|
|
||||||
|
# 检查路径遍历字符
|
||||||
|
if '..' in skill_name or '/' in skill_name or '\\' in skill_name:
|
||||||
|
raise HTTPException(status_code=400, detail="skill_name 包含非法字符")
|
||||||
|
|
||||||
|
return skill_name
|
||||||
|
|
||||||
|
|
||||||
async def validate_upload_file_size(file: UploadFile) -> int:
|
async def validate_upload_file_size(file: UploadFile) -> int:
|
||||||
"""验证上传文件大小,返回实际文件大小"""
|
"""验证上传文件大小,返回实际文件大小"""
|
||||||
file_size = 0
|
file_size = 0
|
||||||
@ -411,3 +423,70 @@ async def upload_skill(file: UploadFile = File(...), bot_id: Optional[str] = For
|
|||||||
logger.error(f"Error uploading skill file: {str(e)}")
|
logger.error(f"Error uploading skill file: {str(e)}")
|
||||||
# 不暴露详细错误信息给客户端(安全考虑)
|
# 不暴露详细错误信息给客户端(安全考虑)
|
||||||
raise HTTPException(status_code=500, detail="Skill文件上传失败")
|
raise HTTPException(status_code=500, detail="Skill文件上传失败")
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api/v1/skill/remove")
|
||||||
|
async def remove_skill(
|
||||||
|
bot_id: str = Query(..., description="Bot ID"),
|
||||||
|
skill_name: str = Query(..., description="Skill name to remove")
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
删除用户上传的 skill
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot_id: Bot ID
|
||||||
|
skill_name: 要删除的 skill 名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 删除结果
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- 只能删除用户上传的 skills,不能删除官方 skills
|
||||||
|
- 删除路径: projects/uploads/{bot_id}/skills/{skill_name}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 验证参数(防止路径遍历攻击)
|
||||||
|
bot_id = validate_bot_id(bot_id)
|
||||||
|
skill_name = validate_skill_name(skill_name)
|
||||||
|
|
||||||
|
logger.info(f"Skill remove - bot_id: {bot_id}, skill_name: {skill_name}")
|
||||||
|
|
||||||
|
# 构建删除目录路径
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
skill_dir = os.path.join(base_dir, "projects", "uploads", bot_id, "skills", skill_name)
|
||||||
|
|
||||||
|
# 规范化路径并确保在允许的目录内
|
||||||
|
skill_dir_real = os.path.realpath(skill_dir)
|
||||||
|
allowed_base = os.path.realpath(os.path.join(base_dir, "projects", "uploads", bot_id, "skills"))
|
||||||
|
|
||||||
|
if not skill_dir_real.startswith(allowed_base + os.sep):
|
||||||
|
raise HTTPException(status_code=403, detail="非法的删除路径")
|
||||||
|
|
||||||
|
# 检查目录是否存在
|
||||||
|
if not os.path.exists(skill_dir_real):
|
||||||
|
raise HTTPException(status_code=404, detail="Skill 不存在")
|
||||||
|
|
||||||
|
# 检查是否为目录
|
||||||
|
if not os.path.isdir(skill_dir_real):
|
||||||
|
raise HTTPException(status_code=400, detail="目标路径不是目录")
|
||||||
|
|
||||||
|
# 使用线程池删除目录(避免阻塞事件循环)
|
||||||
|
await asyncio.to_thread(shutil.rmtree, skill_dir_real)
|
||||||
|
|
||||||
|
logger.info(f"Successfully removed skill directory: {skill_dir_real}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Skill '{skill_name}' 删除成功",
|
||||||
|
"bot_id": bot_id,
|
||||||
|
"skill_name": skill_name
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
error_details = traceback.format_exc()
|
||||||
|
logger.error(f"Error removing skill: {str(e)}")
|
||||||
|
logger.error(f"Full traceback: {error_details}")
|
||||||
|
raise HTTPException(status_code=500, detail="删除 Skill 失败")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user