From 037872924b3a2c47e6eb275172abcaa085d5fa98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:19:33 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E9=81=8D=E5=8E=86=E5=AE=89=E5=85=A8=E6=BC=8F=E6=B4=9E=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E5=90=8D=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 前端:在 ExtensionsView.vue 上传前添加 beforeUpload 校验, 仅允许 .zip 或 SKILL.md 文件,其他 .md 文件给出明确错误提示 - 后端:import_skill_dir 添加路径限制检查(is_relative_to), 确保来源目录在系统临时目录内,防止路径遍历 - 后端:install_remote_skill 改为扫描目录查找安装结果, 避免直接用用户输入构造文件路径(消除 CodeQL 告警) - 后端:_run_skills_cli 错误输出清洗 ANSI 控制字符并截断到 500 字符 - 后端:install_remote_skill 返回类型从 object 修正为 Skill Agent-Logs-Url: https://github.com/xerrors/Yuxi/sessions/0145c647-72bc-41f9-b07b-04d4acf51932 Co-authored-by: xerrors <35524243+xerrors@users.noreply.github.com> --- .../services/remote_skill_install_service.py | 23 +++++++++++++++---- .../package/yuxi/services/skill_service.py | 4 ++++ web/src/views/ExtensionsView.vue | 12 ++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/backend/package/yuxi/services/remote_skill_install_service.py b/backend/package/yuxi/services/remote_skill_install_service.py index eb22f0a7..b7495882 100644 --- a/backend/package/yuxi/services/remote_skill_install_service.py +++ b/backend/package/yuxi/services/remote_skill_install_service.py @@ -6,10 +6,14 @@ import re import shutil import tempfile from pathlib import Path +from typing import TYPE_CHECKING from sqlalchemy.ext.asyncio import AsyncSession from yuxi.services.skill_service import import_skill_dir, is_valid_skill_slug +if TYPE_CHECKING: + from yuxi.storage.postgres.models_business import Skill + ANSI_ESCAPE_RE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]") CONTROL_SEQUENCE_RE = re.compile(r"\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[\(\)][A-Za-z0-9]") CLI_TIMEOUT_SECONDS = 300 @@ -111,7 +115,9 @@ async def _run_skills_cli( error_output = (stderr or b"").decode("utf-8", errors="replace") combined = "\n".join(part for part in [output.strip(), error_output.strip()] if part) if process.returncode != 0: - raise ValueError(combined or "skills CLI 执行失败") + cleaned_lines = _clean_cli_output(combined) + error_msg = "\n".join(line for line in cleaned_lines if line)[:500] + raise ValueError(error_msg or "skills CLI 执行失败") return combined @@ -149,7 +155,7 @@ async def install_remote_skill( source: str, skill: str, created_by: str | None, -) -> object: +) -> Skill: normalized_source = _normalize_source(source) normalized_skill = _normalize_skill_name(skill) @@ -183,8 +189,17 @@ async def install_remote_skill( cwd=workdir, ) - installed_dir = Path(temp_home) / ".agents" / "skills" / normalized_skill - if not installed_dir.exists() or not installed_dir.is_dir(): + base_dir = Path(temp_home).resolve() + skills_dir = base_dir / ".agents" / "skills" + # Scan for the installed skill directory rather than constructing the path + # from user input, to avoid path traversal concerns + installed_dir = None + if skills_dir.is_dir(): + for candidate in skills_dir.iterdir(): + if candidate.name == normalized_skill and candidate.is_dir(): + installed_dir = candidate + break + if installed_dir is None: raise ValueError("skills CLI 未生成预期的技能目录") return await import_skill_dir( diff --git a/backend/package/yuxi/services/skill_service.py b/backend/package/yuxi/services/skill_service.py index fd57dc85..05ea66ac 100644 --- a/backend/package/yuxi/services/skill_service.py +++ b/backend/package/yuxi/services/skill_service.py @@ -594,6 +594,10 @@ async def import_skill_dir( created_by: str | None, ) -> Skill: source_skill_dir = Path(source_dir).resolve() + # Confine to the system temp directory to prevent path traversal + tmp_root = Path(tempfile.gettempdir()).resolve() + if not source_skill_dir.is_relative_to(tmp_root): + raise ValueError("技能目录路径不合法") if not source_skill_dir.exists() or not source_skill_dir.is_dir(): raise ValueError("技能目录不存在") return await _import_skill_dir_impl( diff --git a/web/src/views/ExtensionsView.vue b/web/src/views/ExtensionsView.vue index 84db1a74..d59f93ea 100644 --- a/web/src/views/ExtensionsView.vue +++ b/web/src/views/ExtensionsView.vue @@ -22,6 +22,7 @@ accept=".zip,.md" :show-upload-list="false" :custom-request="handleImportUpload" + :before-upload="beforeSkillUpload" :disabled="skillsLoading || skillsImporting" > @@ -98,6 +99,7 @@