qwen-code ACP Skill 管理模块深度解析:源码获取、原子安装与安全边界 📅 发布时间:2026/9/12 10:02:19 👁 浏览次数: qwen-code ACP Skill 管理模块深度解析源码获取、原子安装与安全边界【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读本文聚焦 qwen-code一个运行在终端中的开源 AI 编码代理中 ACPAgent Client ProtocolSkill 管理模块的设计与实现。该模块将 Skill 源码获取远程校验、GitHub 下载、归档解压与受管 Skill 变更安装、删除、启用/禁用从原先散布在会话与工作区控制代码中的逻辑收敛为两个职责清晰的独立模块。读完本文你将掌握 Skill 安装请求的完整处理链路、原子化安装与回滚机制、frontmatter 手术式编辑原理以及整套 SSRF / 解压炸弹 / 路径穿越防护在源码中的具体落点。背景为何需要独立的 Skill 管理模块在重构之前ACP agent 把远程 Skill 源码校验、GitHub 下载与归档解压、本地安装、删除与启用这些职责全部塞在会话和工作区控制的同一个文件里。Skill 逻辑本身是高内聚的但实现却被拆散在顶层 helper、私有 agent 方法和三个扩展方法分支中导致安装、删除、启用的代码路径分散难以整体审查安全边界源码获取逻辑与会话控制耦合任何一方的改动都可能互相影响测试难以针对 Skill 单一能力做聚焦覆盖。本次重构的目标是只做模块划分不做行为变更保留既有扩展方法接口、校验逻辑、文件系统安全约束、缓存刷新顺序、响应结构与错误语义同时不改变 Skill 发现与状态投影、不新增 scope 或 source host、不改变会话 Skill 刷新也不触碰任何 Web Shell、bridge 或 SDK 契约。设计文档 acp-skill-management-module.md 对此有明确表述。模块边界source 模块与 management 模块职责划分设计文档定义了清晰的 module seam模块接缝模块职责source 模块HTTPS 与 GitHub host 校验、重定向校验、下载大小限制、GitHub 目录遍历、归档回退、tar 解压主接口把一个 source URL 解析为要安装的 manifest 内容与文件列表management 模块请求校验、全局与项目 Skill 解析、frontmatter 启用/禁用编辑、原子化安装、受保护删除、缓存刷新ACP agent 仅做委托不解释请求载荷两者的文件系统与网络实现都是直接依赖没有引入新的适配层测试使用临时目录与 stub 的 fetch 响应。源码落点在仓库中这两个模块分别对应packages/cli/src/acp-integration/skill-source-download.ts下载与解压导出downloadSkill、extractFilesFromTarGz、fetchAllowedGitHubpackages/cli/src/acp-integration/skill-management.ts管理变更导出installManagedSkill、deleteManagedSkill、setManagedSkillEnabled。management 模块通过import { downloadSkill } from ./skill-source-download.js单向依赖 source 模块而 ACP agentacpAgent.ts只做三件事把qwen/skills/install、qwen/skills/delete、qwen/skills/setEnabled三个扩展方法路由到上述三个导出函数其中setEnabled还会透传客户端请求的 working directoryrequestedCwd用于项目级 Skill 解析。源码获取模块下载、校验与解压的完整链路HTTPS 与 host 白名单SSRF 防护第一道门downloadSkill的入口是assertAllowedSkillSourceUrlskill-source-download.ts协议强制 HTTPSSkill 内容可能包含可执行 hooks明文http:可被网络中间人篡改因此即使是白名单 host 也只接受https:host 白名单仅允许github.com、raw.githubusercontent.com、codeload.github.com、api.github.com四个 host。该限制防止客户端提供的sourceUrl驱动服务端去访问内网/回环/链路本地端点SSRF例如http://169.254.169.254/云元数据接口或http://localhost:port/。重定向逐跳校验GitHub 的 raw/codeload 下载通常会 302 到对象 CDN因此简单使用redirect: manual会导致真实下载失败而完全自动跟随又可能被恶意仓库弹到内网。fetchAllowedGitHubskill-source-download.ts的实现是手动逐跳跟随每次请求显式携带redirect: manual对 3xx 响应读取location用new URL(location, current)解析兼容相对路径校验下一跳协议为 HTTPS 且 host 位于允许集或允许后缀.githubusercontent.com、.github.com内超过 5 跳即报错。值得注意的是.github.io被有意排除——它是用户可控的 GitHub Pages 站点允许重定向过去会重新打开 SSRF/数据外泄面代码注释对此有专门说明skill-source-download.ts。下载大小双保险对响应体采取两层防护skill-source-download.tscontent-length头预检声明大小超过 100 MB 直接拒绝advisory服务端可省略该头readBodyWithLimit流式硬限制即使没有content-lengthchunked 传输、CDN 重定向也逐块累加实际字节数超过 100 MB 立即reader.cancel()并抛错防止arrayBuffer()把超大响应整体读入内存。GitHub 目录 Skill 的两种获取路径当sourceUrl指向 GitHub blob 路径且文件名为SKILL.md时downloadSkill走目录下载分支skill-source-download.tsContents API 路径优先递归调用downloadGitHubSkillDirectoryFromApi用https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref{ref}列出目录并逐个下载文件。递归受三大约束目录深度 ≤ 16、文件数 ≤ 2000、累计字节 ≤ 500 MB。API 返回的download_url是攻击者可影响的字段因此在下载前会再次经过assertAllowedSkillSourceUrl校验归档回退API 路径任何失败限流、非数组响应等都会记录 debug 日志并回退到downloadGitHubSkillDirectoryFromArchive即从https://codeload.github.com/{owner}/{repo}/tar.gz/{ref}下载整个仓库归档再按目标目录前缀提取。tar.gz 流式解压与解压炸弹防护extractFilesFromTarGzskill-source-download.ts是归档路径的核心压缩后字节上限 100 MB、解压后上限 500 MB解压用createGunzip()流式进行在自定义Writable中累加已膨胀字节一旦超过 500 MB 立即中止约 1000:1 的 gzip 压缩比下一个小归档可能在事后检查前膨胀到数 GB因此必须流式中止而不是先整体解压再检查tar 头解析完全自研readTarString/readTarSize/readTarPath只提取普通文件typeFlag0或\0剥离归档根目录前缀后按目标目录匹配SKILL.md 位于仓库根时按空前缀处理解压失败与超限会分别映射为明确的RequestError.invalidParams错误文本。单文件 URL非 GitHub blob 或非 SKILL.md则走downloadSingleSkillFile把 blob URL 转换为raw.githubusercontent.com直链后单文件下载。管理模块请求校验与三种变更操作扩展方法请求格式三个 ACP 扩展方法都接受{ skill: {...} }载荷也兼容把字段平铺在顶层。请求类型定义于 skill-management.ts扩展方法字段约束qwen/skills/installid、slug、name、description?、sourceUrl、scopescope仅支持globalqwen/skills/deleteslug、scopescope仅支持globalqwen/skills/setEnabledslug、enabled: boolean、scopescope支持global \| projectid缺省时取slugname缺省时也取slug。安装与删除保持全局-only启用/禁用保留全局与项目两级 scope——这与设计文档中“Preserved invariants”的约定完全一致。slug 校验字符白名单 路径穿越拦截slug 会被拼进globalQwenDir/skills下的文件系统路径因此validateSkillSlugskill-management.ts做了两层防护字符白名单/^[a-zA-Z0-9._-]$/从字符层面排除/与\path.sep显式拒绝.与..——注释指出即使白名单排除了路径分隔符.和..仍能通过path.join逃逸出 skills 目录例如 slug..会解析到全局配置目录因此必须单独拦截。此外还有一层纵深防御resolveManagedSkillDir用path.resolve后断言目标目录严格位于 skills 根之下resolveSkillInstallPath对归档内的相对路径同样做前缀断言防止../逃逸写出。rejectInstallArtifactSlug则拒绝形如foo.backup-1-2/foo.installing-12345-67890的保留后缀 slug——这些是原子换装流程的中间产物目录名会被 Skill loader 跳过安装它们会“报成功但永远加载不出来”。原子化安装staging 目录 可回滚 rename 换装installSkillFromUrlskill-management.ts的执行顺序是调用downloadSkill(request.sourceUrl)获取skillContent与文件列表用skillManager.parseSkillContent(skillContent, skillFile, user)解析 manifest断言解析出的name与请求slug完全一致防止换名攻击把全部文件先写入兄弟目录slug.installing-pid-timestamp可回滚换装先把旧目录 rename 为slug.backup-pid-timestamp再把 staging 目录 rename 为正式目录rename 失败时把 backup 恢复回去最后清理 staging成功后再删除 backup。旧目录不存在ENOENT则跳过备份步骤换装成功后调用skillManager.refreshCache()刷新缓存——只有 swap 成功后才刷新。这种“先 staging、后换装”的设计保证中途写盘失败磁盘满、权限错误不会留下半成品旧版本 Skill 保持完好rename 式换装还能顺带丢弃旧版本遗留的孤儿文件等价于此前 rm 式方案的清理效果。对应测试 skill-management.test.ts 覆盖了“重装后无 backup 残留”与“swap rename 失败EPERM时旧内容完整恢复”两个关键场景。安装成功响应{ id: pptx-id, slug: pptx, installed: true, installedPath: globalQwenDir/skills/pptx/SKILL.md, sourceUrl: https://github.com/... }受保护删除绝不删根目录或全局目录deleteGlobalSkillskill-management.ts在递归删除前做了严格断言解析出的 Skill 目录必须直接包含刚校验过、name 匹配 slug 的SKILL.mdskillFile path.join(skillDir, SKILL.md)目录不得等于文件系统根path.parse(dir).root目录不得等于全局 Qwen 目录Storage.getGlobalQwenDir()。代码注释明确解释了原因readManagedSkillFile的通用回退分支可能从listSkills()解析出任意路径只有“直接包含已验证SKILL.md的专用目录”才是安全的删除目标从而避免畸形 Skill 条目触发对共享/父目录的破坏性rm。删除成功后同样刷新缓存。frontmatter 手术式编辑只动一个字段setGlobalSkillEnabled的核心是setSkillFrontmatterEnabledskill-management.ts它不做 YAML 解析/序列化回写而是对原始文本按行处理剥离 BOM、统一\r\n为\n用正则/^---\n([\s\S]*?)\n---(?:\n|$)([\s\S]*)$/切分 frontmatter 与正文在 frontmatter 行中定位^disable-model-invocation\s*:启用enabled: true时删除该行禁用时若存在则改写为disable-model-invocation: true否则在尾部跳过末尾空行插入该行重新拼装---\n...\n---\n正文写回。选择文本手术而非 YAML round-trip 的原因在代码注释中写得很清楚最小化核心 YAML 序列化器会丢弃注释、压平嵌套结构例如hooks:把带 hooks 的 Skill 重新序列化会直接损坏它们还会剥掉用户注释而按原始文本编辑可以让其他每一个字节保持不变。写回前还有一道纵深防御path.basename(skillFile) ! SKILL.md时拒绝写入确保只写回刚读到的、name 匹配的 manifest 文件。测试 skill-management.test.ts 验证了带# keep this comment注释与hooks: PreToolUse嵌套结构的 Skill 在切换启用状态后注释与 hooks 原样保留。项目级 Skill 解析setManagedSkillEnabled接受第三个参数cwd。readManagedSkillFile的解析顺序是全局直接读globalQwenDir/skills/slug/SKILL.md项目scopeproject 且 cwd 有效依次探测.qwen/skills与.agents/skills两个项目目录PROJECT_SKILL_DIRS用loadSkillsFromDir(baseDir, project)查找匹配 slug 的 Skill——这一分支尊重请求的 working directory而不是当前进程 cwd通用回退listSkills({ level })按 name 查找也是删除路径的解析来源。测试 skill-management.test.ts 验证了从请求的tempProject/.qwen/skills解析项目 Skill且此时listSkills不会被调用。与 ACP agent 的集成路由委托在 acpAgent.ts 中三个扩展方法被直接委托给管理模块case qwen/skills/install: { return installManagedSkill(this.config, params); } case qwen/skills/delete: { return deleteManagedSkill(this.config, params); } case qwen/skills/setEnabled: { return setManagedSkillEnabled(this.config, params, requestedCwd); }agent 完全不解释载荷语义符合设计文档“The ACP agent delegates install, delete, and set-enabled requests without interpreting their payloads”的约定。ACP 集成测试 acpAgent.test.ts 从 agent 入口验证了qwen/skills/install拒绝http://、非 GitHub host 与伪造后缀 hostgithub.com.attacker.com三类恶意 source URL同文件 20112 行起的用例则完整走通“GitHub 目录 Skill 经 ACP 安装”的端到端流程Contents API 列目录 → raw 下载 → 落盘。安全边界总结综合两个模块整套防护构成纵深防御体系威胁防护源码位置SSRF内网/元数据端点HTTPS-only host 白名单 重定向逐跳校验skill-source-download.ts重定向劫持.github.io排除、跳数上限 5skill-source-download.ts超大下载 / 解压炸弹压缩 100 MB / 解压 500 MB流式中止skill-source-download.ts目录遍历 / slug 逃逸字符白名单 ./..拦截 resolve 前缀断言skill-management.ts换名攻击解析 name 必须等于请求 slugskill-management.ts破坏性删除仅删含已验证 SKILL.md 的专用目录禁删根/全局目录skill-management.ts中途写坏staging 目录 可回滚 rename 换装 成功后刷新缓存skill-management.tsfrontmatter 损坏文本手术只改一个字段保留注释与嵌套结构skill-management.ts验证与测试source 模块测试skill-source-download.test.ts覆盖 tar.gz 正常提取、压缩/解压超限拒绝、解压失败、无重定向直返、允许 host 的 CDN 重定向、跳数超限、相对 Location 解析、目录 Skill 全量下载。大小上限以参数注入的方式供测试用小阈值触发无需真的分配 100 MB/500 MBmanagement 模块测试skill-management.test.ts覆盖安装落盘与缓存刷新、启用/禁用/删除全局 Skill、frontmatter 注释与 hooks 保留、用户/项目 Skill 通过既有 manager 回退解析、working directory 解析、backup 清理、rename 失败回滚、遍历 slug 与保留后缀 slug 拒绝ACP 集成测试acpAgent.test.ts从扩展方法入口验证恶意 URL 拒绝与目录 Skill 安装的端到端行为。设计文档确认这些聚焦测试覆盖了 source/archive 安全、全局与项目变更、frontmatter 保留、路由委托与既有 ACP 集成行为CLI 包测试随仓库构建与 typecheck 一起运行。延伸阅读设计文档docs/design/acp-skill-management-module.md管理模块实现packages/cli/src/acp-integration/skill-management.ts源码获取实现packages/cli/src/acp-integration/skill-source-download.ts路由委托packages/cli/src/acp-integration/acpAgent.ts核心 Skill 加载与解析packages/core/src/skills/skill-load.ts、packages/core/src/skills/skill-manager.ts【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考