diff --git a/.codebuddy/.DS_Store b/.codebuddy/.DS_Store new file mode 100644 index 00000000..7027b302 Binary files /dev/null and b/.codebuddy/.DS_Store differ diff --git a/.codebuddy/artifacts/release-note-v2.17.0.md b/.codebuddy/artifacts/release-note-v2.17.0.md new file mode 100644 index 00000000..5ede0f06 --- /dev/null +++ b/.codebuddy/artifacts/release-note-v2.17.0.md @@ -0,0 +1,34 @@ +# CloudBase MCP v2.17.0 + +## 🎉 新功能 + +### 文档与知识检索 +- `searchKnowledgeBase` 新增 CloudBase 官方文档 `docs` 模式,并把官方文档检索整合进统一入口,检索路径更直接。 +- 文档检索能力与技能说明进一步收敛,减少在固定技能文档、OpenAPI 与官方文档之间来回切换的成本。 + +### 应用认证与权限能力 +- 应用认证工具补齐了 publishable key、API key、自定义登录密钥、provider / client 配置等能力,方便在 AI IDE 中直接完成认证准备工作。 +- 扩展了 manager-node 5.1 相关的认证与工作流支持,应用认证、权限与环境配置链路更完整。 + +### 技能与配置体系 +- 新增和整理了一批对外 skills 与配置目录,提升多 IDE / 多场景下的能力发现与复用体验。 +- 增强了对外规则与 skill 的来源回退与发布一致性,降低规则缺失或链接失效带来的使用摩擦。 + +## 🐛 问题修复 + +- 修复 `searchKnowledgeBase` 在官方文档 `docs` 模式下的登录限制,未登录场景也能正常检索文档。 +- 修复环境地域与静态域名相关回退逻辑,减少环境查询和托管配置场景下的误判。 +- 修复应用认证工具在 action 对齐、ensure 流程与返回提示上的多个问题,降低认证配置出错率。 +- 修复 NoSQL 安全规则说明与资源类型边界,避免把数据库规则误用于函数或存储。 +- 修复技能发布中的 ClawHub slug、产物路径与版本同步问题,提升对外发布稳定性。 + +## 📚 文档更新 + +- 补充函数与云存储安全规则引用,帮助在配置权限时更快找到正确文档。 +- 清理公开文档链接与部分重复的 CloudBase 指南内容,减少阅读噪音。 +- 更新 MCP 工具说明和生成文档,让工具描述与当前实现保持一致。 + +## 🔧 维护与工程改进(可选阅读) + +- 持续收敛 issue auto-processor、skills mirror、all-in-one 版本同步等维护链路。 +- 优化部分发布、校验与自动化流程,为后续版本发布与规则同步提供更稳定的基础。 diff --git a/.codebuddy/worktrees/.DS_Store b/.codebuddy/worktrees/.DS_Store new file mode 100644 index 00000000..8d21db50 Binary files /dev/null and b/.codebuddy/worktrees/.DS_Store differ diff --git a/.workbuddy/memory/2026-05-13.md b/.workbuddy/memory/2026-05-13.md new file mode 100644 index 00000000..0a811f49 --- /dev/null +++ b/.workbuddy/memory/2026-05-13.md @@ -0,0 +1,12 @@ +# 2026-05-13 + +- 分析 CloudBase-MCP `downloadRemoteFile` SSRF 报告:确认 `mcp/src/tools/download.ts` 先 `http.get/https.get` 再做 `isUrlAndContentTypeSafe` 校验,属于真实的 pre-connect 校验顺序缺陷。 +- 本地复现实锤:对 `127.0.0.1:18090` 调用时本地监听端收到真实 GET 请求,但工具返回“目标为内网地址”;对关闭端口 `127.0.0.1:18091` 返回 `ECONNREFUSED`,形成 blind SSRF / 端口探测原语。 +- 结论建议:不要用“本地 npx 启动/云模式可关闭工具”否定漏洞成立;更准确的表述是本地 MCP 宿主侧 SSRF,影响边界低于云端服务端 SSRF,但仍应修复并在工单里强调前提与影响边界。 +- 补充输出:为用户起草了面向 TSRC 的回复口径,主张不否认技术缺陷成立,但强调其为本地宿主侧风险、利用前提较高、增量攻击面有限,不应按高危云端 SSRF 定级。 +- 继续收敛表述:建议用户在工单中只陈述“校验时机不够前置、请求可能先于拦截发生、影响限于本地宿主侧”三点,并配合生活化类比说明“先放行后拦截”与“本地宿主侧≠云端暴露面”的区别。 +- 从利用方视角补充分析:该问题更像低能力、盲打型本地宿主侧网络触达原语,攻击收益受限于触发链、工具权限、仅 GET 下载语义、结果不可直接回显等条件;适合用“增量能力有限、更多是便利化而非决定性突破”来压低定级。 +- 继续为用户润色正式工单口径:重点强化“本地开发/调试场景、攻击面在本地宿主网络而非云端公网服务、利用需满足工具启用与触发链前提、收益主要是有限探测能力”四个点,避免主动下重攻击定性。 +- 针对“利用面”表述继续细化:建议把利用拆成前提、能力、收益、边界四段写清,避免只写“有限”但不解释为何有限。 +- 新增表述建议:用户希望突出“Agent/MCP 主要运行在本地,且在具备 shell/代码执行能力的使用场景下,其利用价值与 AI 直接诱导发起 curl/自定义请求接近,因此该工具带来的新增攻击能力有限”这一论点。 +- 用户进一步希望直接采用“不是 SSRF”口径;建议改写为“本问题不认定为典型 SSRF 类漏洞,更接近本地 Agent/MCP 工具的校验时机问题”,以降低被抓绝对表述漏洞的风险。 diff --git a/.workbuddy/memory/2026-05-14.md b/.workbuddy/memory/2026-05-14.md new file mode 100644 index 00000000..4346f4d1 --- /dev/null +++ b/.workbuddy/memory/2026-05-14.md @@ -0,0 +1,23 @@ +# 2026-05-14 + +- 继续分析 CloudBase-MCP `downloadRemoteFile` SSRF 风险修复方案:建议将当前 `isUrlAndContentTypeSafe` 拆为“请求前 URL 安全校验”和“响应后 Content-Type 校验”两段,避免先发请求再拦截。 +- 建议修复点:在 `mcp/src/tools/download.ts` 中于 `http.get/https.get` 之前执行 URL 预检;重定向使用 `new URL(location, currentUrl)` 规范化后逐跳重新校验;域名解析建议改为覆盖 A/AAAA 与系统解析语义的方式;补充“127.0.0.1 开放/关闭端口均应在请求前被拦截”的回归测试。 +- 用户新增偏好:不希望后续再出现同类安全工单,倾向一次性把“点修复”升级为“通用防复发设计”,后续需明确是仅治理 downloadRemoteFile 还是顺带抽象统一安全校验并排查同类出网点。 +- 已完成通用治理落地:新增 `mcp/src/utils/remote-url-safety.ts` 统一做请求前 URL / DNS / 内网地址校验(含 IPv6 mapped IPv4 归一化与 pinned lookup),并让 `mcp/src/tools/download.ts`、`mcp/src/tools/setup.ts` 在发请求前接入;同时新增 `mcp/src/tools/download.test.ts` 回归测试,验证 localhost 开放/关闭端口都会在建连前被拦截。 +- 已验证:`NODE_OPTIONS="--experimental-default-type=module" npx vitest run src/tools/download.test.ts` 通过;`npm run build` 通过;基于构建产物复核 `downloadRemoteFile` 对 `127.0.0.1:18090` 不再发请求(hitCount=0),对关闭端口不再暴露 `ECONNREFUSED`。 +- 继续补齐 SSRF 防复发收尾:新增 `mcp/src/tools/setup.test.ts` 验证 `downloadTemplate` 能正确处理 307 重定向并复用请求前 URL 安全校验;同步将 `mcp/src/tools/setup.ts` 的重定向逻辑从仅 301/302 扩展为统一处理完整 3xx。 +- 继续补强 `mcp/src/tools/download.test.ts`:新增 `::ffff:127.0.0.1` IPv6-mapped localhost 拒绝用例,并将公网成功/重定向用例的 DNS mock 改成兼容 `dns.promises.lookup(..., { all: true })` 的实现,避免仅测试通过但构建阶段 TS 类型失败。 +- 本轮最终验证:`NODE_OPTIONS="--experimental-default-type=module" npx vitest run src/tools/download.test.ts src/tools/setup.test.ts` 通过;`npm run build` 再次通过;最终 review 结论为“未发现阻塞问题”。 +- 补充全量验证结论:直接跑 `npm test` 仍会受本机 `postcss.config.js` 的 ESM 加载问题影响;改用 `NODE_OPTIONS="--experimental-default-type=module" npm test` 后可越过该问题,但全量套件仍存在与本次改动无关的既有失败(`src/server.test.ts` 等因 `@cloudbase/manager-node` / `tough-cookie` / `psl.mjs` 的 ESM 兼容链路报 `ERR_REQUIRE_ESM`),因此当前适合按“目标测试 + 构建通过”作为本次变更验收依据。 +- 后续继续收尾全量测试问题时,重新定位到真正根因不是业务代码,而是测试环境解析条件与宿主配置泄漏:新增 `mcp/postcss.config.cjs` 阻断 Vitest/Vite 向上读取用户主目录 `postcss.config.js`;同时把 `mcp/vitest.config.js` 的 `resolve.conditions` 调整为仅 `node + require`,避免 transitive CJS require 在测试环境里误走 `import/module` 分支触发 `psl`、`uuid`、`is-promise` 等 ESM 入口。 +- 同步修复 `mcp/src/server.test.ts`:对 `@modelcontextprotocol/sdk/types.js` 改为 partial mock,保留真实导出并仅覆盖 `SetLevelRequestSchema`,兼容 SDK 新增 `CancelledNotificationSchema` 等导出,避免 mock 过窄导致 plugin registration 用例失败。 +- 另做一处小修正:`mcp/src/tools/download.ts` 中 content-type 白名单拒绝错误改为仅返回 `不允许的内容类型`,不再把 URL 安全错误文案混进内容类型拦截结果。 +- 本轮最终验证:`cd mcp && npm test` 在默认模式下已通过,结果为 `64 passed | 2 skipped`、`530 passed | 20 skipped`;关键回归集 `src/server.test.ts`、`../tests/esm-import.test.js`、`src/tools/download.test.ts`、`src/tools/setup.test.ts` 也全部通过。 +- 已补写一份可续接开发的会话摘要,聚焦 SSRF 防护治理、Vitest/ESM 兼容修复、PostCSS 宿主配置泄漏隔离、`server.test` partial mock 调整,以及最终验证结论,供后续会话直接承接而无需重跑既有任务。 +- 已按用户指定 XML 格式完成会话历史摘要,覆盖原始用户请求、关键文件/代码片段、报错与修复链路、最终测试结果,以及“当前可见上下文未展示最初技术任务原文”的边界说明。 +- 已在分支 `feature/ssrf-download-governance` 完成提交与 PR 交付:先提交 `db4b99aa`(SSRF + Vitest 修复主提交),创建 PR #709;随后根据 `github-code-quality[bot]` 评论删除 `mcp/src/tools/setup.test.ts` 中未使用的 `https` import,再提交 `e35755ca` 并更新同一 PR。 +- 交付前后均做了 fresh verification:`cd mcp && npm test` 两次均通过,结果保持 `64 passed | 2 skipped`、`530 passed | 20 skipped`;PR #709 最终状态为 `mergeStateStatus=CLEAN`,CodeQL / build-and-publish / sync / Continuous Releases 全部成功,无新的 issue 评论或未处理 review 反馈。 +- 后续修正 `skills/git-workflows/references/source-commands.md` 中 `version_publish_main` 的过时说明:不再要求交互式执行 `npx bumpp`,改为显式使用 Node 22 的非交互命令 `npx bumpp --release --yes --no-commit --no-tag --no-push --ignore-scripts`;同时记录了当前工作区默认 Node 18 可能导致 bumpp 10 直接报 `styleText` 相关错误,需强制使用 Node 22+。 +- 已按用户要求收尾提交本轮改动:纳入 `skills/git-workflows/references/source-commands.md` 的 bumpp 非交互发布说明,以及 2026-05-13 / 2026-05-14 两份工作记忆日志。 +- 最终提交链路为两步:先提交 `870efa38 docs(git-workflows): document non-interactive bumpp release`;随后因 pre-commit build 自动刷新 `doc/mcp-tools.md` 与 `scripts/tools.json`,补提 `1f908eac chore(build): refresh generated tool docs`,最终 `git status --short` 已恢复干净。 +- 复盘后同步补强 `git-workflows` 技能的 `git_commit` 说明:要求每次提交后立刻检查 `git status --short`,若 hooks/build 生成新的跟踪文件改动,则继续补提 follow-up commit,直到工作区清空,避免“提交成功但仓库仍脏”的假完成状态。 diff --git a/.workbuddy/memory/2026-05-15.md b/.workbuddy/memory/2026-05-15.md new file mode 100644 index 00000000..86f8e173 --- /dev/null +++ b/.workbuddy/memory/2026-05-15.md @@ -0,0 +1,4 @@ +# 2026-05-15 + +- 继续就 CloudBase-MCP `downloadRemoteFile` SSRF 工单口径做分析:建议不要接受“服务端 SSRF / 任意内网文件下载”式放大表述,而应明确界定为本地 Agent / MCP 宿主侧的 URL 校验时机问题;利用面位于本地宿主网络,且在已具备本地 shell / 代码执行诱导能力的场景下,与诱导 AI 直接执行 curl / HTTP 请求相比新增攻击能力有限。 +- 结合安全工单模板进一步判断:当前更稳妥的处理策略不是争“误报”,而是承认缺陷成立但强调其为本地宿主侧、影响收敛、增量攻击能力有限的实现问题;对外沟通重点应放在边界与定级收敛,而非否认漏洞存在。 diff --git a/doc/mcp-tools.md b/doc/mcp-tools.md index 68eedb17..b71265df 100644 --- a/doc/mcp-tools.md +++ b/doc/mcp-tools.md @@ -1486,12 +1486,12 @@ CloudBase 云函数统一写入口。支持创建函数、更新代码、更新 OpenAPI 文档 (openapi) 查询当前支持 7 个 API 文档,分别是: API名:mysqldb API介绍:关系型数据库 RESTful API (MySQL/PostgreSQL) - 云开发关系型数据库 HTTP API -API名:storage API介绍:Storage API - 云存储 HTTP API -API名:nosql API介绍:NoSQL RESTful API - 文档型数据库 HTTP API API名:functions API介绍:Cloud Functions API - 云函数 HTTP API -API名:ai_model API介绍:AI 大模型接入 API - 统一 AI 模型 HTTP API API名:auth API介绍:Authentication API - 身份认证 HTTP API API名:cloudrun API介绍:CloudRun API - 云托管服务 HTTP API +API名:storage API介绍:Storage API - 云存储 HTTP API +API名:nosql API介绍:NoSQL RESTful API - 文档型数据库 HTTP API +API名:ai_model API介绍:AI 大模型接入 API - 统一 AI 模型 HTTP API #### 参数 @@ -1511,7 +1511,7 @@ API名:cloudrun API介绍:CloudRun API - 云托管服务 HTTP API { name: "apiName", type: "string", - description: `mode=openapi 时指定。API 名称。 可填写的值: "mysqldb", "storage", "nosql", "functions", "ai_model", "auth", "cloudrun"`, + description: `mode=openapi 时指定。API 名称。 可填写的值: "mysqldb", "functions", "auth", "cloudrun", "storage", "nosql", "ai_model"`, }, { name: "action", @@ -2053,7 +2053,7 @@ CloudBase 应用侧认证配置写入口。用于修改登录方式、provider --- ### `queryApps` -CloudApp 域统一只读入口。可先查应用/版本,再在重新部署后用 listAppVersions 或 getAppVersion 按 serviceName 验证是否生成了新版本与最新构建状态。 +查询 CloudBase 应用部署的应用和版本。可查应用列表/详情、版本列表/详情;部署后用 getAppVersion 按 buildId 轮询构建状态;getBuildLog 可查询构建日志用于诊断失败原因。 #### 参数 @@ -2063,12 +2063,12 @@ CloudApp 域统一只读入口。可先查应用/版本,再在重新部署后 name: "action", type: "string", required: true, - description: ` 可填写的值: "listApps", "getApp", "listAppVersions", "getAppVersion"`, + description: ` 可填写的值: "listApps", "getApp", "listAppVersions", "getAppVersion", "getBuildLog"`, }, { name: "serviceName", type: "string", - description: `CloudApp 服务名。getApp / listAppVersions / getAppVersion 时必填;重新部署后复用同一个 serviceName 查询版本历史。`, + description: `CloudBase 应用服务名。getApp / listAppVersions / getAppVersion / getBuildLog 时必填;重新部署后复用同一个 serviceName 查询版本历史。`, }, { name: "searchKey", @@ -2093,7 +2093,12 @@ CloudApp 域统一只读入口。可先查应用/版本,再在重新部署后 { name: "buildId", type: "string", - description: `构建 ID。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。`, + description: `构建 ID。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。getBuildLog 时必填。`, + }, + { + name: "start", + type: "number", + description: `构建日志偏移量,用于分页拉取后续日志。仅 action=getBuildLog 时使用,不传时从开头返回。`, } ]} /> @@ -2101,7 +2106,7 @@ CloudApp 域统一只读入口。可先查应用/版本,再在重新部署后 --- ### `manageApps` -CloudApp 域统一写入口。action=deployApp 会先 uploadCode 再 createApp;首次调用创建应用,后续复用同一个 serviceName 会直接触发重新部署并生成新版本,无需先删除旧应用。 +部署 Web 应用到 CloudBase(构建前后端,部署到独立子域名)。action=getUploadUrl 获取预签名上传 URL(cloud mode 下使用);action=deployApp 上传源码 ZIP 并触发远端构建部署管道:npm install → npm run build → tcb hosting deploy。部署完成后有独立的 `*.webapps.tcloudbase.com` 子域名,也支持绑定自定义域名。新项目推荐传 framework=static 跳过远端构建。与 manageHosting 对比:manageApps 有独立子域名并支持版本管理;manageHosting 适合已有项目继续使用或作为 fallback。两者均可绑定自定义域名。 #### 参数 @@ -2111,53 +2116,53 @@ CloudApp 域统一写入口。action=deployApp 会先 uploadCode 再 createApp name: "action", type: "string", required: true, - description: ` 可填写的值: "deployApp", "deleteApp", "deleteAppVersion"`, + description: ` 可填写的值: "deployApp", "getUploadUrl", "deleteApp", "deleteAppVersion"`, }, { name: "serviceName", type: "string", required: true, - description: `CloudApp 服务名。deployApp 时复用现有 serviceName 会新增一个部署版本并触发重新部署,而不是删除重建。`, + description: `CloudBase 应用服务名,会体现在域名中:-.webapps.tcloudbase.com。deployApp 时复用现有 serviceName 会新增一个部署版本并触发重新部署,而不是删除重建。首次部署请用新名称。`, }, { name: "filePath", type: "string", - description: `要上传并部署的本地项目根目录或 zip 文件绝对路径。deployApp 时必填;通常传源码所在目录而不是 build 产物目录,构建产物目录请用 buildPath 指定。`, + description: `要上传并部署的本地项目根目录绝对路径。本地模式下 deployApp 时必填;通常传源码所在目录(含 package.json 和源码),不是 dist 目录。构建产物目录请用 buildPath 指定。cloud mode 下无需传此参数,改用 cosTimestamp。`, }, { name: "appPath", type: "string", - description: `应用线上访问路径(hosting mount path),例如 /my-web-app。不是本地目录路径;省略时默认为 /serviceName。`, + description: `应用线上访问路径(hosting mount path),例如 /my-web-app。不是本地目录路径;CloudApp 已有独立子域名,省略时默认为 /(根路径)。`, }, { name: "buildPath", type: "string", - description: `构建产物目录,相对于 filePath,例如 dist 或 build。纯静态 HTML 如果入口文件直接在项目根目录,可省略。`, + description: `构建产物目录,相对于 filePath,例如 dist 或 build。传此值后远端构建系统会 cd 到此目录再执行 tcb hosting deploy,因此 deployCmd 会自动使用 .(当前目录)而非目录名,避免路径重复(如 dist/dist 错误)。纯静态 HTML 如果在项目根目录可省略。`, }, { name: "framework", type: "string", - description: `前端框架类型。可选值:vue、react、next、nuxt、vite、angular、static;纯 HTML/静态站点请传 static。 可填写的值: "vue", "react", "next", "nuxt", "vite", "angular", "static"`, + description: `前端框架类型。可选值:vue、react、next、nuxt、vite、angular、static。即使传 static,仍会经过远端构建管道。如果本地已构建好,建议改用 manageHosting 直接上传。 可填写的值: "vue", "react", "next", "nuxt", "vite", "angular", "static"`, }, { name: "nodeJsVersion", type: "string", - description: `构建时使用的 Node.js 版本;不传时由 CloudApp 使用默认值。`, + description: `构建时使用的 Node.js 版本;不传时由 CloudBase 使用默认值。`, }, { name: "installCmd", type: "string", - description: `依赖安装命令,例如 npm install;静态资源无需安装依赖时可省略。`, + description: `依赖安装命令,例如 npm install。不传时默认 npm install。本地已安装或无需安装可传空字符串 '' 跳过,但远端仍会执行 tcb hosting deploy。`, }, { name: "buildCmd", type: "string", - description: `构建命令,例如 npm run build;纯静态 HTML 无构建步骤时可省略。`, + description: `构建命令,例如 npm run build。不传时默认 npm run build。本地已构建好可传空字符串 '' 跳过构建步骤。`, }, { name: "deployCmd", type: "string", - description: `自定义部署命令。通常无需填写,只有在默认部署步骤不满足要求时才传。`, + description: `自定义部署命令。通常无需填写,默认自动生成 tcb hosting deploy 命令。有 buildPath 时远端已 cd 到该目录,默认用 . 作为源码路径;无 buildPath 时默认用 dist。`, }, { name: "ignore", diff --git a/mcp/src/tools/apps.test.ts b/mcp/src/tools/apps.test.ts index c067e685..03f06aca 100644 --- a/mcp/src/tools/apps.test.ts +++ b/mcp/src/tools/apps.test.ts @@ -10,6 +10,8 @@ const { mockDescribeAppVersionList, mockUploadCode, mockCreateApp, + mockDescribeBuildLog, + mockDescribeCosInfo, } = vi.hoisted(() => ({ mockGetCloudBaseManager: vi.fn(), mockLogCloudBaseResult: vi.fn(), @@ -18,6 +20,8 @@ const { mockDescribeAppVersionList: vi.fn(), mockUploadCode: vi.fn(), mockCreateApp: vi.fn(), + mockDescribeBuildLog: vi.fn(), + mockDescribeCosInfo: vi.fn(), })); vi.mock("../cloudbase-manager.js", () => ({ @@ -25,6 +29,10 @@ vi.mock("../cloudbase-manager.js", () => ({ logCloudBaseResult: mockLogCloudBaseResult, })); +vi.mock("../utils/cloud-mode.js", () => ({ + isCloudMode: () => false, +})); + function createMockServer() { const tools: Record Promise }> = {}; @@ -71,6 +79,22 @@ describe("app tools", () => { VersionName: "v1", RequestId: "req-app-create", }); + mockDescribeBuildLog.mockResolvedValue({ + Response: { + Total: 2, + LogList: [ + { Message: "开始构建", Time: "2026-06-03 12:00:00", Level: "INFO" }, + { Message: "部署完成", Time: "2026-06-03 12:01:00", Level: "INFO" }, + ], + RequestId: "req-build-log", + }, + }); + mockDescribeCosInfo.mockResolvedValue({ + UploadUrl: "https://example.com/upload", + UploadHeaders: [{ Key: "Content-Type", Value: "application/zip" }], + UnixTimestamp: "1741234567", + RequestId: "req-cos-info", + }); mockGetCloudBaseManager.mockResolvedValue({ cloudAppService: { describeAppList: mockDescribeAppList, @@ -78,7 +102,11 @@ describe("app tools", () => { describeAppVersionList: mockDescribeAppVersionList, uploadCode: mockUploadCode, createApp: mockCreateApp, + describeCosInfo: mockDescribeCosInfo, }, + commonService: () => ({ + call: mockDescribeBuildLog, + }), }); ({ tools } = createMockServer()); }); @@ -134,14 +162,72 @@ describe("app tools", () => { }); }); + it("manageApps(action=deployApp) with cosTimestamp should skip uploadCode", async () => { + const result = await tools.manageApps.handler({ + action: "deployApp", + serviceName: "demo-app", + cosTimestamp: "1741234567", + buildPath: "dist", + framework: "static", + }); + const payload = JSON.parse(result.content[0].text); + + // uploadCode should NOT be called when cosTimestamp is provided + expect(mockUploadCode).not.toHaveBeenCalled(); + expect(mockCreateApp).toHaveBeenCalledWith( + expect.objectContaining({ + deployType: "static-hosting", + serviceName: "demo-app", + buildType: "ZIP", + staticConfig: expect.objectContaining({ + cosTimestamp: "1741234567", + }), + }), + ); + expect(payload).toMatchObject({ + success: true, + data: { + action: "deployApp", + serviceName: "demo-app", + }, + }); + }); + + it("manageApps(action=getUploadUrl) should return pre-signed URL", async () => { + const result = await tools.manageApps.handler({ + action: "getUploadUrl", + serviceName: "demo-app", + }); + const payload = JSON.parse(result.content[0].text); + + expect(mockDescribeCosInfo).toHaveBeenCalledWith({ + deployType: "static-hosting", + serviceName: "demo-app", + }); + expect(payload).toMatchObject({ + success: true, + data: { + action: "getUploadUrl", + serviceName: "demo-app", + uploadUrl: "https://example.com/upload", + cosTimestamp: "1741234567", + method: "PUT", + nextAction: { + action: "上传代码到预签名 URL", + }, + }, + }); + }); + it("manageApps schema should clarify redeploy flow and framework values", () => { - expect(tools.manageApps.meta.description).toContain("复用同一个 serviceName"); + expect(tools.manageApps.meta.description).toContain("远端构建"); + expect(tools.manageApps.meta.description).toContain("与 manageHosting 对比"); expect(tools.manageApps.meta.inputSchema.serviceName.description).toContain("重新部署"); expect(tools.manageApps.meta.inputSchema.framework.safeParse("static").success).toBe(true); expect(tools.manageApps.meta.inputSchema.framework.safeParse("html").success).toBe(false); }); - it("manageApps(action=deployApp) should require filePath", async () => { + it("manageApps(action=deployApp) should require filePath or cosTimestamp", async () => { const result = await tools.manageApps.handler({ action: "deployApp", serviceName: "demo-app", @@ -150,7 +236,37 @@ describe("app tools", () => { expect(payload).toMatchObject({ success: false, - message: "action=deployApp 时必须提供 filePath", + message: expect.stringContaining("filePath"), + }); + }); + + it("queryApps(action=getBuildLog) should return build logs", async () => { + const result = await tools.queryApps.handler({ + action: "getBuildLog", + serviceName: "demo-app", + buildId: "build-1", + }); + const payload = JSON.parse(result.content[0].text); + + expect(mockDescribeBuildLog).toHaveBeenCalledWith( + expect.objectContaining({ + Action: "DescribeCloudBaseRunBuildLog", + Param: expect.objectContaining({ + ServiceName: "demo-app", + BuildId: "build-1", + }), + }), + ); + expect(payload).toMatchObject({ + success: true, + data: { + action: "getBuildLog", + serviceName: "demo-app", + buildId: "build-1", + logs: expect.arrayContaining([ + expect.objectContaining({ Message: expect.any(String) }), + ]), + }, }); }); }); diff --git a/mcp/src/tools/apps.ts b/mcp/src/tools/apps.ts index 7b3dcbef..ffbac8a7 100644 --- a/mcp/src/tools/apps.ts +++ b/mcp/src/tools/apps.ts @@ -2,9 +2,10 @@ import { z } from "zod"; import { getCloudBaseManager, logCloudBaseResult } from "../cloudbase-manager.js"; import type { ExtendedMcpServer } from "../server.js"; import { jsonContent } from "../utils/json-content.js"; +import { isCloudMode } from "../utils/cloud-mode.js"; -const QUERY_APP_ACTIONS = ["listApps", "getApp", "listAppVersions", "getAppVersion"] as const; -const MANAGE_APP_ACTIONS = ["deployApp", "deleteApp", "deleteAppVersion"] as const; +const QUERY_APP_ACTIONS = ["listApps", "getApp", "listAppVersions", "getAppVersion", "getBuildLog"] as const; +const MANAGE_APP_ACTIONS = ["deployApp", "getUploadUrl", "deleteApp", "deleteAppVersion"] as const; const APP_FRAMEWORKS = ["vue", "react", "next", "nuxt", "vite", "angular", "static"] as const; type QueryAppAction = (typeof QUERY_APP_ACTIONS)[number]; @@ -43,15 +44,15 @@ export function registerAppTools(server: ExtendedMcpServer) { server.registerTool?.( "queryApps", { - title: "查询 CloudApp", + title: "查询 CloudBase 应用部署状态", description: - "CloudApp 域统一只读入口。可先查应用/版本,再在重新部署后用 listAppVersions 或 getAppVersion 按 serviceName 验证是否生成了新版本与最新构建状态。", + "查询 CloudBase 应用部署的应用和版本。可查应用列表/详情、版本列表/详情;部署后用 getAppVersion 按 buildId 轮询构建状态;getBuildLog 可查询构建日志用于诊断失败原因。", inputSchema: { action: z.enum(QUERY_APP_ACTIONS), serviceName: z .string() .optional() - .describe("CloudApp 服务名。getApp / listAppVersions / getAppVersion 时必填;重新部署后复用同一个 serviceName 查询版本历史。"), + .describe("CloudBase 应用服务名。getApp / listAppVersions / getAppVersion / getBuildLog 时必填;重新部署后复用同一个 serviceName 查询版本历史。"), searchKey: z.string().optional().describe("按应用服务名模糊搜索关键词,仅 action=listApps 时使用。"), pageNo: z.number().optional().describe("分页页码,从 1 开始。"), pageSize: z.number().optional().describe("分页大小。"), @@ -62,7 +63,11 @@ export function registerAppTools(server: ExtendedMcpServer) { buildId: z .string() .optional() - .describe("构建 ID。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。"), + .describe("构建 ID。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。getBuildLog 时必填。"), + start: z + .number() + .optional() + .describe("构建日志偏移量,用于分页拉取后续日志。仅 action=getBuildLog 时使用,不传时从开头返回。"), }, annotations: { readOnlyHint: true, @@ -78,6 +83,7 @@ export function registerAppTools(server: ExtendedMcpServer) { pageSize, versionName, buildId, + start, }: { action: QueryAppAction; serviceName?: string; @@ -86,6 +92,7 @@ export function registerAppTools(server: ExtendedMcpServer) { pageSize?: number; versionName?: string; buildId?: string; + start?: number; }) => { try { const cloudbase = await getManager(); @@ -110,7 +117,7 @@ export function registerAppTools(server: ExtendedMcpServer) { total: result.Total ?? 0, raw: result, }, - "CloudApp 列表查询成功", + "CloudBase 应用列表查询成功", ), ); } @@ -132,7 +139,7 @@ export function registerAppTools(server: ExtendedMcpServer) { serviceName, app: result, }, - "CloudApp 详情查询成功", + "CloudBase 应用详情查询成功", ), ); } @@ -154,7 +161,40 @@ export function registerAppTools(server: ExtendedMcpServer) { total: result.Total ?? 0, raw: result, }, - "CloudApp 版本列表查询成功", + "CloudBase 应用版本列表查询成功", + ), + ); + } + + if (action === "getBuildLog") { + if (!buildId) { + throw new Error("action=getBuildLog 时必须提供 buildId"); + } + const result = await cloudbase.commonService("tcb", "2018-06-08").call({ + Action: "DescribeCloudBaseRunBuildLog", + Param: { + EnvId: cloudBaseOptions?.envId || process.env.CLOUDBASE_ENV_ID, + ServiceName: serviceName, + BuildId: buildId, + Start: start ?? 0, + }, + }); + logCloudBaseResult(server.logger, result); + const logs = result.Response?.LogList || []; + return jsonContent( + buildEnvelope( + { + action, + serviceName, + buildId, + logs, + total: result.Response?.Total || logs.length, + nextStart: result.Response?.NextStart, + raw: result, + }, + logs.length > 0 + ? `查询到 ${logs.length} 条构建日志` + : "暂无构建日志", ), ); } @@ -166,14 +206,35 @@ export function registerAppTools(server: ExtendedMcpServer) { buildId, }); logCloudBaseResult(server.logger, result); - return jsonContent( - buildEnvelope( - { - action, + + const isFailed = result.Status === "FAILED"; + const payload: Record = { + action, + serviceName, + status: result.Status, + buildId: result.BuildId, + failReason: result.FailReason, + buildDuration: result.BuildDuration, + version: result, + }; + + if (isFailed) { + payload.nextStep = { + action: "查询构建日志", + tool: "queryApps", + args: { + action: "getBuildLog", serviceName, - version: result, + buildId: result.BuildId, }, - "CloudApp 版本详情查询成功", + hint: `构建失败。调用 queryApps(action="getBuildLog", serviceName="${serviceName}", buildId="${result.BuildId}") 查看构建日志,诊断失败原因。`, + }; + } + + return jsonContent( + buildEnvelope( + payload, + `CloudBase 应用版本详情查询成功(状态: ${result.Status}${result.FailReason ? `, 失败原因: ${result.FailReason}` : ""}${isFailed ? ",可查询构建日志" : ""})`, ), ); } catch (error) { @@ -185,18 +246,45 @@ export function registerAppTools(server: ExtendedMcpServer) { server.registerTool?.( "manageApps", { - title: "管理 CloudApp", + title: "部署应用到 CloudBase(独立子域名)", description: - "CloudApp 域统一写入口。action=deployApp 会先 uploadCode 再 createApp;首次调用创建应用,后续复用同一个 serviceName 会直接触发重新部署并生成新版本,无需先删除旧应用。", + "部署 Web 应用到 CloudBase(构建前后端,部署到独立子域名)。\n" + + "action=getUploadUrl 获取预签名上传 URL(cloud mode 下使用),返回上传地址和 cosTimestamp。\n" + + "action=deployApp 上传源码 ZIP 并触发远端构建部署管道:\n" + + " 1. 远端 npm install(可通过 installCmd=\"\" 跳过)\n" + + " 2. 远端 npm run build(可通过 buildCmd=\"\" 跳过)\n" + + " 3. 远端 tcb hosting deploy\n" + + "\n" + + "域名格式:`-.webapps.tcloudbase.com`(每个 serviceName 一个独立子域名)\n" + + "\n" + + "✅ 推荐用法(新项目/需要独立域名的 Web 应用,首选此工具):\n" + + " 新建项目首次部署时,传 framework=static, installCmd=\"\", buildCmd=\"\" 跳过远端构建,\n" + + " 只执行 tcb hosting deploy。部署后获得独立子域名,支持版本管理。\n" + + "\n" + + "⚠️ 兼容性说明:\n" + + "- 已有项目若之前用 manageHosting 部署过(域名格式:`-.tcloudbaseapp.com`),\n" + + " 切换到 manageApps 会产生全新的 URL,老链接失效。请保持原部署方式不变。\n" + + "- 如需判断:调用 queryHosting 检查是否已有托管文件。\n" + + "\n" + + "与 manageHosting 对比:\n" + + "- manageApps(本工具,新项目首选):域名 `-.webapps.tcloudbase.com`,独立子域名,支持版本管理\n" + + "- manageHosting(已有项目或 fallback):域名 `-.tcloudbaseapp.com/`,共享环境域名\n" + + "两者均可绑定自定义域名。\n" + + "\n" + + "⚠️ 如果 manageApps 构建失败,先用 queryApps(action=\"getBuildLog\") 查日志;仍不行再 fallback 到 manageHosting。", inputSchema: { action: z.enum(MANAGE_APP_ACTIONS), serviceName: z .string() - .describe("CloudApp 服务名。deployApp 时复用现有 serviceName 会新增一个部署版本并触发重新部署,而不是删除重建。"), + .describe("CloudBase 应用服务名,会体现在域名中:`-.webapps.tcloudbase.com`。deployApp 时复用现有 serviceName 会新增一个部署版本并触发重新部署,而不是删除重建。首次部署请用新名称。"), filePath: z .string() .optional() - .describe("要上传并部署的本地项目根目录或 zip 文件绝对路径。deployApp 时必填;通常传源码所在目录而不是 build 产物目录,构建产物目录请用 buildPath 指定。"), + .describe("要上传并部署的本地项目根目录绝对路径。本地模式下 deployApp 时必填;通常传源码所在目录(含 package.json 和源码),不是 dist 目录。构建产物目录请用 buildPath 指定。cloud mode 下无需传此参数,改用 cosTimestamp。"), + cosTimestamp: z + .string() + .optional() + .describe("可选 COS 时间戳。传入此值则直接使用已上传的代码创建应用,跳过本地文件上传。需先调用 getUploadUrl 获取预签名 URL,上传 ZIP 包后再传此时间戳。cloud mode 下为必填;本地模式也可传此值代替 filePath。两个路径二选一:filePath(本地打包上传)或 cosTimestamp(预签名 URL 上传)。"), appPath: z .string() .optional() @@ -204,27 +292,31 @@ export function registerAppTools(server: ExtendedMcpServer) { buildPath: z .string() .optional() - .describe("构建产物目录,相对于 filePath,例如 dist 或 build。纯静态 HTML 如果入口文件直接在项目根目录,可省略。"), + .describe("构建产物目录,相对于 filePath,例如 dist 或 build。\n" + + "⚠️ 传此值后远端构建系统会 cd 到此目录再执行 tcb hosting deploy,因此 deployCmd 会自动使用 .(当前目录)而非目录名,避免路径重复(如 dist/dist 错误)。\n" + + "纯静态 HTML 如果在项目根目录可省略,但注意 deployCmd 默认用 dist。"), framework: z .enum(APP_FRAMEWORKS) .optional() - .describe("前端框架类型。可选值:vue、react、next、nuxt、vite、angular、static;纯 HTML/静态站点请传 static。"), + .describe("前端框架类型。可选值:vue、react、next、nuxt、vite、angular、static。\n" + + "即使传 static,仍会经过远端构建管道。如果本地已构建好,建议改用 manageHosting 直接上传,可完全跳过远端构建。"), nodeJsVersion: z .string() .optional() - .describe("构建时使用的 Node.js 版本;不传时由 CloudApp 使用默认值。"), + .describe("构建时使用的 Node.js 版本;不传时由 CloudBase 使用默认值。"), installCmd: z .string() .optional() - .describe("依赖安装命令,例如 npm install;静态资源无需安装依赖时可省略。"), + .describe("依赖安装命令,例如 npm install。不传时默认 npm install。本地已安装或无需安装可传空字符串 '' 跳过,但远端仍会执行 tcb hosting deploy。"), buildCmd: z .string() .optional() - .describe("构建命令,例如 npm run build;纯静态 HTML 无构建步骤时可省略。"), + .describe("构建命令,例如 npm run build。不传时默认 npm run build。本地已构建好可传空字符串 '' 跳过构建步骤。若希望完全跳过远端管道,请改用 manageHosting。"), deployCmd: z .string() .optional() - .describe("自定义部署命令。通常无需填写,只有在默认部署步骤不满足要求时才传。"), + .describe("自定义部署命令。通常无需填写,默认自动生成 tcb hosting deploy 命令。" + + "有 buildPath 时远端已 cd 到该目录,默认用 . 作为源码路径;无 buildPath 时默认用 dist。"), ignore: z.array(z.string()).optional().describe("上传时忽略的文件/目录 glob 模式,例如 **/node_modules/**。"), versionName: z .string() @@ -243,6 +335,7 @@ export function registerAppTools(server: ExtendedMcpServer) { action, serviceName, filePath, + cosTimestamp, appPath, buildPath, framework, @@ -256,6 +349,7 @@ export function registerAppTools(server: ExtendedMcpServer) { action: ManageAppAction; serviceName: string; filePath?: string; + cosTimestamp?: string; appPath?: string; buildPath?: string; framework?: string; @@ -273,44 +367,141 @@ export function registerAppTools(server: ExtendedMcpServer) { throw new Error("当前 manager 未提供 cloudAppService"); } - if (action === "deployApp") { - if (!filePath) { - throw new Error("action=deployApp 时必须提供 filePath"); + // getUploadUrl — 获取预签名上传 URL(cloud mode 专用) + if (action === "getUploadUrl") { + if (!serviceName) { + throw new Error("action=getUploadUrl 时必须提供 serviceName"); } - const uploadResult = await appService.uploadCode({ + const cosInfoResult = await appService.describeCosInfo({ deployType: "static-hosting", serviceName, - localPath: filePath, - ignore, }); - logCloudBaseResult(server.logger, uploadResult); + logCloudBaseResult(server.logger, cosInfoResult); + + const defaultIgnore = ["node_modules/**", ".git/**", ".DS_Store", "**/.DS_Store"]; + // eslint-disable-next-line max-len + const zipCmd = "zip -r upload.zip . -x 'node_modules/**' -x '.git/**' -x '.DS_Store' -x '**/.DS_Store'"; + const followupArgs: Record = { + action: "deployApp", + serviceName, + cosTimestamp: cosInfoResult.UnixTimestamp, + }; + + return jsonContent( + buildEnvelope( + { + action, + serviceName, + uploadUrl: cosInfoResult.UploadUrl, + uploadHeaders: cosInfoResult.UploadHeaders, + cosTimestamp: cosInfoResult.UnixTimestamp, + method: "PUT", + ignore: defaultIgnore, + zipCommand: zipCmd, + nextAction: { + action: "上传代码到预签名 URL", + hint: "请先在本地打包项目代码(排除 node_modules/.git),再将其上传到预签名 URL,然后调用 deployApp 触发构建", + details: [ + `1. 打包: ${zipCmd}`, + `2. 上传: curl -X PUT -T upload.zip '${cosInfoResult.UploadUrl}'`, + `3. 触发构建: manageApps(action="deployApp", serviceName="${serviceName}", cosTimestamp="${cosInfoResult.UnixTimestamp}")`, + ], + followup: { + tool: "manageApps", + args: followupArgs, + }, + }, + }, + "预签名上传 URL 获取成功。请上传代码后调用 deployApp 触发构建。", + ), + ); + } + + if (action === "deployApp") { + // cloud mode 下必须有 cosTimestamp;本地模式必须有 filePath + if (isCloudMode()) { + if (!cosTimestamp) { + throw new Error( + "cloud mode 下 deployApp 需要 cosTimestamp 参数。请先调用 getUploadUrl 获取预签名上传 URL。" + + "上传代码后再用 cosTimestamp 调用 deployApp。", + ); + } + } else if (!filePath && !cosTimestamp) { + throw new Error("action=deployApp 时必须提供 filePath(本地模式)或 cosTimestamp(cloud mode)。"); + } + + // 上传代码到 COS(仅本地模式需要,cloud mode 用 cosTimestamp 跳过) + let cosTs = cosTimestamp; + if (filePath && !cosTs) { + const uploadResult = await appService.uploadCode({ + deployType: "static-hosting", + serviceName, + localPath: filePath, + ignore, + }); + logCloudBaseResult(server.logger, uploadResult); + cosTs = uploadResult.cosTimestamp; + } + + // 构建命令智能默认值 + const resolvedInstallCmd = installCmd ?? "npm install"; + const resolvedBuildCmd = buildCmd ?? "npm run build"; + const resolvedDeployPath = appPath || "/"; + const resolvedBuildPath = buildPath || ""; + // ⚠️ 远端构建系统在有 buildPath 时 cd 到此目录再执行 tcb hosting deploy + // 部署命令用 "." 避免 dist/dist 重复。framework=static 无构建步骤,用根目录 + const resolvedDeployCmd = deployCmd || ( + resolvedBuildPath || framework === "static" + ? `tcb hosting deploy . ${resolvedDeployPath}` + : `tcb hosting deploy dist ${resolvedDeployPath}`); + + // 触发远端构建 const result = await appService.createApp({ deployType: "static-hosting", serviceName, buildType: "ZIP", staticConfig: { - appPath, - buildPath, + appPath: resolvedDeployPath, + buildPath: resolvedBuildPath, framework, nodeJsVersion, - cosTimestamp: uploadResult.cosTimestamp, + cosTimestamp: cosTs, staticCmd: { - installCmd, - buildCmd, - deployCmd, + installCmd: resolvedInstallCmd, + buildCmd: resolvedBuildCmd, + deployCmd: resolvedDeployCmd, }, }, }); logCloudBaseResult(server.logger, result); + + const { BuildId, VersionName } = result; return jsonContent( buildEnvelope( { action, serviceName, - upload: uploadResult, + versionName: VersionName, + buildId: BuildId, + upload: { cosTimestamp: cosTs }, deployment: result, + buildConfig: { + installCmd: resolvedInstallCmd, + buildCmd: resolvedBuildCmd, + deployCmd: resolvedDeployCmd, + }, + nextStep: { + action: "轮询构建状态", + tool: "queryApps", + args: { + action: "getAppVersion", + serviceName, + buildId: BuildId, + }, + hint: `调用 queryApps(action="getAppVersion", serviceName="${serviceName}", buildId="${BuildId}") 轮询构建状态,直到 status 变为 SUCCESS 或 FAILED。构建通常需要 3~5 分钟。若状态为 FAILED,可继续调用 queryApps(action="getBuildLog", serviceName="${serviceName}", buildId="${BuildId}") 查看构建日志诊断失败原因。`, + }, }, - "CloudApp 部署成功", + "CloudBase 应用构建已触发,请通过 queryApps 轮询构建状态。", ), ); } @@ -328,7 +519,7 @@ export function registerAppTools(server: ExtendedMcpServer) { serviceName, raw: result, }, - "CloudApp 删除成功", + "CloudBase 应用删除成功", ), ); } @@ -350,7 +541,7 @@ export function registerAppTools(server: ExtendedMcpServer) { versionName, raw: result, }, - "CloudApp 版本删除成功", + "CloudBase 应用版本删除成功", ), ); } catch (error) { diff --git a/mcp/src/tools/setup.ts b/mcp/src/tools/setup.ts index c47ed56f..44e4cb16 100644 --- a/mcp/src/tools/setup.ts +++ b/mcp/src/tools/setup.ts @@ -290,15 +290,34 @@ async function downloadFile(url: string, filePath: string, redirectCount = 0): P }); } -// 解压ZIP文件 +// 解压ZIP文件(含 zip slip 防护) async function extractZip(zipPath: string, extractPath: string): Promise { try { // 创建解压目录 await fsPromises.mkdir(extractPath, { recursive: true }); - // 使用 adm-zip 库进行解压 + // 使用 adm-zip 库逐条目解压,防止 zip slip 路径穿越 const zip = new AdmZip(zipPath); - zip.extractAllTo(extractPath, true); + const entries = zip.getEntries(); + const resolvedExtractPath = path.resolve(extractPath); + + for (const entry of entries) { + // 跳过目录条目,后面由 mkdir 自动创建 + if (entry.isDirectory) continue; + + const entryPath = path.resolve(resolvedExtractPath, entry.entryName); + if (!entryPath.startsWith(resolvedExtractPath + path.sep)) { + throw new Error( + `解压失败: ZIP 文件中包含非法的路径 "${entry.entryName}",已安全拦截`, + ); + } + + // 确保目标目录存在 + await fsPromises.mkdir(path.dirname(entryPath), { recursive: true }); + + // 写入文件 + await fsPromises.writeFile(entryPath, entry.getData()); + } } catch (error) { throw new Error( `解压失败: ${error instanceof Error ? error.message : "未知错误"}`, diff --git a/mcp/src/utils/cloud-mode.ts b/mcp/src/utils/cloud-mode.ts index 9ac959dd..339f3fb0 100644 --- a/mcp/src/utils/cloud-mode.ts +++ b/mcp/src/utils/cloud-mode.ts @@ -93,9 +93,6 @@ export function shouldRegisterTool(toolName: string): boolean { 'manageCloudRun', // Download tools - local file downloads 'manageStorage', - - // Apps tools - deployApp reads local filePath for code upload - 'manageApps', ]; const shouldRegister = !cloudIncompatibleTools.includes(toolName); diff --git a/plugin/cloudbase-vibe-coding/bin/cloudbase-vibe-deploy b/plugin/cloudbase-vibe-coding/bin/cloudbase-vibe-deploy new file mode 100755 index 00000000..4dec5028 --- /dev/null +++ b/plugin/cloudbase-vibe-coding/bin/cloudbase-vibe-deploy @@ -0,0 +1,318 @@ +#!/usr/bin/env node +/** + * cloudbase-vibe-deploy + * + * Build the Vite project in cwd and bridge to cloudbase-mcp's `manageApps` + * tool with framework=static (skips remote npm install/build, only runs + * tcb hosting deploy). Each CloudApp gets an independent subdomain. + * + * Why manageApps not manageHosting: + * - manageApps gives each `serviceName` an independent `*.webapps.tcloudbase.com` + * subdomain — no path collisions between vibe sessions. + * - The local build already produced dist/, so we pass installCmd="" and + * buildCmd="" to skip remote build steps; only tcb hosting deploy runs. + * - Both manageApps and manageHosting support custom domain binding. + * + * Stable serviceName per cwd: + * First deploy generates a name `-<6hex>` and writes it to + * `/.cloudbase-agent/app.json`. Subsequent deploys reuse the same + * serviceName so the URL stays stable. Renaming cwd does not change it. + * + * Two-phase contract (model is expected to bridge them): + * Phase 1 (build): + * `cloudbase-vibe-deploy` + * → git tag snap/pre-deploy- + * → pnpm/npm run build + * → validate dist/ + * → emit JSON with `nextAction.tool=manageApps` and exact args + * (framework=static, installCmd="", buildCmd="", filePath=CWD, buildPath="dist") + * + * Phase 2 (post-deploy, after model calls manageApps): + * `cloudbase-vibe-deploy --post-deploy --access-url [--build-id ]` + * → append to app.json deployHistory + * → git tag deploy/ + * → return finalUrl with cache-bust query + * + * Usage: + * cloudbase-vibe-deploy # build + emit nextAction + * cloudbase-vibe-deploy --skip-build # already built, just emit nextAction + * cloudbase-vibe-deploy --post-deploy \ + * --access-url https://xxx.webapps.tcloudbase.com/ \ + * --build-id # record the deploy result + * cloudbase-vibe-deploy --service-name # print stable serviceName for this cwd, exit + * + * Exit codes (canonical, also re-exported from lib/preview-state.mjs ERR): + * 0 success + * 1 generic + * 2 not a Vite project + * 7 build failed + * 8 dist missing or empty after build + */ + +import { spawnSync } from "node:child_process"; +import { randomBytes } from "node:crypto"; +import { + existsSync, + readdirSync, + readFileSync, + statSync, + writeFileSync, +} from "node:fs"; +import { fileURLToPath } from "node:url"; +import { basename, dirname, join } from "node:path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const lib = await import(join(__dirname, "..", "lib", "preview-state.mjs")); +const { ensureDir, STATE_DIR, emitOk, emitErr, ERR } = lib; + +const CWD = process.cwd(); +const APP_JSON = join(STATE_DIR, "app.json"); + +const args = parseArgs(process.argv.slice(2)); + +main().catch((err) => { + emitErr(err.message || String(err), err.code || ERR.GENERIC); + process.exit(err.code || ERR.GENERIC); +}); + +async function main() { + if (args.help) { printHelp(); process.exit(0); } + + if (args.printServiceName) { + process.stdout.write(getOrCreateServiceName() + "\n"); + return; + } + + if (args.postDeploy) return recordPostDeploy(); + + // 1. Verify Vite project (cheap fingerprint). + const pkg = readPkg(); + if (!pkg) throw withCode(ERR.NOT_VITE_PROJECT, "no package.json in cwd"); + const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }; + if (!deps.vite) throw withCode(ERR.NOT_VITE_PROJECT, "vite not in dependencies/devDependencies"); + if (!deps.react && !deps.vue) throw withCode(ERR.NOT_VITE_PROJECT, "neither react nor vue is a dependency"); + + // 2. Establish stable serviceName for this cwd. + const serviceName = getOrCreateServiceName(); + + // 3. Pre-deploy git snapshot (best-effort; not fatal if no git). + const snapshotTag = `snap/pre-deploy-${Date.now()}`; + const snapshotResult = gitSnapshot(snapshotTag); + + // 4. Build. + if (!args.skipBuild) { + const buildScript = pkg.scripts?.build; + if (!buildScript) throw withCode(ERR.BUILD_FAILED, "package.json has no scripts.build"); + runBuild(); + } + + // 5. Validate dist/. + const distPath = join(CWD, "dist"); + if (!existsSync(distPath) || !statSync(distPath).isDirectory()) { + throw withCode(ERR.DIST_MISSING, "dist/ does not exist after build"); + } + const entries = readdirSync(distPath); + if (entries.length === 0) { + throw withCode(ERR.DIST_MISSING, "dist/ is empty after build"); + } + + // 6. Emit nextAction. Local build is done → manageApps(framework=static) + // skips remote install/build, only runs tcb hosting deploy. + const previouslyDeployed = readAppJson()?.lastDeployedAt != null; + const out = { + stage: "build-complete", + serviceName, + distPath, + distEntries: entries.length, + snapshotTag: snapshotResult.ok ? snapshotTag : null, + snapshotWarning: snapshotResult.ok ? null : snapshotResult.reason, + previouslyDeployed, + nextAction: { + tool: "manageApps", + args: { + action: "deployApp", + serviceName, + filePath: CWD, + buildPath: "dist", + framework: "static", + installCmd: "", + buildCmd: "", + }, + hint: previouslyDeployed + ? `Re-deploys the existing CloudApp '${serviceName}' (URL stays the same).` + : `First-time deploy creates a new CloudApp '${serviceName}' with its own subdomain.`, + followup: { + tool: "cloudbase-vibe-deploy", + args: ["--post-deploy", "--access-url", "", "--build-id", ""], + purpose: "record this deploy in app.json and tag git", + }, + }, + }; + + emitOk(out, `build complete; deploy '${serviceName}' next via manageApps (framework=static)`); +} + +function recordPostDeploy() { + if (!args.accessUrl) throw withCode(ERR.GENERIC, "--post-deploy requires --access-url"); + + ensureDir(STATE_DIR); + const app = readAppJson() || { serviceName: getOrCreateServiceName(), deployHistory: [] }; + const ts = new Date().toISOString(); + const entry = { + deployedAt: ts, + accessUrl: args.accessUrl, + buildId: args.buildId || null, + }; + app.deployHistory = [...(app.deployHistory || []), entry].slice(-50); + app.lastDeployedAt = ts; + app.lastAccessUrl = args.accessUrl; + if (!app.firstDeployedAt) app.firstDeployedAt = ts; + writeFileSync(APP_JSON, JSON.stringify(app, null, 2)); + + // Tag git. + const tag = `deploy/${Date.now()}`; + const tagResult = gitSnapshot(tag); + + const cacheBust = randomQuery(); + const finalUrl = appendQuery(args.accessUrl, `v=${cacheBust}`); + + emitOk( + { + stage: "post-deploy", + serviceName: app.serviceName, + ...entry, + gitTag: tagResult.ok ? tag : null, + finalUrl, + deployCount: app.deployHistory.length, + }, + `deploy recorded for '${app.serviceName}' — open: ${finalUrl}`, + ); +} + +// --------------------------------------------------------------------------- +// serviceName: stable per cwd, generated once, written to app.json +// --------------------------------------------------------------------------- + +function getOrCreateServiceName() { + const existing = readAppJson(); + if (existing?.serviceName) return existing.serviceName; + + // Generate: -<6hex>. + const raw = basename(CWD).toLowerCase() + .replace(/[^a-z0-9-]/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, "") + .slice(0, 36); // CloudApp service names have length limits; + // leave room for the suffix. + const safe = raw || "vibe"; + const suffix = randomBytes(3).toString("hex"); + const name = `${safe}-${suffix}`; + + // Persist immediately so re-runs and --service-name are stable. + ensureDir(STATE_DIR); + const app = existing || {}; + app.serviceName = name; + app.cwd = CWD; + if (!app.firstSeenAt) app.firstSeenAt = new Date().toISOString(); + writeFileSync(APP_JSON, JSON.stringify(app, null, 2)); + return name; +} + +function readAppJson() { + if (!existsSync(APP_JSON)) return null; + try { return JSON.parse(readFileSync(APP_JSON, "utf8")); } catch { return null; } +} + +// --------------------------------------------------------------------------- +// helpers +// --------------------------------------------------------------------------- + +function readPkg() { + const p = join(CWD, "package.json"); + if (!existsSync(p)) return null; + try { return JSON.parse(readFileSync(p, "utf8")); } catch { return null; } +} + +function pickRunner() { + const which = (b) => spawnSync(process.platform === "win32" ? "where" : "which", [b], { stdio: "ignore" }).status === 0; + if (existsSync(join(CWD, "pnpm-lock.yaml")) && which("pnpm")) return ["pnpm", "run", "build"]; + if (which("pnpm")) return ["pnpm", "run", "build"]; + if (which("npm")) return ["npm", "run", "build"]; + return ["npx", "--yes", "--no-install", "vite", "build"]; +} + +function runBuild() { + const [cmd, ...rest] = pickRunner(); + const r = spawnSync(cmd, rest, { + cwd: CWD, + stdio: ["ignore", "inherit", "inherit"], + env: process.env, + }); + if (r.status !== 0) { + throw withCode(ERR.BUILD_FAILED, `build exited with code ${r.status} (cmd: ${cmd} ${rest.join(" ")})`); + } +} + +function gitSnapshot(tag) { + const inRepo = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: CWD, stdio: "ignore" }); + if (inRepo.status !== 0) return { ok: false, reason: "not a git repository" }; + + spawnSync("git", ["add", "-A"], { cwd: CWD, stdio: "ignore" }); + spawnSync("git", ["commit", "--allow-empty", "-m", `cloudbase-vibe: ${tag}`], { cwd: CWD, stdio: "ignore" }); + const tagR = spawnSync("git", ["tag", "-f", tag], { cwd: CWD, stdio: "ignore" }); + if (tagR.status !== 0) return { ok: false, reason: `git tag ${tag} failed` }; + return { ok: true }; +} + +function randomQuery() { + return randomBytes(3).toString("hex"); +} + +function appendQuery(url, qs) { + if (!url || !qs) return url; + return url + (url.includes("?") ? "&" : "?") + qs; +} + +function parseArgs(argv) { + const out = {}; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a === "--skip-build") out.skipBuild = true; + else if (a === "--post-deploy") out.postDeploy = true; + else if (a === "--access-url") out.accessUrl = argv[++i]; + else if (a === "--build-id") out.buildId = argv[++i]; + else if (a === "--service-name") out.printServiceName = true; + else if (a === "--help" || a === "-h") out.help = true; + } + return out; +} + +function printHelp() { + process.stdout.write([ + "cloudbase-vibe-deploy — build + bridge to manageApps (framework=static, independent subdomain)", + "", + "Default flow (build phase):", + " cloudbase-vibe-deploy # git snapshot + build + emit nextAction (-> manageApps)", + " cloudbase-vibe-deploy --skip-build # skip build, just emit nextAction (assumes dist/ exists)", + "", + "Post-deploy flow (call after manageApps succeeds):", + " cloudbase-vibe-deploy --post-deploy --access-url [--build-id ]", + "", + "Other:", + " cloudbase-vibe-deploy --service-name # print the stable serviceName for this cwd, exit", + "", + "Output: single JSON line on stdout. The build phase emits a `nextAction`", + "field telling the model to call manageApps with framework=static", + "(skips remote install/build; only tcb hosting deploy runs).", + "", + "State files:", + " /.cloudbase-agent/app.json stable serviceName + deploy history", + " git tag snap/pre-deploy- before each build", + " git tag deploy/ after each --post-deploy", + "", + ].join("\n")); +} + +function withCode(code, message) { + const e = new Error(message); e.code = code; return e; +} diff --git a/plugin/cloudbase-vibe-coding/skills/cloudbase-agent-runtime/SKILL.md b/plugin/cloudbase-vibe-coding/skills/cloudbase-agent-runtime/SKILL.md new file mode 100644 index 00000000..3dfcba7b --- /dev/null +++ b/plugin/cloudbase-vibe-coding/skills/cloudbase-agent-runtime/SKILL.md @@ -0,0 +1,268 @@ +--- +name: cloudbase-agent-runtime +description: | + Use when the user wants to develop, run, preview, or deploy a CloudBase + Web app in this conversation as a Lovable-like vibe-coding session — i.e. + any request that maps to "spin up a React+Vite project, see it live in + the browser, iterate, deploy". Activate ONLY for browser-rendered Web + projects based on the CloudBase official React (or Vue) + Vite template. + Do NOT activate for: WeChat/Alipay mini-program, React Native / Flutter / + Electron, pure cloud functions / CloudRun backends, or Next.js / Nuxt / + Astro / Remix / SvelteKit (those frameworks are explicitly out of scope). +--- + +# CloudBase Agent Runtime — Web Vibe Coding + +This skill orchestrates a **single working directory = single project** flow +for CloudBase Web apps. Every operation in this skill assumes the user's +current shell `cwd` IS the project root. We do NOT manage cross-cwd state, +session IDs, or workspaces — the cwd itself is the workspace. + +## Activation contract + +### Use this skill when + +- The user says "build me a website / app", "I want to make a landing page", + "create a React app and let me preview", "deploy this to CloudBase", + "vibe coding with CloudBase", or similar. +- The current cwd looks like a CloudBase + Vite project (has `package.json` + with `vite` + `react` or `vue`). +- The user is running this conversation inside a host (OpenClaw / Claude + Code) that has both `cloudbase-mcp` registered AND the `cloudbase-vibe-*` + binaries on PATH from this plugin. + +### Do NOT use this skill when + +- It's a mini-program project (`miniprogram-development` skill instead). +- It's a native app project (`http-api` skill instead). +- It's a backend-only project (`cloud-functions` or `cloudrun-development`). +- The framework is Next/Nuxt/Astro/Remix — politely tell the user this skill + only covers Vite-based React/Vue apps and stop. Do NOT try to adapt the + scripts to those frameworks. + +## What this skill orchestrates + +Think of it as three layers: + +1. **CloudBase MCP tools** (provided by the `cloudbase-mcp` server registered + in `.mcp.json`). Use these for: + - `downloadTemplate({ template: "react" | "vue", ide: "" })` — pull + the official template into cwd. + - `envQuery({ action: "info" })` and `auth({ action: "set_env", envId })` + — bind a CloudBase environment. + - `manageHosting({ action: "upload", localPath: "dist", cloudPath: "/" })` + — deploy the build output to static hosting. + - `envDomainManagement({ action: "create", domains: [...] })` — whitelist + the dev origin if Web SDK calls fail with CORS. + - All other CloudBase operations (auth, db, storage) follow the existing + `auth-tool` / `auth-web` / `no-sql-web-sdk` skills. + +2. **Plugin shell scripts** (provided by this plugin's `bin/` directory; on + PATH automatically when the plugin is enabled). Use these — and ONLY these + — for dev-server lifecycle: + - `cloudbase-vibe-start-preview` — daemonize Vite on `0.0.0.0:`. + - `cloudbase-vibe-stop-preview` — SIGTERM with SIGKILL fallback. + - `cloudbase-vibe-restart-preview` — stop + start in one shot. + - `cloudbase-vibe-status-preview` — JSON line of current state. + + Never invent your own `npm run dev` / `vite` invocation. The shell scripts + handle host=0.0.0.0 forcing, port allocation, daemonization, base path + injection, and state file management. Reinventing them will break preview + on Lighthouse hosts and lose the recorded PID. + +3. **Standard tools** (Bash, git). Use for editing files, snapshots + (`git tag snap/`), rollback (`git reset --hard`), build (`pnpm build`). + +## How dev-server lifecycle is handled + +**You do NOT manage the dev server yourself.** Two host hooks do it for you: + +- **SessionStart hook** (auto-runs when the conversation begins): + - If cwd is on the danger blacklist (`$HOME`, `/`, `/tmp`, `~/Desktop`, ...) → skips silently. + - If cwd is a Vite + React/Vue project: starts the daemon (`cloudbase-vibe-start-preview`) if not already running, otherwise reuses the live one. + - If cwd is "empty enough" (only `.git` / `.gitignore` / `README*` / `LICENSE` allowed): auto-runs `cloudbase-vibe-init --start` which downloads the official CloudBase React template, runs `pnpm install`, and starts the daemon. **By the time you read the user's first prompt the dev server is usually live or ~10s away.** + - If cwd is a non-Vite project: skips silently. + +- **PostToolUse(Edit|Write|MultiEdit) hook**: + - If you edited `vite.config.*` / `package.json` / `tsconfig*.json` / `tailwind.config.*` / `postcss.config.*` / `.env*` → triggers `cloudbase-vibe-restart-preview` automatically (1.5s debounced). + - Other file edits → does nothing. Vite HMR handles them. + +**Implication: you almost never need to call `cloudbase-vibe-*` scripts directly.** The hooks handle init / start / restart. You only invoke a `cloudbase-vibe-*` script when: + +- The user explicitly says "stop the dev server" → `cloudbase-vibe-stop-preview`. +- The user explicitly says "what's the URL" or "is it running" → `cloudbase-vibe-status-preview`. +- The user wants to deploy → `cloudbase-vibe-deploy` (see deploy section below). + +If `cloudbase-vibe-status-preview` reports the preview is unhealthy and SessionStart didn't fix it (e.g., user-level error in `vite.config.ts`), surface the JSON `logPath` to the user — never try to fix it by editing their config blindly. + +## When the user just walked into the conversation + +1. **Check the preview state** before doing anything else by reading `/.cloudbase-agent/preview.json` (or run `cloudbase-vibe-status-preview --quiet`). It is overwhelmingly likely that the preview is already up — courtesy of SessionStart. + +2. **Tell the user the URL**. If the file has `internalUrl`, surface it. If not (rare — SessionStart still running or skipped), inform the user the dev server is starting and ask them to wait ~10s, OR check `/.cloudbase-agent/logs/hook-session-start.log` for the reason it skipped. + +3. **DO NOT** call `cloudbase-vibe-init` or `cloudbase-vibe-start-preview` proactively in your first message. The hook already does this. Calling it again is redundant and wastes a turn (`start` is idempotent so it's safe, but `init` will fail with code 10 because cwd is no longer empty). + +## When the user asks you to deploy + +The deploy flow uses **two-step bridge**: local build → CloudApp deploy. + +1. `bash> cloudbase-vibe-deploy` — performs `git tag snap/pre-deploy-...` + `pnpm run build`, validates `dist/`, generates a stable `serviceName` (e.g. `cb-test3-9f0d62`) on first run, and emits a JSON line with `nextAction`. +2. Read `nextAction.tool` and `nextAction.args`. The tool will be `manageApps` with `framework=static` (skips remote npm install/build — only deploys the pre-built dist/): + ``` + manageApps({ + action: "deployApp", + serviceName: "", + filePath: "", + buildPath: "dist", + framework: "static", + installCmd: "", + buildCmd: "" + }) + ``` +3. After `manageApps` succeeds and gives you the access URL, call: + `bash> cloudbase-vibe-deploy --post-deploy --access-url [--build-id ]` + This appends to `/.cloudbase-agent/app.json`'s `deployHistory`, tags git with `deploy/`, and returns a `finalUrl` with cache-busting query. +4. Show `finalUrl` to the user. Optionally suggest the user can further polish the design by asking you to apply the **CloudBase UI design skill** (`ui-design`) for a more polished look. +5. After deployment, proactively ask: "要我在部署后的基础上用 UI 设计能力进一步优化样式和体验吗?" + +**Why `manageApps(framework=static)` not `manageHosting`?** Each `manageApps` service gets its own independent `*.webapps.tcloudbase.com` subdomain — no path collisions between vibe sessions, and no need to bind a custom domain just for isolation. Since the local build already produced `dist/`, we pass `installCmd=""` and `buildCmd=""` to skip remote build steps; only the `tcb hosting deploy` step runs in the cloud. If that still fails, fall back to `manageHosting` + bind a custom domain. + +**If `manageApps` fails with "no envId" or env-related error:** call `envQuery({ action: "info" })` once. If multiple envs exist, ask the user to pick. After the env is bound, retry. + +**If `manageApps` builds FAILED:** call `queryApps({ action: "getBuildLog", serviceName, buildId })` to diagnose. Common cause is the remote `tcb hosting deploy` step failing — fall back to `manageHosting` upload in that case. + +Never bypass `cloudbase-vibe-deploy` with your own `pnpm build` + manual `manageApps` — you would lose the snapshot, deploy history, and serviceName stability. + +## Proactive deploy suggestion + +When you finish a user-requested feature (especially "make me a X app", "build me a Y", "add Z feature"), end your reply by asking the user **whether to deploy**: + +> 现在这版要部署看一下吗?(部署到 CloudApp,会得到一个独立的可分享 URL) + +Do NOT deploy unsolicited — only after the user explicitly says yes. The reason for asking is to make sharing as low-friction as possible, similar to Lovable: the user shouldn't have to learn that "deploy" is a separate action they need to ask for. + +Skip the suggestion when: +- The work was a bug fix or small refactor (no new feature). +- The user already deployed in this conversation and the change since then is trivial. +- The user explicitly said "don't deploy yet" earlier. + +## Proactive verification suggestion + +After finishing a feature, do NOT spend turns on writing or running automated UI tests / playwright / agent-browser by default. Instead end your reply by asking the user **whether to verify in a browser**: + +> 现在这版要不要我用内置浏览器打开 帮你点一遍验证一下? + +Skip when: +- The user already said "no tests" / "skip tests" earlier. +- The change was config-only or non-visual. +- The user is mid-iterating and obviously wants to keep editing. + +Only after explicit consent should you spawn a browser-driving tool. The first version of a vibe-coding app is judged by "does the user see the UI working in the dev server", not by passing tests. + +## Data persistence — BaaS-first + +When the feature needs to store / query / update data: + +1. **Create the schema via cloudbase-mcp.** Call: + ``` + writeNoSqlDatabaseStructure({ + action: "createCollection", + collectionName: "todos" + }) + ``` + And add indexes via `updateCollection` with `updateOptions.CreateIndexes`. Do NOT ask the user to create collections in the console. +2. **Read/write via Web SDK directly from the React/Vue code.** The template ships an initialized `@cloudbase/js-sdk` at `src/utils/cloudbase.ts`. Use it: + ```ts + import { db } from "@/utils/cloudbase"; + await db.collection("todos").add({ text, done: false, createdAt: Date.now() }); + const { data } = await db.collection("todos").orderBy("createdAt", "desc").get(); + db.collection("todos").watch({ onChange: (snap) => setTodos(snap.docs) }); + ``` +3. **For auth**: follow `auth-tool` skill first (provider check), then `auth-web` for client code. + +**Do NOT add a cloud function** unless ALL of these are true: +(a) the logic absolutely cannot be expressed as database security rules; +(b) it needs server-side secrets / third-party APIs (e.g. payment, SMS); +(c) it's a scheduled / background job, not user-triggered. + +A Todo app, Notes app, Chat app, Kanban etc. live ENTIRELY in the JS SDK + database rules. Adding a cloud function "just to be safe" is over-engineering and slows the user. + +## When the conversation ends + +Do nothing. The dev server is daemonized (PPID=1) and survives. The next SessionStart will detect it's still running and reuse it — the user comes back to a live URL with no startup delay. + +## Hard rules + +1. **Never** spawn `npm run dev` / `vite` / `vite build` yourself. Dev-server lifecycle is owned by the SessionStart and PostToolUse hooks; build/deploy is owned by `cloudbase-vibe-deploy`. Bypassing them loses host=0.0.0.0 forcing, port allocation, daemonization, snapshot, and deploy history. + +2. **Never** edit the user's `vite.config.*` to set `server.host` or `base`. + The CLI flags from `start-preview` already override config. If the user's + config conflicts (for example `server.host: false`), surface error code 4 + from start-preview and ask the user to relax that config — do NOT silently + patch their file. + +3. **Never** touch the `~/.cloudbase-agent/` global directory. State lives + in `/.cloudbase-agent/preview.json`. Each cwd is independent. + +4. **Always** parse stdout of `cloudbase-vibe-*` scripts as a single JSON + line. The first line of stdout is the machine-readable result. The + stderr `[cloudbase-agent] ...` line is for humans only — do not parse it. + +5. **Always** surface `logPath` to the user when a script reports failure. + Don't guess the cause from a one-line error — point them at the log file. + +## Error codes from the bin scripts + +| code | meaning | recovery | +|---|---|---| +| 1 | generic failure | check the message | +| 2 | not a Vite project (or `vite` binary missing) | `pnpm install` then retry | +| 3 | port pool exhausted in 17173..17272 | `cloudbase-vibe-stop-preview` for stale ones, or pass `--port` | +| 4 | dev server failed health check in 30s | read `logPath`; usually a build error in user code | +| 5 | no preview is running (status / stop) | start one with `cloudbase-vibe-start-preview` | +| 6 | stop failed (process refused SIGKILL) | check the PID manually with `ps`; very rare | +| 7 | build failed (`cloudbase-vibe-deploy`) | inspect build output in this terminal; fix code, retry | +| 8 | `dist/` missing or empty after build | confirm `scripts.build` runs `vite build`; rerun | + +## State files (for debugging) + +- `/.cloudbase-agent/preview.json` — current preview state. +- `/.cloudbase-agent/logs/preview-.log` — Vite stdout/stderr. +- `/.cloudbase-agent/logs/hook-restart.log` — restart trigger trail (PostToolUse hook). +- `/.cloudbase-agent/restart.lock` — debounce lock for the hook (1.5s window). + +These are owned by the scripts. If the user asks "show me the dev log", +open the path from `status-preview`'s JSON output. + +## Hook & monitor behavior (auto-driven, model is passive) + +This plugin ships two host-driven mechanisms; the model itself does NOT need +to invoke them, but should be aware they exist: + +- **PostToolUse hook** (`hooks/hooks.json`): when the model edits a file via + `Edit` / `Write` / `MultiEdit`, the host fires `hooks/on-file-change.sh`. + The hook checks the edited file path; if it's a config file (`vite.config.*`, + `tailwind.config.*`, `postcss.config.*`, `package.json`, `tsconfig*.json`, + `.env*`), it triggers `cloudbase-vibe-restart-preview` in the background + with a 1.5s debounce. The model does NOT need to manually restart on + config edits — the hook handles it. If the host doesn't support hooks, + fall back to manually calling restart per the rule above. + +- **Vite log monitor** (`monitors/monitors.json`, EXPERIMENTAL): the host + tails the latest `/.cloudbase-agent/logs/preview-*.log` and forwards + each new line to the model as a notification. The model can use this to + proactively report build errors / HMR failures without the user asking. + ⚠️ This relies on the host's monitor implementation honoring the project + cwd; if monitor lines do not appear in the conversation, treat it as not + available and fall back to `cloudbase-vibe-status-preview` on demand. + +## What this skill is NOT + +- It is **not** a session manager. There is no sessionId. There is only cwd. +- It is **not** a reverse proxy. If the host is on Lighthouse and the user + needs `0.0.0.0:8080/s//` style routing, that's a separate runtime + component (`cloudbase-agent-proxy`, see project README) — out of scope here. +- It is **not** a CloudBase auth/database guide. For those, route to the + existing `auth-web`, `auth-tool`, `no-sql-web-sdk` skills. +- It is **not** a UI design guide. For visual decisions, route to `ui-design`. diff --git a/scripts/tools.json b/scripts/tools.json index 492b9643..a63bbddc 100644 --- a/scripts/tools.json +++ b/scripts/tools.json @@ -1517,7 +1517,7 @@ }, { "name": "searchKnowledgeBase", - "description": "云开发知识库智能检索工具,支持向量查询 (vector)、固定技能文档 (skill)、OpenAPI 文档 (openapi) 和 CloudBase 官方文档 (docs) 查询。\n\n 强烈推荐始终优先使用固定技能文档 (skill)、OpenAPI 文档 (openapi) 或 CloudBase 官方文档 (docs) 模式进行检索,仅当固定文档无法覆盖你的问题时,再使用向量查询 (vector) 模式。\n\n ⚠️ 重要:当 CloudBase skills 处于禁用状态或当前 IDE 不支持 skill 文件读取时,必须使用 searchKnowledgeBase(mode=skill, skillName=...) 来获取 CloudBase 技能文档内容,而不是尝试直接读取 skill 文件。直接读取可能返回 400 错误。示例:\n - 需要 auth-tool 指南时:searchKnowledgeBase(mode=skill, skillName=auth-tool)\n - 需要 auth-web 指南时:searchKnowledgeBase(mode=skill, skillName=auth-web)\n - 需要 cloudbase-agent 指南时:searchKnowledgeBase(mode=skill, skillName=cloudbase-agent)\n\n 固定技能文档 (skill) 查询当前支持 25 个固定文档,分别是:\n 文档名:ai-model-nodejs 文档介绍:\"Use this skill for Node.js backend AI via @cloudbase/node-sdk (>=3.16.0) — cloud functions, CloudRun, Express, Koa, NestJS, serverless APIs, scheduled jobs, LLM proxies. Only SDK supporting image generation (ai.createImageModel + generateImage). Text models via ai.createModel with groups cloudbase, hunyuan-exp, or custom-*. Model IDs (deepseek-v4-flash, deepseek-v3.2, hunyuan-2.0-instruct-20251111, glm-5, kimi-k2.6) go in the model field of generateText/streamText. MUST run two-step preflight before code — see body. Keywords: backend, 云函数, 云托管, serverless, LLM proxy, agent orchestration, generateText, streamText, generateImage, createModel, hunyuan-image, Token Credits, TokenHub, Hunyuan, DeepSeek, GLM, Kimi, MiniMax. NOT for browser/Web (use ai-model-web) or Mini Program (use ai-model-wechat).\"\n文档名:ai-model-web 文档介绍:\"Use this skill when a browser/Web app (React, Vue, Angular, Next, Nuxt, static sites, SPAs, dashboards, AI chat UI) needs AI models via @cloudbase/js-sdk. Default routing for page/页面/Web/前端/frontend/网页/H5 AI — call directly from browser, do NOT propose a Node.js proxy. Covers generateText and streamText. Models via ai.createModel with groups cloudbase, hunyuan-exp, or custom-*. Model IDs (deepseek-v4-flash, deepseek-v3.2, hunyuan-2.0-instruct-20251111, glm-5, kimi-k2.6) go in the model field. MUST run two-step preflight before code — see body. Keywords: 页面, Web, 前端, React, Vue, Next, Nuxt, SPA, AI chat UI, generateText, streamText, createModel, hunyuan-exp, Token Credits, TokenHub, Hunyuan, DeepSeek, GLM, Kimi, MiniMax. NOT for Node.js backend (use ai-model-nodejs), Mini Program (use ai-model-wechat), or image generation (Node SDK only).\"\n文档名:ai-model-wechat 文档介绍:\"Use this skill for WeChat Mini Program AI via wx.cloud.extend.AI (小程序, 企业微信小程序, wx.cloud apps). Features generateText and streamText with callbacks (onText, onEvent, onFinish). Models via wx.cloud.extend.AI.createModel with groups hunyuan-exp (小程序成长计划), cloudbase (main managed), or custom-*. Model IDs (deepseek-v4-flash, deepseek-v3.2, hunyuan-2.0-instruct-20251111, glm-5, kimi-k2.6) go in the data wrapper model field. API differs from JS/Node SDK — streamText needs data wrapper, generateText returns raw response. MUST run two-step preflight before code — see body. Keywords: Mini Program AI, wx.cloud.extend.AI, 小程序成长计划, ai_miniprogram_inspire_plan, Token Credits 资源包, generateText, streamText, createModel, hunyuan-exp, TokenHub, Hunyuan, DeepSeek, GLM, Kimi, MiniMax. NOT for browser/Web (use ai-model-web), Node.js backend (use ai-model-nodejs), or image generation (use ai-model-nodejs).\"\n文档名:auth-nodejs 文档介绍:CloudBase Node SDK auth guide for server-side identity, user lookup, and custom login tickets. This skill should be used when Node.js code must read caller identity, inspect end users, or bridge an existing user system into CloudBase; not when configuring providers or building client login UI.\n文档名:auth-tool 文档介绍:CloudBase auth provider configuration and login-readiness guide. This skill should be used when users need to inspect, enable, disable, or configure auth providers, publishable-key prerequisites, login methods, SMS/email sender setup, or other provider-side readiness before implementing a client or backend auth flow.\n文档名:auth-web 文档介绍:CloudBase Web Authentication Quick Guide for frontend integration after auth-tool has already been checked. Provides concise and practical Web authentication solutions with multiple login methods and complete user management.\n文档名:auth-wechat 文档介绍:CloudBase WeChat Mini Program native authentication guide. This skill should be used when users need mini program identity handling, OPENID/UNIONID access, or `wx.cloud` auth behavior in projects where login is native and automatic.\n文档名:cloud-functions 文档介绍:CloudBase function runtime guide for building, deploying, and debugging your own Event Functions or HTTP Functions. This skill should be used when users need application runtime code on CloudBase, not when they are merely calling CloudBase official platform APIs.\n文档名:cloud-storage-web 文档介绍:Complete guide for CloudBase cloud storage using Web SDK (@cloudbase/js-sdk) - upload, download, temporary URLs, file management, and best practices.\n文档名:cloudbase-agent 文档介绍:Build and deploy AI agents with CloudBase Agent SDK (TypeScript & Python). Implements the AG-UI protocol for streaming agent-UI communication. Use when deploying agent servers, using LangGraph/LangChain/CrewAI adapters, building custom adapters, understanding AG-UI protocol events, or building web/mini-program UI clients. Supports both TypeScript (@cloudbase/agent-server) and Python (cloudbase-agent-server via FastAPI).\n文档名:cloudbase-cli 文档介绍:CloudBase CLI (tcb, 云开发CLI, Tencent CloudBase命令行) resource management skill. This skill should be used when users need to deploy cloud functions, manage CloudRun apps, upload files to storage, query NoSQL/MySQL databases, deploy static hosting, set access permissions, or configure CORS/domains/routing via tcb commands. Also use for CI/CD pipeline scripting, batch operations, terminal-based CloudBase management, or when the user prefers CLI over SDK/MCP.\n文档名:cloudbase-platform 文档介绍:CloudBase platform overview and routing guide. This skill should be used when users need high-level capability selection, platform concepts, console navigation, or cross-platform best practices before choosing a more specific implementation skill.\n文档名:cloudbase-wechat-integration 文档介绍:CloudBase WeChat integration guide for Mini Program WeChat Pay, Official Account JSAPI Pay, Native QR-code Pay, Official Account OAuth, openid handling, payment callbacks, and CloudBase Integration Center generated functions. This skill should be used when users ask to add, debug, or extend WeChat payment or official-account flows on CloudBase.\n文档名:cloudrun-development 文档介绍:CloudBase Run backend development rules (Function mode/Container mode). Use this skill when deploying backend services that require long connections, multi-language support, custom environments, or AI agent development.\n文档名:data-model-creation 文档介绍:Optional advanced tool for complex data modeling. For simple table creation, use relational-database-tool directly with SQL statements.\n文档名:http-api 文档介绍:CloudBase official HTTP API client guide. This skill should be used when backends, scripts, or non-SDK clients must call CloudBase platform APIs over raw HTTP instead of using a platform SDK or MCP management tool.\n文档名:miniprogram-development 文档介绍:WeChat Mini Program development skill for building, debugging, previewing, testing, publishing, and optimizing mini program projects. This skill should be used when users ask to create, develop, modify, debug, preview, test, deploy, publish, launch, review, or optimize WeChat Mini Programs, mini program pages, components, `tabBar`, routing, navigation, icon assets, project structure, project configuration, `project.config.json`, `appid` setup, device preview, real-device validation, WeChat Developer Tools workflows, `miniprogram-ci` preview/upload flows, or mini program release processes. It should also be used when users explicitly mention CloudBase, `wx.cloud`, Tencent CloudBase, 腾讯云开发, or 云开发 in a mini program project.\n文档名:no-sql-web-sdk 文档介绍:Use CloudBase document database Web SDK to query, create, update, and delete data. Supports complex queries, pagination, aggregation, realtime, and geolocation queries.\n文档名:no-sql-wx-mp-sdk 文档介绍:Use CloudBase document database WeChat MiniProgram SDK to query, create, update, and delete data. Supports complex queries, pagination, aggregation, and geolocation queries.\n文档名:ops-inspector 文档介绍:AIOps-style one-click inspection skill for CloudBase resources. Use this skill when users need to diagnose errors, check resource health, inspect logs, or run a comprehensive health check across cloud functions, CloudRun services, databases, and other CloudBase resources.\n文档名:relational-database-tool 文档介绍:This is the required documentation for agents operating on the CloudBase Relational Database through MCP. It defines the canonical SQL management flow with `querySqlDatabase`, `manageSqlDatabase`, `queryPermissions`, and `managePermissions`, including MySQL provisioning, destroy flow, async status checks, safe query execution, schema initialization, and permission updates.\n文档名:relational-database-web 文档介绍:Use when building frontend Web apps that talk to CloudBase Relational Database via @cloudbase/js-sdk – provides the canonical init pattern so you can then use Supabase-style queries from the browser.\n文档名:spec-workflow 文档介绍:Use when medium-to-large changes need explicit requirements, technical design, and task planning before implementation, especially for multi-module work, unclear acceptance criteria, or architecture-heavy requests.\n文档名:ui-design 文档介绍:Use when users need visual direction, interface hierarchy, layout decisions, design specifications, or prototypes before implementing a Web or mini program UI.\n文档名:web-development 文档介绍:Use when users need to implement, integrate, debug, build, deploy, or validate a Web frontend after the product direction is already clear, especially for React, Vue, Vite, browser flows, or CloudBase Web integration.\n\n OpenAPI 文档 (openapi) 查询当前支持 7 个 API 文档,分别是:\n API名:mysqldb API介绍:关系型数据库 RESTful API (MySQL/PostgreSQL) - 云开发关系型数据库 HTTP API\nAPI名:storage API介绍:Storage API - 云存储 HTTP API\nAPI名:nosql API介绍:NoSQL RESTful API - 文档型数据库 HTTP API\nAPI名:functions API介绍:Cloud Functions API - 云函数 HTTP API\nAPI名:ai_model API介绍:AI 大模型接入 API - 统一 AI 模型 HTTP API\nAPI名:auth API介绍:Authentication API - 身份认证 HTTP API\nAPI名:cloudrun API介绍:CloudRun API - 云托管服务 HTTP API", + "description": "云开发知识库智能检索工具,支持向量查询 (vector)、固定技能文档 (skill)、OpenAPI 文档 (openapi) 和 CloudBase 官方文档 (docs) 查询。\n\n 强烈推荐始终优先使用固定技能文档 (skill)、OpenAPI 文档 (openapi) 或 CloudBase 官方文档 (docs) 模式进行检索,仅当固定文档无法覆盖你的问题时,再使用向量查询 (vector) 模式。\n\n ⚠️ 重要:当 CloudBase skills 处于禁用状态或当前 IDE 不支持 skill 文件读取时,必须使用 searchKnowledgeBase(mode=skill, skillName=...) 来获取 CloudBase 技能文档内容,而不是尝试直接读取 skill 文件。直接读取可能返回 400 错误。示例:\n - 需要 auth-tool 指南时:searchKnowledgeBase(mode=skill, skillName=auth-tool)\n - 需要 auth-web 指南时:searchKnowledgeBase(mode=skill, skillName=auth-web)\n - 需要 cloudbase-agent 指南时:searchKnowledgeBase(mode=skill, skillName=cloudbase-agent)\n\n 固定技能文档 (skill) 查询当前支持 25 个固定文档,分别是:\n 文档名:ai-model-nodejs 文档介绍:\"Use this skill for Node.js backend AI via @cloudbase/node-sdk (>=3.16.0) — cloud functions, CloudRun, Express, Koa, NestJS, serverless APIs, scheduled jobs, LLM proxies. Only SDK supporting image generation (ai.createImageModel + generateImage). Text models via ai.createModel with groups cloudbase, hunyuan-exp, or custom-*. Model IDs (deepseek-v4-flash, deepseek-v3.2, hunyuan-2.0-instruct-20251111, glm-5, kimi-k2.6) go in the model field of generateText/streamText. MUST run two-step preflight before code — see body. Keywords: backend, 云函数, 云托管, serverless, LLM proxy, agent orchestration, generateText, streamText, generateImage, createModel, hunyuan-image, Token Credits, TokenHub, Hunyuan, DeepSeek, GLM, Kimi, MiniMax. NOT for browser/Web (use ai-model-web) or Mini Program (use ai-model-wechat).\"\n文档名:ai-model-web 文档介绍:\"Use this skill when a browser/Web app (React, Vue, Angular, Next, Nuxt, static sites, SPAs, dashboards, AI chat UI) needs AI models via @cloudbase/js-sdk. Default routing for page/页面/Web/前端/frontend/网页/H5 AI — call directly from browser, do NOT propose a Node.js proxy. Covers generateText and streamText. Models via ai.createModel with groups cloudbase, hunyuan-exp, or custom-*. Model IDs (deepseek-v4-flash, deepseek-v3.2, hunyuan-2.0-instruct-20251111, glm-5, kimi-k2.6) go in the model field. MUST run two-step preflight before code — see body. Keywords: 页面, Web, 前端, React, Vue, Next, Nuxt, SPA, AI chat UI, generateText, streamText, createModel, hunyuan-exp, Token Credits, TokenHub, Hunyuan, DeepSeek, GLM, Kimi, MiniMax. NOT for Node.js backend (use ai-model-nodejs), Mini Program (use ai-model-wechat), or image generation (Node SDK only).\"\n文档名:ai-model-wechat 文档介绍:\"Use this skill for WeChat Mini Program AI via wx.cloud.extend.AI (小程序, 企业微信小程序, wx.cloud apps). Features generateText and streamText with callbacks (onText, onEvent, onFinish). Models via wx.cloud.extend.AI.createModel with groups hunyuan-exp (小程序成长计划), cloudbase (main managed), or custom-*. Model IDs (deepseek-v4-flash, deepseek-v3.2, hunyuan-2.0-instruct-20251111, glm-5, kimi-k2.6) go in the data wrapper model field. API differs from JS/Node SDK — streamText needs data wrapper, generateText returns raw response. MUST run two-step preflight before code — see body. Keywords: Mini Program AI, wx.cloud.extend.AI, 小程序成长计划, ai_miniprogram_inspire_plan, Token Credits 资源包, generateText, streamText, createModel, hunyuan-exp, TokenHub, Hunyuan, DeepSeek, GLM, Kimi, MiniMax. NOT for browser/Web (use ai-model-web), Node.js backend (use ai-model-nodejs), or image generation (use ai-model-nodejs).\"\n文档名:auth-nodejs 文档介绍:CloudBase Node SDK auth guide for server-side identity, user lookup, and custom login tickets. This skill should be used when Node.js code must read caller identity, inspect end users, or bridge an existing user system into CloudBase; not when configuring providers or building client login UI.\n文档名:auth-tool 文档介绍:CloudBase auth provider configuration and login-readiness guide. This skill should be used when users need to inspect, enable, disable, or configure auth providers, publishable-key prerequisites, login methods, SMS/email sender setup, or other provider-side readiness before implementing a client or backend auth flow.\n文档名:auth-web 文档介绍:CloudBase Web Authentication Quick Guide for frontend integration after auth-tool has already been checked. Provides concise and practical Web authentication solutions with multiple login methods and complete user management.\n文档名:auth-wechat 文档介绍:CloudBase WeChat Mini Program native authentication guide. This skill should be used when users need mini program identity handling, OPENID/UNIONID access, or `wx.cloud` auth behavior in projects where login is native and automatic.\n文档名:cloud-functions 文档介绍:CloudBase function runtime guide for building, deploying, and debugging your own Event Functions or HTTP Functions. This skill should be used when users need application runtime code on CloudBase, not when they are merely calling CloudBase official platform APIs.\n文档名:cloud-storage-web 文档介绍:Complete guide for CloudBase cloud storage using Web SDK (@cloudbase/js-sdk) - upload, download, temporary URLs, file management, and best practices.\n文档名:cloudbase-agent 文档介绍:Build and deploy AI agents with CloudBase Agent SDK (TypeScript & Python). Implements the AG-UI protocol for streaming agent-UI communication. Use when deploying agent servers, using LangGraph/LangChain/CrewAI adapters, building custom adapters, understanding AG-UI protocol events, or building web/mini-program UI clients. Supports both TypeScript (@cloudbase/agent-server) and Python (cloudbase-agent-server via FastAPI).\n文档名:cloudbase-cli 文档介绍:CloudBase CLI (tcb, 云开发CLI, Tencent CloudBase命令行) resource management skill. This skill should be used when users need to deploy cloud functions, manage CloudRun apps, upload files to storage, query NoSQL/MySQL databases, deploy static hosting, set access permissions, or configure CORS/domains/routing via tcb commands. Also use for CI/CD pipeline scripting, batch operations, terminal-based CloudBase management, or when the user prefers CLI over SDK/MCP.\n文档名:cloudbase-platform 文档介绍:CloudBase platform overview and routing guide. This skill should be used when users need high-level capability selection, platform concepts, console navigation, or cross-platform best practices before choosing a more specific implementation skill.\n文档名:cloudbase-wechat-integration 文档介绍:CloudBase WeChat integration guide for Mini Program WeChat Pay, Official Account JSAPI Pay, Native QR-code Pay, Official Account OAuth, openid handling, payment callbacks, and CloudBase Integration Center generated functions. This skill should be used when users ask to add, debug, or extend WeChat payment or official-account flows on CloudBase.\n文档名:cloudrun-development 文档介绍:CloudBase Run backend development rules (Function mode/Container mode). Use this skill when deploying backend services that require long connections, multi-language support, custom environments, or AI agent development.\n文档名:data-model-creation 文档介绍:Optional advanced tool for complex data modeling. For simple table creation, use relational-database-tool directly with SQL statements.\n文档名:http-api 文档介绍:CloudBase official HTTP API client guide. This skill should be used when backends, scripts, or non-SDK clients must call CloudBase platform APIs over raw HTTP instead of using a platform SDK or MCP management tool.\n文档名:miniprogram-development 文档介绍:WeChat Mini Program development skill for building, debugging, previewing, testing, publishing, and optimizing mini program projects. This skill should be used when users ask to create, develop, modify, debug, preview, test, deploy, publish, launch, review, or optimize WeChat Mini Programs, mini program pages, components, `tabBar`, routing, navigation, icon assets, project structure, project configuration, `project.config.json`, `appid` setup, device preview, real-device validation, WeChat Developer Tools workflows, `miniprogram-ci` preview/upload flows, or mini program release processes. It should also be used when users explicitly mention CloudBase, `wx.cloud`, Tencent CloudBase, 腾讯云开发, or 云开发 in a mini program project.\n文档名:no-sql-web-sdk 文档介绍:Use CloudBase document database Web SDK to query, create, update, and delete data. Supports complex queries, pagination, aggregation, realtime, and geolocation queries.\n文档名:no-sql-wx-mp-sdk 文档介绍:Use CloudBase document database WeChat MiniProgram SDK to query, create, update, and delete data. Supports complex queries, pagination, aggregation, and geolocation queries.\n文档名:ops-inspector 文档介绍:AIOps-style one-click inspection skill for CloudBase resources. Use this skill when users need to diagnose errors, check resource health, inspect logs, or run a comprehensive health check across cloud functions, CloudRun services, databases, and other CloudBase resources.\n文档名:relational-database-tool 文档介绍:This is the required documentation for agents operating on the CloudBase Relational Database through MCP. It defines the canonical SQL management flow with `querySqlDatabase`, `manageSqlDatabase`, `queryPermissions`, and `managePermissions`, including MySQL provisioning, destroy flow, async status checks, safe query execution, schema initialization, and permission updates.\n文档名:relational-database-web 文档介绍:Use when building frontend Web apps that talk to CloudBase Relational Database via @cloudbase/js-sdk – provides the canonical init pattern so you can then use Supabase-style queries from the browser.\n文档名:spec-workflow 文档介绍:Use when medium-to-large changes need explicit requirements, technical design, and task planning before implementation, especially for multi-module work, unclear acceptance criteria, or architecture-heavy requests.\n文档名:ui-design 文档介绍:Use when users need visual direction, interface hierarchy, layout decisions, design specifications, or prototypes before implementing a Web or mini program UI.\n文档名:web-development 文档介绍:Use when users need to implement, integrate, debug, build, deploy, or validate a Web frontend after the product direction is already clear, especially for React, Vue, Vite, browser flows, or CloudBase Web integration.\n\n OpenAPI 文档 (openapi) 查询当前支持 7 个 API 文档,分别是:\n API名:mysqldb API介绍:关系型数据库 RESTful API (MySQL/PostgreSQL) - 云开发关系型数据库 HTTP API\nAPI名:functions API介绍:Cloud Functions API - 云函数 HTTP API\nAPI名:auth API介绍:Authentication API - 身份认证 HTTP API\nAPI名:cloudrun API介绍:CloudRun API - 云托管服务 HTTP API\nAPI名:storage API介绍:Storage API - 云存储 HTTP API\nAPI名:nosql API介绍:NoSQL RESTful API - 文档型数据库 HTTP API\nAPI名:ai_model API介绍:AI 大模型接入 API - 统一 AI 模型 HTTP API", "inputSchema": { "type": "object", "properties": { @@ -1565,12 +1565,12 @@ "type": "string", "enum": [ "mysqldb", - "storage", - "nosql", "functions", - "ai_model", "auth", - "cloudrun" + "cloudrun", + "storage", + "nosql", + "ai_model" ], "description": "mode=openapi 时指定。API 名称。" }, @@ -2223,7 +2223,7 @@ }, { "name": "queryApps", - "description": "CloudApp 域统一只读入口。可先查应用/版本,再在重新部署后用 listAppVersions 或 getAppVersion 按 serviceName 验证是否生成了新版本与最新构建状态。", + "description": "查询 CloudBase 应用部署的应用和版本。可查应用列表/详情、版本列表/详情;部署后用 getAppVersion 按 buildId 轮询构建状态;getBuildLog 可查询构建日志用于诊断失败原因。", "inputSchema": { "type": "object", "properties": { @@ -2233,12 +2233,14 @@ "listApps", "getApp", "listAppVersions", - "getAppVersion" - ] + "getAppVersion", + "getBuildLog" + ], + "description": "可填写的值: listApps, getApp, listAppVersions, getAppVersion, getBuildLog" }, "serviceName": { "type": "string", - "description": "CloudApp 服务名。getApp / listAppVersions / getAppVersion 时必填;重新部署后复用同一个 serviceName 查询版本历史。" + "description": "CloudBase 应用服务名。getApp / listAppVersions / getAppVersion / getBuildLog 时必填;重新部署后复用同一个 serviceName 查询版本历史。" }, "searchKey": { "type": "string", @@ -2258,7 +2260,11 @@ }, "buildId": { "type": "string", - "description": "构建 ID。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。" + "description": "构建 ID。getAppVersion 时可与 versionName 二选一;部署返回 BuildId 后可直接用它轮询状态。getBuildLog 时必填。" + }, + "start": { + "type": "number", + "description": "构建日志偏移量,用于分页拉取后续日志。仅 action=getBuildLog 时使用,不传时从开头返回。" } }, "required": [ @@ -2270,7 +2276,7 @@ }, { "name": "manageApps", - "description": "CloudApp 域统一写入口。action=deployApp 会先 uploadCode 再 createApp;首次调用创建应用,后续复用同一个 serviceName 会直接触发重新部署并生成新版本,无需先删除旧应用。", + "description": "部署 Web 应用到 CloudBase(构建前后端,部署到独立子域名)。\naction=getUploadUrl 获取预签名上传 URL(cloud mode 下使用),返回上传地址和 cosTimestamp。\naction=deployApp 上传源码 ZIP 并触发远端构建部署管道:\n 1. 远端 npm install(可通过 installCmd=\"\" 跳过)\n 2. 远端 npm run build(可通过 buildCmd=\"\" 跳过)\n 3. 远端 tcb hosting deploy\n部署完成后每个 CloudApp 有独立的 `*.webapps.tcloudbase.com` 子域名,也支持绑定自定义域名。\n\n✅ 推荐用法(新项目/需要独立域名的 Web 应用,首选此工具):\n 新建项目首次部署时,传 framework=static, installCmd=\"\", buildCmd=\"\" 跳过远端构建,\n 只执行 tcb hosting deploy。部署后获得独立子域名,支持版本管理。\n\n⚠️ 兼容性说明:\n- 已有项目若之前用 manageHosting 部署过(域名格式:-.tcloudbaseapp.com),\n 切换到 manageApps 会产生全新的 URL,老链接失效。请保持原部署方式不变。\n- 如需判断:调用 queryHosting 检查是否已有托管文件。\n\n与 manageHosting 对比:\n- manageApps(本工具,新项目首选):域名 -.webapps.tcloudbase.com,独立子域名,支持版本管理\n- manageHosting(已有项目或 fallback):域名 -.tcloudbaseapp.com/,共享环境域名\n两者均可绑定自定义域名。\n\n如果 manageApps 构建失败,先用 queryApps(action=\"getBuildLog\") 查日志;仍不行再 fallback 到 manageHosting。", "inputSchema": { "type": "object", "properties": { @@ -2278,25 +2284,31 @@ "type": "string", "enum": [ "deployApp", + "getUploadUrl", "deleteApp", "deleteAppVersion" - ] + ], + "description": "可填写的值: deployApp, getUploadUrl, deleteApp, deleteAppVersion" }, "serviceName": { "type": "string", - "description": "CloudApp 服务名。deployApp 时复用现有 serviceName 会新增一个部署版本并触发重新部署,而不是删除重建。" + "description": "CloudBase 应用服务名,会体现在域名中:`-.webapps.tcloudbase.com`。deployApp 时复用现有 serviceName 会新增一个部署版本并触发重新部署,而不是删除重建。首次部署请用新名称。" }, "filePath": { "type": "string", - "description": "要上传并部署的本地项目根目录或 zip 文件绝对路径。deployApp 时必填;通常传源码所在目录而不是 build 产物目录,构建产物目录请用 buildPath 指定。" + "description": "要上传并部署的本地项目根目录绝对路径。本地模式下 deployApp 时必填;通常传源码所在目录(含 package.json 和源码),不是 dist 目录。构建产物目录请用 buildPath 指定。cloud mode 下无需传此参数,改用 cosTimestamp。" + }, + "cosTimestamp": { + "type": "string", + "description": "cloud mode 下使用的 COS 时间戳。必须先调用 getUploadUrl 获取预签名 URL,上传 ZIP 包到该 URL 后,再将此时间戳传给 deployApp 以跳过本地文件上传。本地模式无需传此参数。" }, "appPath": { "type": "string", - "description": "应用线上访问路径(hosting mount path),例如 /my-web-app。不是本地目录路径;省略时默认为 /serviceName。" + "description": "应用线上访问路径(hosting mount path),例如 /my-web-app。不是本地目录路径;CloudApp 已有独立子域名,省略时默认为 /(根路径)。" }, "buildPath": { "type": "string", - "description": "构建产物目录,相对于 filePath,例如 dist 或 build。纯静态 HTML 如果入口文件直接在项目根目录,可省略。" + "description": "构建产物目录,相对于 filePath,例如 dist 或 build。\n传此值后远端构建系统会 cd 到此目录再执行 tcb hosting deploy,因此 deployCmd 会自动使用 .(当前目录)而非目录名,避免路径重复(如 dist/dist 错误)。\n纯静态 HTML 如果在项目根目录可省略,但注意 deployCmd 默认用 dist。" }, "framework": { "type": "string", @@ -2309,7 +2321,7 @@ "angular", "static" ], - "description": "前端框架类型。可选值:vue、react、next、nuxt、vite、angular、static;纯 HTML/静态站点请传 static。" + "description": "前端框架类型。可选值:vue、react、next、nuxt、vite、angular、static。\n即使传 static,仍会经过远端构建管道。如果本地已构建好,建议改用 manageHosting 直接上传,可完全跳过远端构建。" }, "nodeJsVersion": { "type": "string", @@ -2317,15 +2329,15 @@ }, "installCmd": { "type": "string", - "description": "依赖安装命令,例如 npm install;静态资源无需安装依赖时可省略。" + "description": "依赖安装命令,例如 npm install。不传时默认 npm install。本地已安装或无需安装可传空字符串 '' 跳过,但远端仍会执行 tcb hosting deploy。" }, "buildCmd": { "type": "string", - "description": "构建命令,例如 npm run build;纯静态 HTML 无构建步骤时可省略。" + "description": "构建命令,例如 npm run build。不传时默认 npm run build。本地已构建好可传空字符串 '' 跳过构建步骤。若希望完全跳过远端管道,请改用 manageHosting。" }, "deployCmd": { "type": "string", - "description": "自定义部署命令。通常无需填写,只有在默认部署步骤不满足要求时才传。" + "description": "自定义部署命令。通常无需填写,默认自动生成 tcb hosting deploy 命令。有 buildPath 时远端已 cd 到该目录,默认用 . 作为源码路径;无 buildPath 时默认用 dist。" }, "ignore": { "type": "array", @@ -2765,4 +2777,4 @@ } } ] -} \ No newline at end of file +} diff --git a/skills/codebase-audit/SKILL.md b/skills/codebase-audit/SKILL.md index d538ba39..18ac14b5 100644 --- a/skills/codebase-audit/SKILL.md +++ b/skills/codebase-audit/SKILL.md @@ -115,6 +115,7 @@ Use this skill when you need to: | Task | Read | | --- | --- | | What to review and how to check each category | `references/review-strategy.md` | +| Security severity classification (TSRC-style) | `references/security-severity-checklist.md` | | How to classify, deduplicate, and batch findings | `references/classification.md` | | How to create well-structured GitHub issues | `references/issue-workflow.md` | | How to create worktrees and fix issues in isolation | `references/worktree-fix.md` | diff --git a/skills/codebase-audit/references/classification.md b/skills/codebase-audit/references/classification.md index 74880c2d..63b1f151 100644 --- a/skills/codebase-audit/references/classification.md +++ b/skills/codebase-audit/references/classification.md @@ -5,10 +5,24 @@ | Severity | Criteria | SLA | |----------|----------|-----| | **Critical** | Security vulnerability exploitable by external input; data loss or corruption risk; authentication/authorization bypass | Must fix immediately | -| **High** | Runtime errors in normal usage; error handling gaps causing silent failures; resource leaks under load | Fix in current session | +| **High** | Runtime errors in normal usage; error handling gaps causing silent failures; resource leaks under load | Must fix in current session | | **Medium** | Type safety holes; code quality issues affecting maintainability; API inconsistencies | Fix if time allows | | **Low** | Style issues; naming; minor cleanup; documentation gaps | Defer or batch with other work | +## Security finding severity mapping + +For security-specific findings, use the detailed TSRC-style checklist in `references/security-severity-checklist.md`. It maps each vulnerability type to a severity tier with concrete technical conditions: + +| TSRC Grade | Audit Severity | Example Vulnerabilities | +|------------|---------------|----------------------| +| 严重 (Critical) | **Critical** | RCE with server access, platform-level info leak, cash drain >¥100K | +| 高 (High) | **High** | Stored XSS (platform), SQL injection (readable), unauthorized admin, SSRF w/ response | +| 中 (Medium) | **Medium** | Stored XSS (needs interaction), blind SSRF, local code exec, single-API IDOR | +| 低 (Low) | **Low** | XSS in obsolete browsers, open redirect, rate limiting gaps | +| 无 (None) | **Ignore** | Self-XSS, no-impact CSRF, scanner noise | + +When classifying a security finding, first assign its TSRC grade using the condition-based checklist in `security-severity-checklist.md`, then map to the audit severity above. + ## Deduplication rules Many findings are instances of the **same pattern** across different files. Deduplicate aggressively: diff --git a/skills/codebase-audit/references/review-strategy.md b/skills/codebase-audit/references/review-strategy.md index 8ac72db2..1fa54971 100644 --- a/skills/codebase-audit/references/review-strategy.md +++ b/skills/codebase-audit/references/review-strategy.md @@ -21,17 +21,27 @@ For **every** file, check each category below. Not every category applies to eve ### 1. Security (Critical priority) -| Check | What to look for | -|-------|-----------------| -| Path traversal | User-controlled paths not validated with `path.resolve` + prefix check | -| Command injection | String interpolation in `exec()`, `execSync()`, shell commands | -| SQL/NoSQL injection | Unparameterized queries with user input | -| Hardcoded secrets | API keys, tokens, passwords in source code | -| Improper error exposure | Stack traces, internal paths, or secrets in error messages returned to clients | -| Missing input validation | Tool parameters accepted without type/range/format checks | -| Prototype pollution | Unchecked `Object.assign`, spread of user-controlled objects | -| SSRF | User-controlled URLs fetched without allowlist validation | -| Vulnerable dependencies | Known CVEs in direct or transitive dependencies (see `dependency-audit.md`) | +For severity classification of security findings, reference `security-severity-checklist.md` which maps each vulnerability type to TSRC-style severity tiers (Critical/High/Medium/Low/Ignore) with concrete technical conditions. + +| Check | What to look for | Min Severity | +|-------|-----------------|-------------| +| Path traversal | User-controlled paths not validated with `path.resolve` + prefix check | HIGH | +| Command injection | String interpolation in `exec()`, `execSync()`, shell commands | CRITICAL | +| SQL/NoSQL injection | Unparameterized queries with user input | HIGH (readable data) | +| Hardcoded secrets | API keys, tokens, passwords in source code | MEDIUM | +| Improper error exposure | Stack traces, internal paths, or secrets in error messages returned to clients | MEDIUM | +| Missing input validation | Tool parameters accepted without type/range/format checks | HIGH | +| Prototype pollution | Unchecked `Object.assign`, spread of user-controlled objects | HIGH | +| SSRF | User-controlled URLs fetched without allowlist validation | MEDIUM (blind) / HIGH (with response) | +| Vulnerable dependencies | Known CVEs in direct or transitive dependencies (see `dependency-audit.md`) | Varies | +| Unauthorized data access | Missing auth/ownership checks on user data APIs | HIGH | +| Open redirect | Unvalidated redirect parameters | LOW | +| XSS (stored) | User content rendered without sanitization | HIGH (platform products) / MEDIUM (others) | +| XSS (reflected/DOM) | Input reflected in response without encoding | LOW / MEDIUM | +| CSRF | Missing anti-CSRF tokens on state-changing operations | MEDIUM | +| Arbitrary file read/write | Path traversal in file operations | HIGH (write) / MEDIUM (read) | +| IDOR / privilege escalation | User-controlled IDs without ownership validation | HIGH (core features) / MEDIUM (single endpoint) | +| Rate limiting defects | Missing throttling on auth/sms/OTP endpoints | LOW | ### 2. Error handling (High priority) diff --git a/skills/codebase-audit/references/security-severity-checklist.md b/skills/codebase-audit/references/security-severity-checklist.md new file mode 100644 index 00000000..dc4d06d8 --- /dev/null +++ b/skills/codebase-audit/references/security-severity-checklist.md @@ -0,0 +1,535 @@ +# Security Vulnerability Severity Classification Checklist + +TSRC-style severity checklist for classifying security findings during codebase audits. Each vulnerability type is mapped to a severity tier with concrete technical conditions. + +## Severity Mapping (TSRC → Audit) + +| TSRC | Audit | Action | +|------|-------|--------| +| 严重 (Critical) | **Critical** | Immediate fix, individual batch | +| 高 (High) | **High** | Fix in current session, batch by type | +| 中 (Medium) | **Medium** | Fix if time allows | +| 低 (Low) | **Low** | Defer or batch | +| 无 (None) | **Ignore** | Record as informational, no action | + +--- + +## CRITICAL (严重) + +### C1 — Remote Code Execution / Command Injection + +Check for any user-controlled input reaching system execution paths. + +**Conditions (all must be met):** +- [ ] Vulnerability grants direct server OS / core product client permission access +- [ ] Falls into one of: remote arbitrary command execution, arbitrary code execution, SQL injection yielding system-level exec permissions, buffer overflow, root/elevated privilege escalation, cluster privilege escalation +- [ ] Exfiltrates server-level critical credentials (AK/SK/certificates/keys that can log into CVM) + +**If any condition fails → downgrade to High.** + +Checklist: +- [ ] `exec()`, `execSync()`, `spawn()` with unsanitized user input +- [ ] `eval()`, `new Function()`, `setTimeout(string)` with user input +- [ ] Template engine SSTI (`res.render()` with user data in template path) +- [ ] Shell command string interpolation in `child_process` calls +- [ ] SQL injection leading to `xp_cmdshell` or equivalent OS command execution +- [ ] Buffer overflow reachable from external input +- [ ] Deserialization of untrusted data (`JSON.parse` on unvalidated input, `eval`-based deserializers) + +### C2 — Mass Information Leakage (Platform-Level) + +Only for platform-scale systems (QQ, WeChat, Honor of Kings or equivalent). + +**Conditions (all must be met):** +- [ ] Target is a platform/ecosystem-level product with >100M users +- [ ] Leaked information can compromise user identity security +- [ ] No significant barriers to exploitation + +Checklist: +- [ ] Bulk user identity data accessible without authentication +- [ ] Database dump containing PII of large user base exposed +- [ ] API endpoint returns full user profiles without auth checks + +### C3 — Severe Logic Flaws + +**Conditions (meet one):** +- [ ] Allows impersonating arbitrary user identity and sending fully customizable messages to arbitrary recipients +- [ ] Enables credential changes for arbitrary accounts +- [ ] Mass identity forgery in chat/transaction scenarios — bulk impersonation with arbitrary content send + +Checklist: +- [ ] OAuth token forgery allowing full message send as any user +- [ ] Password reset API without identity verification +- [ ] JWT authentication bypass (weak secret, `none` algorithm, expired token reuse) +- [ ] Session fixation allowing account takeover +- [ ] Chat/transaction API allows specifying arbitrary sender identity in messages + +**Exclusion:** Popup/alert-only exploits that cannot specify readable content → downgraded. + +### C4 — Direct Cash Drain + +**Conditions (all must be met):** +- [ ] Directly withdrawable cash (not virtual goods/points) +- [ ] No utilization restrictions +- [ ] Impact amount > ¥100,000 (CNY) + +Checklist: +- [ ] Payment logic bypass allowing direct cash extraction +- [ ] Balance manipulation that can be converted to cash +- [ ] Recharge/refund logic reversal + +--- + +## HIGH (高) + +### H1 — Stored XSS (High-Impact Products) + +For QQ Zone, QQ Mail, Enterprise Mail, Web WeChat, WeChat Official Account products. + +**Conditions:** +- [ ] Stored XSS with low-interaction propagation path affecting large user base +- [ ] Can be verified (use `console.log` — **not** blind, not page-destroying payloads) + +Checklist: +- [ ] User-generated content rendered without sanitization (comments, profiles, signatures) +- [ ] Markdown/HTML/rich-text fields not sanitized on output +- [ ] File upload filenames reflected in HTML without encoding +- [ ] JSONP callback parameter not sanitized + +**Note:** QQ Mail / Enterprise Mail (`*.mail.com`, `*.exmail.com`) sharing the same frontend template: only the first report of the same XSS path across multiple subdomains is valid. + +### H2 — SQL Injection (Readable Data) + +**Conditions:** +- [ ] Can read database table column names +- [ ] Can directly read user data (not just "first character of first row") + +**Exclusion:** Only able to read first character of first column → unreliable, demoted. + +Checklist: +- [ ] Unparameterized SQL queries with string interpolation +- [ ] Dynamic `ORDER BY` / `LIMIT` / `OFFSET` with user input +- [ ] Stored procedure calls with concatenated parameters +- [ ] ORM raw query methods with string formatting (e.g. `sequelize.query()`, `knex.raw()`) +- [ ] NoSQL injection in MongoDB `$where`, `$regex` with user input + +### H3 — Unauthorized Admin Access + +**Conditions:** +- [ ] Can access management console/admin panel without authorization +- [ ] Can perform management functions (not just read-only) + +Rated based on: active user base (≥1000 users), feature criticality, user data sensitivity, product importance. + +Checklist: +- [ ] Admin dashboard accessible without authentication +- [ ] Role/permission bypass (e.g. tampering role header/cookie) +- [ ] Direct object reference to admin API endpoints +- [ ] Missing authorization check on management APIs + +### H4 — High-Risk Information Leakage + +**Conditions (all must be met):** +- [ ] ≥3 distinct sensitive PII fields leaked (real name, ID number, address, phone, WeChat/QQ, bank card, full transaction records, medical info) +- [ ] Data volume exceeds threshold (scaled by business importance) + +Checklist: +- [ ] API endpoint returns full user profile without field filtering +- [ ] Logging system stores PII in readable format +- [ ] Backup/S3 bucket with public read containing PII +- [ ] GraphQL introspection + unauthenticated query returning sensitive fields +- [ ] Debug endpoint exposing database records in production +- [ ] CSV/Excel download with unrestricted user data export + +### H5 — Remote Client Code Execution + +**Conditions:** +- [ ] Can execute arbitrary commands (prove with `ipconfig` or `id` output) +- [ ] Not limited to launching built-in system programs + +Checklist: +- [ ] Browser use-after-free (UAF) in core product client +- [ ] Remote kernel code execution +- [ ] Electron/CEF `nodeIntegration: true` + `contextIsolation: false` + XSS +- [ ] Protocol handler injection in desktop clients +- [ ] Logic-error-based remote code execution + +### H6 — SSRF With Response (Internal Network) + +**Conditions:** +- [ ] Can target Tencent internal network (not just public endpoints) +- [ ] Response is returned to the attacker (not blind) +- [ ] Test domains: `http://tst.woa.com/flag.html` (domain), `http://9.138.237.216/flag.html` (IP) + +**Scoring:** +- Full response returned → High 7 points +- Partial response (limited chars) → High 6 points +- Image-only response → Medium 4 points +- Blind/no response → Medium 3 points + +Checklist: +- [ ] User-controlled URL fetched server-side without allowlist +- [ ] URL validation bypass using `@`, `#`, DNS rebinding, IPv6 variants +- [ ] Protocol smuggling (`file://`, `gopher://`, `dict://`) +- [ ] Cloud metadata service accessible via SSRF (169.254.169.254) +- [ ] Internal service discovery via response timing/error differences + +### H7 — Privilege Escalation / IDOR (Core Features) + +**Conditions (meet one):** +- [ ] Unauthorized access to another user's full account or all core functions +- [ ] Batch traversal of large user data (videos/images/live streams/CDN configs/models/training data) +- [ ] Zero-interaction mass group chat creation + +Checklist: +- [ ] User ID/object ID in API parameters not validated for ownership +- [ ] Missing `user_id` check in database queries +- [ ] Auto-increment IDs used for sensitive resources without auth +- [ ] WebSocket messages processed without session validation +- [ ] Role/group membership assignment without authorization + +### H8 — Resource Draining / Infinite Free Usage + +**Conditions (meet one):** +- [ ] Unlimited free usage of large-scale core cloud resources +- [ ] Access to core production data +- [ ] Active exploit causing significant financial loss + +Checklist: +- [ ] Pricing/billing logic bypass for major resources +- [ ] Coupon/discount logic not validated server-side +- [ ] Rate limiting missing on paid resource provisioning +- [ ] Subscription downgrade doesn't properly restrict access + +### H9 — XSS in Core Client Products + +- [ ] **Core** client product (desktop/mobile app handling sensitive data/logins) +- [ ] Can exfiltrate sensitive information or perform sensitive operations +- [ ] Rating: High 6 points; wormable → bonus upgrade + +**Distinction:** This covers **core** client products. XSS in non-core client products that can still exfiltrate info → Medium (M2). + +### H10 — Permanent Client DoS + +- [ ] Core product client only +- [ ] Client rendered permanently unusable (requires reinstall) +- [ ] Not just crash/restart (temporary DoS → Medium) + +### H11 — Virtual Goods Drain (>¥5000) + +**Conditions (all must be met):** +- [ ] Single user can arbitrarily acquire/transfer virtual goods with cash value (memberships, points, in-game RMB items, no-limit coupons, free cloud servers) +- [ ] Impact value > ¥5000 +- [ ] Outside business/product intended behavior + +Checklist: +- [ ] Recharge/point balance manipulation +- [ ] In-game item duplication or theft +- [ ] Coupon/voucher unlimited generation +- [ ] Subscription benefit bypass allowing resource monopolization + +### H12 — Arbitrary File Read/Write (Full Scope) + +- [ ] COS/cloud storage arbitrary file read/write covering entire bucket +- [ ] File overwrite affecting the whole site + +Checklist: +- [ ] File path traversal in download/read API +- [ ] File upload with insufficient path validation allowing overwrite +- [ ] COS bucket policy too permissive for server-side operations + +--- + +## MEDIUM (中) + +### M1 — Stored XSS (Requires Interaction) + +- [ ] Stored XSS but requires user interaction (e.g., clicking a link) to trigger +- [ ] CSRF on important/sensitive operations + +Checklist: +- [ ] Stored XSS in non-critical application (not platform-level) +- [ ] CSRF on password change, payment, or data export +- [ ] CSRF token missing or static +- [ ] CORS misconfiguration allowing credentialed cross-origin writes + +### M2 — Remote DoS + +- [ ] Remote application denial of service (no user interaction required) +- [ ] Kernel-level DoS +- [ ] Client XSS that can exfiltrate info or perform sensitive ops (non-core client product only; core client → H9) + +**Exclusion:** Requires user interaction → Low. + +Checklist: +- [ ] Unbounded resource consumption via API (large queries, expensive computations) +- [ ] Regex ReDoS with user-controllable input +- [ ] Infinite loop triggerable via malformed input +- [ ] Memory exhaustion via file upload or data payload + +### M3 — Information Leakage (Moderate) + +Checklist: +- [ ] Passwords stored in plaintext in client-side code +- [ ] QQ password transmitted in plaintext +- [ ] Source code archive containing sensitive config leaked +- [ ] `.env`, `.git`, `.svn`, `node_modules` exposed on production server +- [ ] `phpinfo()` or debug endpoints accessible in production +- [ ] Server error pages exposing full file paths and config + +### M4 — Subdomain Takeover + +- [ ] `*.qq.com` or `*.tencent.com` subdomain vulnerable to takeover + +Checklist: +- [ ] DNS CNAME pointing to unclaimed cloud service (S3, Heroku, GitHub Pages, Azure) +- [ ] Expired cloud service still has DNS record pointing to it +- [ ] Third-party service (Statuspage, Zendesk, etc.) deprovisioned but CNAME remains +- [ ] Can verify via `dig CNAME ` + check service availability + +### M5 — OAuth Login/Binding Hijack (Click Required) + +- [ ] User must click link; not zero-interaction + +Checklist: +- [ ] OAuth `redirect_uri` not validated or insufficiently validated +- [ ] CSRF token in OAuth flow missing or static (login CSRF) +- [ ] OAuth `state` parameter not validated +- [ ] Account binding API without confirming current session ownership + +### M6 — Blind SSRF + +- [ ] Can reach Tencent internal network but no response returned +- [ ] Server executes request but response not observable + +### M7 — Local Code Execution + +**Conditions:** +- [ ] Locally exploitable (requires existing access to user machine) + +Checklist: +- [ ] Stack/heap overflow in native code +- [ ] Use-after-free / double-free +- [ ] Format string vulnerability +- [ ] Local privilege escalation (user → Administrator/System, default client config) +- [ ] File association DLL hijacking +- [ ] Logic-error-based local code execution + +**Exclusions (not valid):** +- Local DLL hijacking (no remote vector) +- Loading non-existent DLL +- Loading DLL without integrity check +- Requires admin privileges +- Requires extensive user interaction +- KnownDLLs-based hijacking + +### M8 — Mini Program Key Leak (Limited Impact) + +- [ ] WeChat Mini Program secret key leaked +- [ ] Cannot prove exploitation of high-impact APIs (send customer messages, Tencent Cloud API write operations) + +**If exploitable for high-impact operations → upgrade to High.** + +### M9 — Single-Function IDOR + +- [ ] Non-core functionality, single API endpoint + +### M10 — Non-Critical SQL Injection + +- [ ] Non-core business database +- [ ] Leaks minimal or non-sensitive data + +### M11 — Arbitrary File Read (Read-Only) + +- [ ] Can read files but cannot write +- [ ] Or can manipulate non-sensitive files with no further exploitation path + +### M12 — Test System Server Compromise + +- [ ] Cannot reach production/internal Tencent network + +**If cannot get server permissions → ignore.** + +### M13 — JSONP Hijacking (Sensitive Data) + +- [ ] Callback parameter can be manipulated +- [ ] Returns sensitive user data + +Checklist: +- [ ] JSONP endpoint returns user PII without referer/csrf token +- [ ] Callback function name controllable allowing arbitrary function execution + +### M14 — Local Database Injection + +- [ ] SQLite / IndexedDB injection on client side +- [ ] Can cause info leak or other harm + +### M15 — Feature Abuse / Discount Bypass + +- [ ] Basic-tier user can access premium features via API +- [ ] Limited discounts repeatedly bypassed (but can only obtain specific non-core resources) +- [ ] Usage restrictions have bypassable conditions + +**Downgrade if:** no real profit gained, limited-impact edge functionality. + +--- + +## LOW (低) + +### L1 — XSS (Obsolete Browser Only) + +- [ ] Only works in specific non-mainstream or obsolete browsers (IE and older versions not accepted) + +Checklist: +- [ ] Stored XSS in niche browser only +- [ ] Reflected XSS in niche browser only +- [ ] DOM clobbering requiring old browser + +### L2 — Minor Information Leakage + +Checklist: +- [ ] Non-sensitive source code leaked on GitHub +- [ ] SVN `.svn/entries` exposed +- [ ] `phpinfo()` without sensitive data +- [ ] `logcat` with non-sensitive information +- [ ] Valid internal network credentials leaked (but no access yet) + +### L3 — URL Redirect / Open Redirect + +- [ ] `qq.com`, `tencent.com`, `wechat.com` subdomains + +**Proof requirement:** +- [ ] Must demonstrate redirect to `http://www.qq.com/521_qq_diao_yu_wangzhan_789.com` +- [ ] Direct redirect (no intermediate hops, no user warnings) +- Otherwise → not accepted as a vulnerability. + +Checklist: +- [ ] `redirect_url`, `next`, `return_to` parameters not validated +- [ ] Referer-based redirect can be spoofed +- [ ] Meta-refresh with user-controlled URL +- [ ] `window.location` assignment with untrusted input + +### L4 — Hard-to-Exploit Issues + +Checklist: +- [ ] Reflected XSS with propagation/exploitation difficulty (mail XSS excluded) +- [ ] DOM-based XSS (not stored, not reflected) +- [ ] SQL injection in test system (hard to exploit) +- [ ] MITM-required RCE with valid PoC +- [ ] Client memory modification affecting single game skill/property + +### L5 — Rate Limiting / Brute Force Defects + +Checklist: +- [ ] SMS bombing bypass (single number receiving 50+ messages in 3 minutes) +- [ ] Account password brute-force without rate limiting +- [ ] OTP/TOTP no rate limiting or cooldown +- [ ] CAPTCHA bypass re-usable across requests + +### L6 — Non-Security Bugs + +- [ ] Page rendering issues +- [ ] Page unavailability +- [ ] Feature malfunction + +--- + +## IGNORE / NON-ISSUE (无) + +### N1 — Unusable "Vulnerabilities" + +Checklist: +- [ ] Scanner-generated report without context (e.g., "Web Server version too low") +- [ ] Self-XSS (requires user to paste attacker's code themselves) +- [ ] JSON hijacking without sensitive data +- [ ] CSRF on non-sensitive operations (bookmarks, cart add, non-important order, profile edit) +- [ ] Meaningless source code leakage +- [ ] Meaningless concurrency issues +- [ ] IDOR with no impact on other users +- [ ] Internal IP address / domain leakage +- [ ] 401 basic auth phishing (not a vulnerability) +- [ ] Program path disclosure +- [ ] Non-sensitive logcat information + +### N2 — Low-Risk / Hard-to-Exploit + +Checklist: +- [ ] PDF XSS +- [ ] Email bombing +- [ ] Prank CSRF (single logout, Self-CSRF) +- [ ] DLL hijacking without remote exploitation path +- [ ] Username enumeration +- [ ] SPF email spoofing (without proof of delivery to spam folder) +- [ ] SSRF that cannot reach internal network +- [ ] API key leakage for non-critical resources +- [ ] Local DoS (crashes app locally on user's own machine) +- [ ] "Log4j2" with only DNSlog evidence (no actual exploitation demonstrated) + +### N3 — No Evidence + +- [ ] Self-claim ("my QQ was hacked therefore vulnerability") +- [ ] Cannot reproduce after multiple attempts with auditor + +### N4 — Business-as-Design + +- [ ] Expected business behavior within operational tolerance +- [ ] Cannot cause financial loss +- [ ] Multiple accounts claiming small rewards as intended business activity + +### N5 — Out of Scope + +- [ ] Not a Tencent business vulnerability +- [ ] Not a bug in Tencent product itself + +### N6 — Under Active Remediation + +- [ ] System currently in penetration testing /专项排查 +- [ ] Check TSRC暂停收录列表 first + +### N7 — Intended RCE + +- [ ] Product-expected code execution (e.g., sandbox, REPL, code runner tools) +- [ ] Container/test environment controllable execution + +--- + +## Quick Reference: Severity Decision Tree + +``` +Is it exploitable without user interaction? +├── YES +│ ├── Remote code/command execution? +│ │ ├── Server permission gained + credentials exfiltrated? → CRITICAL +│ │ └── Server permission gained (no credential exfil)? → HIGH +│ ├── Data exfiltration? +│ │ ├── Platform-level + user identity info? → CRITICAL +│ │ ├── ≥3 PII fields + significant volume? → HIGH +│ │ └── Minor/non-sensitive data? → MEDIUM or LOW +│ ├── Bypass auth/privilege? +│ │ ├── Full account takeover, mass traversal? → HIGH +│ │ └── Single endpoint, non-core? → MEDIUM +│ ├── Direct cash drain? +│ │ ├── >¥100K? → CRITICAL +│ │ ├── >¥5000 virtual goods? → HIGH +│ │ └── <¥5000 or non-cash? → MEDIUM +│ └── DoS? +│ ├── Permanent, core client? → HIGH +│ └── Remote DoS (no interaction)? → MEDIUM +├── NO (requires interaction) +│ ├── Stored XSS (click required)? → MEDIUM +│ ├── CSRF on sensitive operation? → MEDIUM +│ ├── Click + OAuth hijack? → MEDIUM +│ └── XSS in obsolete browser only? → LOW +└── NOT EXPLOITABLE / NO IMPACT → IGNORE +``` + +## Usage in Codebase Audit + +When reviewing source code: + +1. **Identify** the vulnerability pattern from the checklists above +2. **Classify** the severity using the conditions and decision tree +3. **Record** the TSRC grade as an annotation (e.g., `[TSRC: HIGH]`) alongside the audit severity +4. **Assign** the mapped audit severity for the fix batch priority + +For findings where conditions are ambiguous, default to the lower severity and note the uncertainty.