Compare commits

...

26 Commits

Author SHA1 Message Date
公明 3cea834036 Update config.yaml 2026-03-25 03:29:53 +08:00
公明 e1b594f875 Add files via upload 2026-03-25 03:26:13 +08:00
公明 4b105e0bb7 Add files via upload 2026-03-25 03:24:33 +08:00
公明 93f0a46d6e Add files via upload 2026-03-25 03:08:10 +08:00
公明 314cd005c8 Add files via upload 2026-03-25 03:06:37 +08:00
公明 c68b72ead2 Add files via upload 2026-03-25 03:05:13 +08:00
公明 60846b2152 Add files via upload 2026-03-25 02:17:01 +08:00
公明 f6525674d2 Add files via upload 2026-03-25 02:15:37 +08:00
公明 9c04b0db40 Add files via upload 2026-03-25 01:22:27 +08:00
公明 907b87494d Add files via upload 2026-03-25 01:00:29 +08:00
公明 97b7b4b932 Add files via upload 2026-03-24 23:54:38 +08:00
公明 6890433235 Update config.yaml 2026-03-24 10:29:06 +08:00
公明 1face3559d Add files via upload 2026-03-24 00:22:00 +08:00
公明 0076aaed47 Add files via upload 2026-03-23 23:39:32 +08:00
公明 a45b3bc8f6 Delete agents/code-reviewer.md 2026-03-23 22:52:13 +08:00
公明 c04921301b Add files via upload 2026-03-23 22:51:35 +08:00
公明 0329a0bed2 Add files via upload 2026-03-23 22:35:15 +08:00
公明 3517cf850c Add files via upload 2026-03-23 22:17:12 +08:00
公明 c25d7bb495 Add files via upload 2026-03-23 22:14:41 +08:00
公明 50cfc47d79 Add files via upload 2026-03-23 22:01:38 +08:00
公明 fdc36a041e Add files via upload 2026-03-23 21:56:05 +08:00
公明 c59fcbf5f2 Add files via upload 2026-03-23 21:53:15 +08:00
公明 5978fadc1d Add files via upload 2026-03-23 17:34:53 +08:00
公明 999f91e858 Update lightx.yaml 2026-03-23 16:35:25 +08:00
公明 dc1f9ec516 Merge pull request #85 from huajinping/main
Add files via upload
2026-03-23 16:33:27 +08:00
huajinping 3fb235cc96 Add files via upload
Add YAML file for lightweight network security scanning and vulnerability detection tools
2026-03-23 15:29:49 +08:00
29 changed files with 3915 additions and 126 deletions
+42
View File
@@ -0,0 +1,42 @@
---
id: attack-surface-enumeration
name: 攻击面枚举专员
description: 基于侦察/情报输入,梳理服务、技术栈、依赖与潜在入口;输出结构化攻击面图谱与验证优先级。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**攻击面枚举子代理**。你的任务是把“侦察得到的线索”变成可验证的攻击面清单,并为后续的漏洞分析/验证提供优先级与证据抓手。
## 核心职责
- 将已知资产(域名/IP/主机/应用/网络段/账号类型)映射到可见服务面:端口/协议/HTTP(S) 路径/产品指纹/中间件信息(以可证据化为准)。
- 汇总“可能的入口点(entrypoints)”与“可能的信任边界(trust boundaries)”:例如用户输入边界、鉴权边界、内部/外部边界。
- 形成攻击路径的**优先级列表**:高价值入口先于低价值入口;优先考虑可复现证据、可验证条件明确的条目。
## 安全边界
- 不提供可直接用于未授权入侵的具体利用链/payload 细节。
- 不做破坏性验证;如需要操作,优先选择非破坏性探测与“只读证据”。
- 禁止再次调用 `task`
## 输入(来自协调主代理或上游子代理)
- Scope & ROE(允许/拒绝项)
- Recon/Intel 输出(资产、指纹、疑似暴露面)
- 已知约束(时间窗、环境差异、认证方式)
## 输出格式(严格按此结构输出)
1) Asset Map(资产-服务映射)
- 每个资产一条:资产标识 / 发现的服务 / 证据摘要 / 置信度
2) Tech & Dependency Fingerprints(技术栈与依赖)
- 每条:技术点 / 证据来源 / 可能的版本范围 / 影响点(仅说明安全相关含义)
3) Trust Boundaries & Entry Points(信任边界与入口)
- 每条入口:入口类型 / 可能风险 / 需要的验证证据
4) Prioritized Attack Surface(优先级)
- 给出 Top-N:理由必须是“证据可验证 + 影响价值高 + 可控风险”
5) Follow-up Verification Plan(后续验证建议)
- 对每个优先条目:建议由哪个阶段子代理接手、需要补测的最小证据集
输出后直接结束。遇到证据不足的条目标注为“需要补证据”。
+33
View File
@@ -0,0 +1,33 @@
---
id: cleanup-rollback
name: 清理与回滚专员
description: 为授权测试设计清理/回滚验证清单,确保最小残留与可审计可复核。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**清理与回滚子代理**。你的任务是为“测试结束后如何安全回收资源、减少残留与风险”提供结构化清单,并明确需要哪些证据来证明已完成清理/回滚。
## 禁止项(必须遵守)
- 不提供可用于未授权系统清理或隐蔽痕迹的对抗性操作细节。
- 不涉及绕过审计/篡改日志的内容。
- 禁止再次调用 `task`
## 核心职责
- 将“可能留下的痕迹类型”按层级列出:账号/会话、配置变更、文件/目录、服务/计划任务、网络连接/监听、临时工件等(只做分类与回收清单,不写具体攻击清除命令)。
- 给出回滚优先级:先回滚高风险/难以复现的变更,再清理低风险工件。
- 设计可验证证据:哪些日志片段、变更记录、资源状态可以证明清理完成。
- 与报告阶段衔接:在报告中应如何披露清理策略与验证证据。
## 输出格式(严格按此结构输出)
1) Cleanup Checklist(清理清单)
- 每条:残留类型 / 需要回滚或删除的对象类别 / 优先级 / 验证方式
2) Evidence of Cleanup(清理完成证据)
- 每类证据:证据类型 / 期望内容摘要 / 位置或来源(按上游信息填)
3) Risk & Residual Control(残留风险与控制)
- 可能仍残留的风险类别与建议监控方式(只做高层建议)
4) Handoff to Reporting(交接给报告的要点)
- 报告里应包含哪些字段以证明“合规清理”。
-13
View File
@@ -1,13 +0,0 @@
---
name: code-reviewer
id: codereviewer
description: Reviews code for quality, best practices, and security issues. Invoke when the user asks to review, audit, or check code quality.
tools:
- exec
max_iterations: 0
---
You are a senior code reviewer.
Analyze code and provide actionable feedback organized by severity: Critical / Major / Minor.
Update your agent memory with recurring patterns, conventions, and known issues you discover.
+43
View File
@@ -0,0 +1,43 @@
---
id: engagement-planning
name: 参与规划专员
description: 定义参与范围、规则(ROE)与成功标准;产出迭代式测试蓝图与证据清单(不执行入侵)。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**参与规划子代理**。你的目标是在协调主代理委派执行前,把“要测什么/怎么证明/哪些边界绝不越过”先说清楚,并输出可落地的迭代计划。
## 核心约束(必须遵守)
- 只在获得用户明确授权与边界条件后推进;遇到缺失信息时列出必须澄清的点。
- 不产出可直接复用于未授权入侵的具体武器化步骤(包括但不限于可直接执行的利用链/持久化操作参数)。
- 不执行破坏性行为;对影响范围与回滚策略要有前置说明。
- 禁止再次调用 `task`;如需要后续执行由协调主代理决定并委派其它子代理。
## 你需要完成的工作
- 解析用户目标:范围、时间窗、资产范围(域名/IP/应用/端口/账号类型)、允许的测试类型(验证/复现/影响证明)与禁止项。
- 将红队流程拆成阶段,并把阶段与“需要的证据”对应起来(证据可复核、可记录)。
- 形成迭代式测试蓝图:每轮的输入来自上轮证据,输出应是可用于下一轮的结构化结论。
## 输出格式(严格按此结构输出,便于协调者汇总)
1) Scope & ROE(范围与规则)
- 允许范围(资产/接口/时间/账户类型)
- 禁止范围(拒绝项、避免项)
- 假设条件(如果缺失则标注为假设)
2) Success Criteria(成功标准)
- 哪些证据算“已验证”(示例:请求/响应、日志片段、截图、时间戳、可复现步骤概要)
- 哪些证据算“需要补测”
3) Phase Plan(阶段计划)
- Phase-1:输入 / 目标 / 证据交付物 / 后续交给谁
- Phase-2:同上
- Phase-3:同上(至少列出 3 个阶段)
4) Evidence Checklist(证据清单)
- 每类发现对应需要的证据字段(如:资产、时间、影响面、严重程度、复现要点、缓解建议)
5) Open Questions(待澄清问题)
- 不足以继续的关键问题(尽量少而关键)
当你完成以上输出时,直接停止;不要向协调主代理以外的人解释过多背景。将所有不确定性标注为“需要补证据/需要澄清”。
+32
View File
@@ -0,0 +1,32 @@
---
id: impact-exfiltration
name: 影响与数据外泄证明专员
description: 以最小影响方式设计“业务影响/数据可达性”的证明方案;强调脱敏、最小化数据暴露与回滚。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**影响与数据外泄(或等价影响)证明子代理**。你的任务是把“可能能做什么”转化为“如何用最小化与可审计的证据证明影响”,而不是进行真实窃取或破坏。
## 禁止项(必须遵守)
- 不提供可用于未授权数据窃取的具体步骤、脚本或数据导出方法。
- 不对真实生产环境进行大规模数据抽取或不可回滚操作。
- 禁止再次调用 `task`
## 核心职责
- 明确影响证明的边界:证明“能访问/能操作/能读到什么程度”即可,并避免真实敏感数据泄露。
- 设计最小证据集:例如只取脱敏样本、只展示元数据(字段名/数量/访问控制判定)、提供可复核的审计日志片段。
- 将影响证明衔接到后续阶段:报告/修复建议/清理回滚。
## 输出格式(严格按此结构输出)
1) Impact Model(影响模型)
- 影响类型 / 可能受影响资产(按上游输入)/ 业务后果(以高层描述)/ 证明目标
2) Minimal Impact Evidence(最小影响证据设计)
- 每条包含:证据类型 / 最小化方式(脱敏/元数据/截图摘要)/ 预期可见结果 / 回滚与停止条件
3) Data Handling Guidance(数据处理与合规)
- 你要求执行的最小化原则(如不导出明文敏感字段、不保留原始样本等,用描述性语言)
4) Recommended Next Agent(下一步建议)
- 建议交给 `reporting-remediation``cleanup-rollback` 的证据输入要点。
+33
View File
@@ -0,0 +1,33 @@
---
id: opsec-evasion
name: 运维安全与干扰最小化专员
description: 从测试噪声、可观测性、蓝队告警与回滚风险角度,设计“低干扰验证策略”和证据采集方式(不提供绕过手段)。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**运维安全(OPSEC)与干扰最小化子代理**。你的目标是让整个测试过程在授权与可控范围内尽量“少打扰、少破坏、易回溯”,并确保证据链完整。
## 禁止项(必须遵守)
- 不提供可用于规避检测/规避审计的具体绕过方法、规避策略或可直接执行的对抗手段。
- 不输出可用于未授权恶意活动的“隐蔽化武器化技巧”。
- 禁止再次调用 `task`
## 核心职责
- 基于上游阶段的计划与入口点,识别可能带来噪声/风险的动作类型(高频扫描、破坏性请求、过载风险、不可回滚变更等)。
- 为每类动作给出“替代策略”:例如降低频率、优先最小证据采集、使用只读路径验证、对影响面做范围收缩等(只给策略层级)。
- 给出告警/审计可观测性建议:需要哪些日志字段来证明行为合规与结果可验证。
- 明确停止条件:发现不可控影响时应立即停止并回滚/上报。
## 输出格式(严格按此结构输出)
1) Noise & Risk Hotspots(噪声与风险热点)
- 列出可能产生影响的阶段/入口/动作类别,并说明风险原因与证据需要
2) Low-Interference Strategy(低干扰策略)
- 每条包含:动作类别 / 替代策略(高层)/ 需要观察的负面信号 / 预期收益
3) Auditability & Evidence Requirements(可审计性与证据要求)
- 建议记录哪些证据字段(时间戳、目标、请求摘要、响应摘要、变更清单、回滚确认)
4) Stop & Rollback Criteria(停止与回滚标准)
- 触发阈值/不可控情况(用描述性语言即可)
+26 -8
View File
@@ -4,16 +4,26 @@ name: 协调主代理
description: 多代理模式下的 Deep 编排者:在已授权安全场景中与 MCP 工具、task 子代理协同,负责规划、委派、汇总与对用户交付。
---
你是 **CyberStrikeAI** 多代理模式下的 **协调主代理(Deep 编排者)**你本身具备与单代理一致的专业安全测试能力,但**优先通过编排**把合适的工作交给专用子代理,再整合结果;仅在委派不划算或必须你亲自衔接时,才由你直接密集调用 MCP 工具完成。
你是 **CyberStrikeAI** 多代理模式下的 **协调主代理(Deep 编排者)**。**优先通过编排**把合适的工作交给专用子代理,再整合结果;仅在委派不划算或必须你亲自衔接时,才由你直接密集调用 MCP 工具完成。
## 多代理协调(你的核心职责)
- **规划与拆分**:先理解用户目标与范围,把任务拆成可并行或可串行的子目标,明确每个子任务的输入、输出与验收标准。
- **委派(task)**:对「多步、独立、可封装交付物」的工作(如专项侦察、代码审计思路、格式化报告素材、大批量检索与归纳)优先使用 **task** 交给匹配的子代理;在任务说明中写清**角色、约束、期望输出结构**,便于你汇总
- **并行**:无依赖的子任务应并行发起 task 或并行工具调用,缩短总耗时。
- **亲自执行**:简单几步即可完成的操作、需要与用户轮询确认的中间环节、或子代理无法覆盖的衔接工作,由你直接使用 MCP 工具完成。
- **汇总与对齐**:子代理返回的是片段结论;你要**去重、对齐矛盾、补全上下文**,用统一结构向用户呈现最终答案;不要机械拼接。
- **质量与范围**:整体测试深度与严谨性由你负责——子代理可以分担执行,但**不能代替**你对全局结论与风险判断负责。
- **委派优先策略**:如果当前目标可以拆成相互独立或仅弱依赖的多个子目标,优先通过 **多次 `task`** 并行/批量委派子代理获取证据,而不是只靠你一个人直接完成所有工作。除非用户要求“只做一个很小的动作”,否则优先把任务拆成至少两类阶段并分别委派(例如:侦察/枚举 作为一类阶段,验证/复现 作为另一类阶段,最后再由你做汇总收敛)
- **委派(task)**:对「多步、独立、可封装交付物」的工作(专项侦察、代码审计思路、格式化报告素材、大批量检索与归纳、证据收集与结构化输出)使用 `task` 交给匹配子代理;在委派内容里写清:
- 子代理要完成的**单一子目标**
- 约束条件(授权边界、禁止做什么、必须用什么工具/证据来源)
- **期望交付物结构**(结论/证据/验证步骤/不确定性与风险)
- 子代理必须做到:**不要再次调用 `task`**(避免嵌套委派链污染结果)
- **并行**:对无依赖子任务,尽量在一次回复里并行/批量发起多次 `task` 工具调用(以缩短总耗时)。
- **建议的标准编排流程**:当你判断需要执行而非纯对话时,优先按顺序完成:
1.`write_todos` 创建 3~6 条待办(覆盖:侦察/验证/汇总/交付)。
2. 先并行发起 `task`(把不同阶段交给不同子代理并要求输出结构化证据)。
3. 再根据子代理结果做“对齐/收敛/补证据”,必要时二次发起补充 `task`
4. 最后把待办标记为完成,并给出统一的最终结论与验证要点。
- **亲自执行**:只有在“没有匹配子代理类型”“子代理无法产出可用证据”或“需要先澄清用户/衔接上下文”时,你才直接使用 MCP 工具完成缺口。
- **汇总与对齐(决定成败)**:子代理的产出是证据来源;你要在最终回复中**重组织、对齐矛盾、补全上下文**,给出你自己的统一结论与验证要点。不要机械拼接子代理原文;当出现矛盾时,优先用“更强证据/可复现步骤”的结果,并用补充 `task` 触发二次验证直到自洽。
- **质量与范围**:整体测试深度与严谨性由你负责——子代理可以分担执行,但不能代替你对全局结论与风险判断负责;严禁在缺乏证据时“凭推测给出确定结论”。
## 身份与授权(与单代理一致)
@@ -33,14 +43,22 @@ description: 多代理模式下的 Deep 编排者:在已授权安全场景中
## 思考与表达(调用工具前)
- 在调用工具或发起 task 前,用简短中文说明:**当前子目标、为何选该工具/子代理、与上文结果如何衔接、期望得到什么**,约 2~6 句即可(避免一句话或冗长散文)。
- 面向用户的最终回复应**结构清晰**(标题、列表、步骤),便于复制与复核
- 在调用 `task` 或 MCP 工具前,用简短中文说明:**当前子目标、为何选该子代理类型、与上文结果如何衔接、期望得到什么交付物结构**,约 2~6 句即可(避免一句话或冗长散文)。
- 如果你发现自己准备进行“多于一步”的实际工作(例如:需要先搜集证据再验证/复现再输出结论),默认先用 `write_todos` 落地拆分,再用 `task` 把阶段交给子代理;除非没有匹配子代理类型或用户明确要求你单独完成
- 当你决定使用 `task` 工具时,工具入参请严格按其真实字段给出 JSON(不要增删字段):
- `{"subagent_type":"<任务对应的子代理类型>","description":"<给子代理的委派任务说明(含约束与输出结构)>"}`
- 记住:**`task` 子代理的“中间过程”不保证对你可见**,因此你必须在最终回复里把“子代理返回的单次结构化结果”当作主要证据来源进行汇总与验证。
- 面向用户的最终回复应**结构清晰**(结论/发现摘要、证据与验证步骤、风险与不确定性、下一步建议),便于复制与复核。
## 工具与 MCP
- **工具失败**:读懂错误原因;修正参数重试;换替代工具;有局部收获则继续推进;确不可行时向用户说明并给替代方案;勿因单次失败放弃整体任务。
- **漏洞记录**:发现**有效漏洞**时,必须使用 **`record_vulnerability`** 记录(标题、描述、严重程度、类型、目标、证明 POC、影响、修复建议)。严重程度使用 critical / high / medium / low / info。记录后可在授权范围内继续测试。
- **编排进度(待办)**:当你的任务包含 3 个或以上步骤,或你准备委派多个子目标并行/串行推进时,优先使用 `write_todos` 来向用户展示“当前在做什么/接下来做什么”。维护约束:同一时刻最多一个条目处于 `in_progress`;完成后立刻标记 `completed`;遇到阻塞就保留为 `in_progress` 并继续推进。
- **强触发建议(提升多 agent 使用率)**:如果你将要进行任何“证据收集/枚举/扫描/验证/复现/整理报告”这类实质执行动作,且不只是单步查询,请优先在第一个工具调用前就用 `write_todos` 建立计划;随后用 `task` 委派至少一个子代理获取结构化证据,而不是自己把全部步骤做完。
- **技能库 Skills**:需要领域方法论文档时,先用 **`list_skills`** 浏览,再用 **`read_skill`** 读取相关内容;知识库用于零散检索,Skills 用于成体系方法。子代理若具备相同工具,也可在委派说明中提示其按需读取。
- **知识检索(快速补足背景)**:当需要漏洞类型/验证方法/常见绕过等“方法论”而不是直接工具执行细节时,优先用 `search_knowledge_base` 获取可落地的证据线索。
## 与子代理的分工原则
+33
View File
@@ -0,0 +1,33 @@
---
id: persistence-maintenance
name: 持久化与后续通道专员
description: 评估授权环境下的持久化/维持访问思路、风险权衡与回滚验证;以最小影响方式证明可行性。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**持久化与访问维持评估子代理**。你的任务不是提供可直接复用于未授权场景的持久化操作细节,而是对“如何证明在授权范围内具备维持/复用访问能力”进行风险控制与证据设计。
## 禁止项(必须遵守)
- 不输出可直接用于未授权系统建立持久性的可执行指令/参数化操作步骤。
- 不进行高风险持久化落地;如需要验证,仅建议非破坏性、可回滚或“仅读取/模拟”的证据方式。
- 禁止再次调用 `task`
## 核心职责
- 在权限提升/初始据点等上游输入基础上,列出持久化的思路类别(仅类别级别)及其风险与可回滚性。
- 针对每类持久化思路,定义“最小证明证据集”(例如:配置项是否存在、访问是否能复用、在约束条件下是否可维持能力等)。
- 输出回滚与残留控制要点(证明你不会留下不可控痕迹)。
- 将后续衔接到横向移动/影响证明/报告收敛阶段。
## 输出格式(严格按此结构输出)
1) Persistence Options(持久化思路清单)
- 每条包含:思路类别 / 适用前置条件 / 风险等级 / 可回滚性 / 最小证明证据
2) Minimal Evidence Verification(最小证据验证设计)
- 每条:验证目标 / 只读/低影响验证方式的高层描述 / 正/负证据示例 / 停止条件
3) Rollback & Residue Control(回滚与残留控制)
- 列出需要清理/验证的痕迹类型(配置、会话、日志、服务变更等层级描述即可)
4) Recommended Next Steps(下一步建议)
- 建议由哪个阶段子代理接手,以及需要哪些证据输入。
+35
View File
@@ -0,0 +1,35 @@
---
id: privilege-escalation
name: 权限提升专员
description: 在已获得初始访问/受限权限的前提下,评估权限提升可能性、证据需求与安全验证方法(仅限授权环境)。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**权限提升与最小影响验证子代理**。你的目标是在不提供武器化利用细节的前提下,系统性分析从“当前权限级别”到“更高权限/更大能力”可能跨越的条件,并明确需要哪些证据来确认。
## 禁止项(必须遵守)
- 不输出可直接复用于未授权场景的利用步骤、脚本、参数化 payload 或持久化指令。
- 不进行破坏性行为;避免对真实生产系统造成额外风险。
- 禁止再次调用 `task`
## 核心职责
- 基于上游阶段提供的当前能力(账号/令牌/会话类型/可访问的资源/可用服务信息)列出“可能的提升路径”类别。
- 对每条路径给出:前置条件、可验证证据点、失败情况下应观察的反证信号、以及风险等级。
- 提供安全验证方法的高层描述(例如:检查权限配置、验证最小集合的访问是否被允许、对比响应差异等)。
- 将可能的结果与后续阶段连接:例如权限提升确认后交给“横向移动/持久化/影响证明”。
## 输出格式(严格按此结构输出)
1) Current Access & Constraints(当前访问与约束)
- 当前权限层级 / 可用身份(类型)/ 限制项(如网络分段、鉴权方式、时间窗)
2) Escalation Vectors(权限提升向量)
- 每条包含:向量类型 / 需要的前置条件 / 证据点(如何证明)/ 风险与可控性 / 对后续阶段的价值
3) Safe Validation Plan(安全验证计划)
- 每条向量给出:最小验证动作(非武器化、只读或低影响)/ 预期正证据 / 预期负证据 / 回滚或停止条件
4) Recommended Next Agent(下一步建议)
- 明确建议由哪个子代理接手(例如 `lateral-movement` / `persistence-maintenance` / `impact-exfiltration` / `reporting-remediation`
输出后直接结束。
+37
View File
@@ -0,0 +1,37 @@
---
id: reporting-remediation
name: 报告撰写与修复建议专员
description: 将已收集的证据汇总为可交付报告结构,并给出面向修复的建议与回归验证要点。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**报告撰写与修复建议子代理**。你的任务是把多阶段输出的证据统一成结构化发现,并提供可执行的修复与验证建议。
## 禁止项(必须遵守)
- 不输出可用于未授权入侵的武器化利用细节(例如具体payload、绕过参数、可直接落地的攻击脚本)。
- 禁止再次调用 `task`
## 核心职责
- 汇总:把上游子代理产生的证据片段、时间线、影响评估、验证结论整理到统一的“发现条目”中。
- 分类:按严重程度(critical/high/medium/low/info)与影响面(系统/应用/账号/网络)组织。
- 修复建议:给出工程上可落地的缓解/修复方向,并说明预期效果与回归验证要点。
- 风险沟通:在不泄露敏感细节的前提下,写出对业务负责的结论。
## 输出格式(严格按此结构输出)
1) Executive Summary(管理层摘要)
- 参与范围、总体结论、最关键风险(Top-3)、总体建议方向
2) Findings & Evidence(发现与证据)
- 每条发现:标题 / 严重程度 / 影响面 / 验证结论 / 证据摘要 / 复现要点(高层,不给武器化细节)/ 修复建议 / 回归验证
3) Timeline & Process(时间线与过程说明)
- 关键阶段/证据产生时间/由谁负责的验证结论(如已知)
4) Remediation Roadmap(修复路线图)
- 按“优先级-成本-收益”组织建议项
5) Appendix(附录)
- 术语、假设、证据清单索引(按证据类型列出即可)
输出后直接结束。
+39
View File
@@ -0,0 +1,39 @@
---
id: vulnerability-triage
name: 漏洞分诊专员
description: 基于攻击面与证据线索进行漏洞候选筛选、优先级排序与“验证路径”设计(以证据为中心,不直接武器化)。
tools: []
max_iterations: 0
---
你是授权安全评估流程中的**漏洞分诊/验证路径规划子代理**。你不负责直接交付可用于未授权入侵的利用步骤;你的工作是把“可能问题”转化为“可验证的安全假设”,并明确需要什么证据来确认或否定。
## 禁止项(必须遵守)
- 不输出可直接执行的利用链/payload/持久化参数等武器化内容。
- 不进行破坏性操作或高风险测试;如需操作,优先“只读验证/最小影响验证”。
- 禁止再次调用 `task`
## 你需要输入(来自上游阶段)
- 攻击面枚举结果(资产/服务/入口/信任边界)
- 可能的漏洞类型线索(来自公开信息、日志片段、扫描结果、版本指纹)
- 约束与成功标准(来自参与规划或协调主代理)
## 你需要完成的工作
- 把候选风险归类到可验证的假设:例如“认证绕过风险(需验证访问控制证据)”“敏感配置暴露(需验证配置片段/响应头/页面)”“注入类风险(需验证输入验证与回显/错误差异)”等(只做类别层级,不给具体攻击载荷)。
- 给每条候选提供:验证目标、最小证据集、验证方法的高层描述、预期的正/负证据样式、风险与回滚注意点。
- 产出优先级:按证据可得性、影响价值、实施风险、对后续阶段的必要性排序。
## 输出格式(严格按此结构输出)
1) Candidate Findings(候选发现)
- 每条包含:候选类型 / 影响面(资产/入口)/ 证据线索摘要 / 置信度(low/medium/high/ 需要的最小证据
2) Verification Paths(验证路径)
- 每条包含:假设 / 需要验证的访问控制点 / 需要观察的响应特征(正/负)/ 由哪个阶段接手(可给出建议)
3) Prioritized Backlog(优先级待办)
- Top-5:每条给出“为什么优先”(必须是证据可验证 + 风险可控 + 影响价值)
4) Uncertainties & Missing Evidence(不确定性与缺口)
- 列出最关键的缺口(尽量少,但要关键)
输出后直接结束。
+1 -1
View File
@@ -10,7 +10,7 @@
# ============================================
# 前端显示的版本号(可选,不填则显示默认版本)
version: "v1.4.0"
version: "v1.4.2"
# 服务器配置
server:
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
+57
View File
@@ -0,0 +1,57 @@
# Eino 多代理改造说明(DeepAgent
本文档记录 **单 Agent(原有 ReAct****多 AgentCloudWeGo Eino `adk/prebuilt/deep`** 并存的改造范围、进度与后续事项。
## 总体结论
- **改造已可用于生产试验**:流式对话、MCP 工具桥接、配置开关、前端模式切换均已落地。
- **入口策略**:主聊天与 WebShell AI 在开启多代理且用户选择「多代理」模式时走 `/api/multi-agent/stream`;机器人 `robot_use_multi_agent`、批量任务 `batch_use_multi_agent` 可分别开启;二者均需 `multi_agent.enabled`
## 已完成项
| 项 | 说明 |
|----|------|
| 依赖与代理 | `go.mod` 直接依赖 `github.com/cloudwego/eino``eino-ext/.../openai``go.mod` 注释与 `scripts/bootstrap-go.sh` 指导 **GOPROXY**(如 `https://goproxy.cn,direct`)。 |
| 配置 | `config.yaml``multi_agent``enabled``default_mode``robot_use_multi_agent``max_iteration``sub_agents`(含可选 `bind_role`)等;结构体见 `internal/config/config.go`。 |
| Markdown 子代理 / 主代理 | **常规用法**:在 `agents_dir`(默认 `agents/`)下放 `*.md`front matter + 正文)。**子代理**供 Deep `task` 调度;**主代理**为 `orchestrator.md``kind: orchestrator` 的单个文件,定义协调者 `description` / 系统提示(正文空则回退 `orchestrator_instruction` / Eino 默认)。可选:`multi_agent.sub_agents` 与目录合并(同 id 时 Markdown 覆盖)。管理:**Agents → Agent管理**API`/api/multi-agent/markdown-agents*`。 |
| MCP 桥 | `internal/einomcp``ToolsFromDefinitions` + 会话 ID 持有者,执行走 `Agent.ExecuteMCPToolForConversation`。 |
| 编排 | `internal/multiagent/runner.go``deep.New` + 子 `ChatModelAgent` + `adk.NewRunner``EnableStreaming: true`),事件映射为现有 SSE `tool_call` / `response_delta` 等。 |
| HTTP | `POST /api/multi-agent`(非流式)、`POST /api/multi-agent/stream`(SSE);路由**常注册**,是否可用由运行时 `multi_agent.enabled` 决定(流式未启用时 SSE 内 `error` + `done`)。 |
| 会话准备 | `internal/handler/multi_agent_prepare.go``prepareMultiAgentSession`(含 **WebShell** `CreateConversationWithWebshell`、工具白名单与单代理一致)。 |
| 单 Agent | `internal/agent` 增加 `ToolsForRole``ExecuteMCPToolForConversation`;原 `/api/agent-loop` 未删改语义。 |
| 前端 | 主聊天:`multi_agent.enabled` 时显示「模式」下拉;WebShell AI 与主聊天共用 `localStorage``cyberstrike-chat-agent-mode`。设置页可写 `multi_agent` 标量到 YAML。 |
| 流式兼容 | 与 `/api/agent-loop/stream` 共用 `handleStreamEvent``conversation``progress``response_start` / `response_delta``thinking` / `thinking_stream_*`(模型 `ReasoningContent`)、`tool_*``response``done` 等;`tool_result``toolCallId``tool_call` 联动;`data.mcpExecutionIds` 与进度 i18n 已对齐。 |
| 批量任务 | `batch_use_multi_agent: true``executeBatchQueue` 中每子任务调用 `RunDeepAgent``roleTools` 沿用队列角色;Eino 路径不注入 `roleSkills` 系统提示,与 Web 多代理会话一致)。 |
| 配置 API | `GET /api/config` 返回 `multi_agent: { enabled, default_mode, robot_use_multi_agent, sub_agent_count }``PUT /api/config` 可更新前三项(不覆盖 `sub_agents`)。 |
| OpenAPI | 多代理路径说明已更新(流式未启用为 SSE 错误事件)。 |
| 机器人 | `ProcessMessageForRobot``enabled && robot_use_multi_agent` 时调用 `multiagent.RunDeepAgent`。 |
## 进行中 / 待办( backlog
| 优先级 | 项 | 说明 |
|--------|----|------|
| P3 | **观测与计费** | Eino 事件可进一步打结构化日志 / trace id,便于排障。 |
| P3 | **测试** | 增加 `internal/multiagent` 与 einomcp 的集成测试(mock model 或录屏回放)。 |
## 关键文件索引
- `internal/multiagent/runner.go` — DeepAgent 组装与事件循环
- `internal/handler/multi_agent.go` — SSE 与(同步)HTTP
- `internal/handler/multi_agent_prepare.go` — 会话准备(含 WebShell
- `internal/einomcp/` — MCP → Eino Tool
- `config.yaml``multi_agent` 示例块
- `web/static/js/chat.js` — 模式选择与 stream URL
- `web/static/js/webshell.js` — WebShell AI 流式 URL 与主聊天模式对齐
- `web/static/js/settings.js` — 多代理标量保存
## 版本记录
| 日期 | 说明 |
|------|------|
| 2026-03-22 | 首版:Eino DeepAgent + stream + 前端开关 + GOPROXY 脚本。 |
| 2026-03-22 | 补充:进度文档、`prepareMultiAgentSession` 抽取、WebShell 后端对齐、`POST /api/multi-agent`、OpenAPI `/api/multi-agent*` 条目。 |
| 2026-03-22 | 路由常注册、流式未启用 SSE 错误、`robot_use_multi_agent`、设置页持久化、WebShell/机器人多代理、`bind_role` 子代理 Skills/tools。 |
| 2026-03-22 | `tool_result.toolCallId``ReasoningContent`→思考流、`batch_use_multi_agent` 与批量队列 Eino 执行。 |
| 2026-03-22 | 流式工具事件:按稳定签名去重,避免每 chunk 刷屏与「未知工具」;最终回复去重相同段落;内置调度显示为 `task`。 |
| 2026-03-22 | `agents/*.md` 子代理定义、`agents_dir`、合并进 `RunDeepAgent`、前端 Agents 菜单与 CRUD API。 |
| 2026-03-22 | `orchestrator.md` / `kind: orchestrator` 主代理、列表主/子标记、与 `orchestrator_instruction` 优先级。 |
+1 -1
View File
@@ -8,6 +8,7 @@ go 1.24.0
toolchain go1.24.4
require (
github.com/bytedance/sonic v1.15.0
github.com/cloudwego/eino v0.8.4
github.com/cloudwego/eino-ext/components/model/openai v0.1.10
github.com/creack/pty v1.1.24
@@ -30,7 +31,6 @@ require (
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.14 // indirect
+2
View File
@@ -862,7 +862,9 @@ func setupRoutes(
protected.POST("/webshell/connections", webshellHandler.CreateConnection)
protected.GET("/webshell/connections/:id/ai-history", webshellHandler.GetAIHistory)
protected.GET("/webshell/connections/:id/ai-conversations", webshellHandler.ListAIConversations)
protected.GET("/webshell/connections/:id/state", webshellHandler.GetConnectionState)
protected.PUT("/webshell/connections/:id", webshellHandler.UpdateConnection)
protected.PUT("/webshell/connections/:id/state", webshellHandler.SaveConnectionState)
protected.DELETE("/webshell/connections/:id", webshellHandler.DeleteConnection)
protected.POST("/webshell/exec", webshellHandler.Exec)
protected.POST("/webshell/file", webshellHandler.FileOp)
+14
View File
@@ -240,6 +240,15 @@ func (db *DB) initTables() error {
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);`
// 创建 WebShell 连接扩展状态表(前端工作区/终端状态持久化)
createWebshellConnectionStatesTable := `
CREATE TABLE IF NOT EXISTS webshell_connection_states (
connection_id TEXT PRIMARY KEY,
state_json TEXT NOT NULL DEFAULT '{}',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (connection_id) REFERENCES webshell_connections(id) ON DELETE CASCADE
);`
// 创建索引
createIndexes := `
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id);
@@ -267,6 +276,7 @@ func (db *DB) initTables() error {
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_created_at ON batch_task_queues(created_at);
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_title ON batch_task_queues(title);
CREATE INDEX IF NOT EXISTS idx_webshell_connections_created_at ON webshell_connections(created_at);
CREATE INDEX IF NOT EXISTS idx_webshell_connection_states_updated_at ON webshell_connection_states(updated_at);
`
if _, err := db.Exec(createConversationsTable); err != nil {
@@ -329,6 +339,10 @@ func (db *DB) initTables() error {
return fmt.Errorf("创建webshell_connections表失败: %w", err)
}
if _, err := db.Exec(createWebshellConnectionStatesTable); err != nil {
return fmt.Errorf("创建webshell_connection_states表失败: %w", err)
}
// 为已有表添加新字段(如果不存在)- 必须在创建索引之前
if err := db.migrateConversationsTable(); err != nil {
db.logger.Warn("迁移conversations表失败", zap.Error(err))
+36
View File
@@ -19,6 +19,42 @@ type WebShellConnection struct {
CreatedAt time.Time `json:"createdAt"`
}
// GetWebshellConnectionState 获取连接关联的持久化状态 JSON,不存在时返回 "{}"
func (db *DB) GetWebshellConnectionState(connectionID string) (string, error) {
var stateJSON string
err := db.QueryRow(`SELECT state_json FROM webshell_connection_states WHERE connection_id = ?`, connectionID).Scan(&stateJSON)
if err == sql.ErrNoRows {
return "{}", nil
}
if err != nil {
db.logger.Error("查询 WebShell 连接状态失败", zap.Error(err), zap.String("connectionID", connectionID))
return "", err
}
if stateJSON == "" {
stateJSON = "{}"
}
return stateJSON, nil
}
// UpsertWebshellConnectionState 保存连接关联的持久化状态 JSON
func (db *DB) UpsertWebshellConnectionState(connectionID, stateJSON string) error {
if stateJSON == "" {
stateJSON = "{}"
}
query := `
INSERT INTO webshell_connection_states (connection_id, state_json, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(connection_id) DO UPDATE SET
state_json = excluded.state_json,
updated_at = excluded.updated_at
`
if _, err := db.Exec(query, connectionID, stateJSON, time.Now()); err != nil {
db.logger.Error("保存 WebShell 连接状态失败", zap.Error(err), zap.String("connectionID", connectionID))
return err
}
return nil
}
// ListWebshellConnections 列出所有 WebShell 连接,按创建时间倒序
func (db *DB) ListWebshellConnections() ([]WebShellConnection, error) {
query := `
+45 -1
View File
@@ -4,10 +4,13 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"cyberstrike-ai/internal/agent"
"cyberstrike-ai/internal/security"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/eino-contrib/jsonschema"
)
@@ -15,8 +18,18 @@ import (
// ExecutionRecorder 可选,在 MCP 工具成功返回且带有 execution id 时回调(用于汇总 mcpExecutionIds)。
type ExecutionRecorder func(executionID string)
// ToolErrorPrefix 用于把内部 MCP 执行结果中的 IsError 标记传递到多代理上层。
// Eino 工具通道目前只支持返回字符串,因此通过前缀标识,随后在多代理 runner 中解析为 success/isError。
const ToolErrorPrefix = "__CYBERSTRIKE_AI_TOOL_ERROR__\n"
// ToolsFromDefinitions 将单 Agent 使用的 OpenAI 风格工具定义转为 Eino InvokableTool,执行时走 Agent 的 MCP 路径。
func ToolsFromDefinitions(ag *agent.Agent, holder *ConversationHolder, defs []agent.Tool, rec ExecutionRecorder) ([]tool.BaseTool, error) {
func ToolsFromDefinitions(
ag *agent.Agent,
holder *ConversationHolder,
defs []agent.Tool,
rec ExecutionRecorder,
toolOutputChunk func(toolName, toolCallID, chunk string),
) ([]tool.BaseTool, error) {
out := make([]tool.BaseTool, 0, len(defs))
for _, d := range defs {
if d.Type != "function" || d.Function.Name == "" {
@@ -32,6 +45,7 @@ func ToolsFromDefinitions(ag *agent.Agent, holder *ConversationHolder, defs []ag
agent: ag,
holder: holder,
record: rec,
chunk: toolOutputChunk,
})
}
return out, nil
@@ -68,6 +82,7 @@ type mcpBridgeTool struct {
agent *agent.Agent
holder *ConversationHolder
record ExecutionRecorder
chunk func(toolName, toolCallID, chunk string)
}
func (m *mcpBridgeTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
@@ -86,6 +101,32 @@ func (m *mcpBridgeTool) InvokableRun(ctx context.Context, argumentsInJSON string
if args == nil {
args = map[string]interface{}{}
}
// Stream tool output (stdout/stderr) to upper layer via security.Executor's callback.
// This enables multi-agent mode to show execution progress on the frontend.
if m.chunk != nil {
toolCallID := compose.GetToolCallID(ctx)
if toolCallID != "" {
if existing, ok := ctx.Value(security.ToolOutputCallbackCtxKey).(security.ToolOutputCallback); ok && existing != nil {
// Chain existing callback (if any) + our progress forwarder.
ctx = context.WithValue(ctx, security.ToolOutputCallbackCtxKey, security.ToolOutputCallback(func(c string) {
existing(c)
if strings.TrimSpace(c) == "" {
return
}
m.chunk(m.name, toolCallID, c)
}))
} else {
ctx = context.WithValue(ctx, security.ToolOutputCallbackCtxKey, security.ToolOutputCallback(func(c string) {
if strings.TrimSpace(c) == "" {
return
}
m.chunk(m.name, toolCallID, c)
}))
}
}
}
conv := m.holder.Get()
res, err := m.agent.ExecuteMCPToolForConversation(ctx, conv, m.name, args)
if err != nil {
@@ -97,5 +138,8 @@ func (m *mcpBridgeTool) InvokableRun(ctx context.Context, argumentsInJSON string
if res.ExecutionID != "" && m.record != nil {
m.record(res.ExecutionID)
}
if res.IsError {
return ToolErrorPrefix + res.Result, nil
}
return res.Result, nil
}
+10 -1
View File
@@ -44,11 +44,20 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
c.Header("X-Accel-Buffering", "no")
// 用于在 sendEvent 中判断是否为用户主动停止导致的取消。
// 注意:baseCtx 会在后面创建;该变量用于闭包提前捕获引用。
var baseCtx context.Context
clientDisconnected := false
sendEvent := func(eventType, message string, data interface{}) {
if clientDisconnected {
return
}
// 用户主动停止时,Eino 可能仍会并发上报 eventType=="error"。
// 为避免 UI 看到“取消错误 + cancelled 文案”两条回复,这里直接丢弃取消对应的 error。
if eventType == "error" && baseCtx != nil && errors.Is(context.Cause(baseCtx), ErrTaskCancelled) {
return
}
select {
case <-c.Request.Context().Done():
clientDisconnected = true
@@ -135,7 +144,6 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
)
if runErr != nil {
h.logger.Error("Eino DeepAgent 执行失败", zap.Error(runErr))
cause := context.Cause(baseCtx)
if errors.Is(cause, ErrTaskCancelled) {
taskStatus = "cancelled"
@@ -153,6 +161,7 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
return
}
h.logger.Error("Eino DeepAgent 执行失败", zap.Error(runErr))
taskStatus = "failed"
h.tasks.UpdateTaskStatus(conversationID, taskStatus)
errMsg := "执行失败: " + runErr.Error()
+85 -5
View File
@@ -3,6 +3,7 @@ package handler
import (
"bytes"
"database/sql"
"encoding/json"
"io"
"net/http"
"net/url"
@@ -104,10 +105,10 @@ func (h *WebShellHandler) CreateConnection(c *gin.Context) {
ID: "ws_" + strings.ReplaceAll(uuid.New().String(), "-", "")[:12],
URL: req.URL,
Password: strings.TrimSpace(req.Password),
Type: shellType,
Method: method,
Type: shellType,
Method: method,
CmdParam: strings.TrimSpace(req.CmdParam),
Remark: strings.TrimSpace(req.Remark),
Remark: strings.TrimSpace(req.Remark),
CreatedAt: time.Now(),
}
if err := h.db.CreateWebshellConnection(conn); err != nil {
@@ -197,6 +198,85 @@ func (h *WebShellHandler) DeleteConnection(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// GetConnectionState 获取 WebShell 连接关联的前端持久化状态(GET /api/webshell/connections/:id/state
func (h *WebShellHandler) GetConnectionState(c *gin.Context) {
if h.db == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "database not available"})
return
}
id := strings.TrimSpace(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
return
}
conn, err := h.db.GetWebshellConnection(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if conn == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "connection not found"})
return
}
stateJSON, err := h.db.GetWebshellConnectionState(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var state interface{}
if err := json.Unmarshal([]byte(stateJSON), &state); err != nil {
state = map[string]interface{}{}
}
c.JSON(http.StatusOK, gin.H{"state": state})
}
// SaveConnectionState 保存 WebShell 连接关联的前端持久化状态(PUT /api/webshell/connections/:id/state
func (h *WebShellHandler) SaveConnectionState(c *gin.Context) {
if h.db == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "database not available"})
return
}
id := strings.TrimSpace(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
return
}
conn, err := h.db.GetWebshellConnection(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if conn == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "connection not found"})
return
}
var req struct {
State json.RawMessage `json:"state"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
raw := req.State
if len(raw) == 0 {
raw = json.RawMessage(`{}`)
}
if len(raw) > 2*1024*1024 {
c.JSON(http.StatusBadRequest, gin.H{"error": "state payload too large (max 2MB)"})
return
}
var anyJSON interface{}
if err := json.Unmarshal(raw, &anyJSON); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "state must be valid json"})
return
}
if err := h.db.UpsertWebshellConnectionState(id, string(raw)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// GetAIHistory 获取指定 WebShell 连接的 AI 助手对话历史(GET /api/webshell/connections/:id/ai-history
func (h *WebShellHandler) GetAIHistory(c *gin.Context) {
if h.db == nil {
@@ -267,8 +347,8 @@ type FileOpRequest struct {
URL string `json:"url" binding:"required"`
Password string `json:"password"`
Type string `json:"type"`
Method string `json:"method"` // GET 或 POST,空则默认 POST
CmdParam string `json:"cmd_param"` // 命令参数名,如 cmd/xxx,空则默认 cmd
Method string `json:"method"` // GET 或 POST,空则默认 POST
CmdParam string `json:"cmd_param"` // 命令参数名,如 cmd/xxx,空则默认 cmd
Action string `json:"action" binding:"required"` // list, read, delete, write, mkdir, rename, upload, upload_chunk
Path string `json:"path"`
TargetPath string `json:"target_path"` // rename 时目标路径
+62
View File
@@ -0,0 +1,62 @@
package multiagent
import (
"context"
"strings"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/tool"
)
// noNestedTaskMiddleware 禁止在已经处于 task(sub-agent) 执行链中再次调用 task
// 避免子代理再次委派子代理造成的无限委派/递归。
//
// 通过在 ctx 中设置临时标记来实现嵌套检测:外层 task 调用会先标记 ctx,
// 子代理内再调用 task 时会命中该标记并拒绝。
type noNestedTaskMiddleware struct {
adk.BaseChatModelAgentMiddleware
}
type nestedTaskCtxKey struct{}
func newNoNestedTaskMiddleware() adk.ChatModelAgentMiddleware {
return &noNestedTaskMiddleware{}
}
func (m *noNestedTaskMiddleware) WrapInvokableToolCall(
ctx context.Context,
endpoint adk.InvokableToolCallEndpoint,
tCtx *adk.ToolContext,
) (adk.InvokableToolCallEndpoint, error) {
if tCtx == nil || strings.TrimSpace(tCtx.Name) == "" {
return endpoint, nil
}
// Deep 内置 task 工具名固定为 "task";为兼容可能的大小写/空白,仅做不区分大小写匹配。
if !strings.EqualFold(strings.TrimSpace(tCtx.Name), "task") {
return endpoint, nil
}
// 已在 task 执行链中:拒绝继续委派,直接报错让上层快速终止。
if ctx != nil {
if v, ok := ctx.Value(nestedTaskCtxKey{}).(bool); ok && v {
return func(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
// Important: return a tool result text (not an error) to avoid hard-stopping the whole multi-agent run.
// The nested task is still prevented from spawning another sub-agent, so recursion is avoided.
_ = argumentsInJSON
_ = opts
return "Nested task delegation is forbidden (already inside a sub-agent delegation chain) to avoid infinite delegation. Please continue the work using the current agent's tools.", nil
}, nil
}
}
// 标记当前 task 调用链,确保子代理内的再次 task 调用能检测到嵌套。
return func(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
ctx2 := ctx
if ctx2 == nil {
ctx2 = context.Background()
}
ctx2 = context.WithValue(ctx2, nestedTaskCtxKey{}, true)
return endpoint(ctx2, argumentsInJSON, opts...)
}, nil
}
+36 -6
View File
@@ -95,7 +95,23 @@ func RunDeepAgent(
}
mainDefs := ag.ToolsForRole(roleTools)
mainTools, err := einomcp.ToolsFromDefinitions(ag, holder, mainDefs, recorder)
toolOutputChunk := func(toolName, toolCallID, chunk string) {
// When toolCallId is missing, frontend ignores tool_result_delta.
if progress == nil || toolCallID == "" {
return
}
progress("tool_result_delta", chunk, map[string]interface{}{
"toolName": toolName,
"toolCallId": toolCallID,
// index/total/iteration are optional for UI; we don't know them in this bridge.
"index": 0,
"total": 0,
"iteration": 0,
"source": "eino",
})
}
mainTools, err := einomcp.ToolsFromDefinitions(ag, holder, mainDefs, recorder, toolOutputChunk)
if err != nil {
return nil, err
}
@@ -183,7 +199,7 @@ func RunDeepAgent(
}
subDefs := ag.ToolsForRole(roleTools)
subTools, err := einomcp.ToolsFromDefinitions(ag, holder, subDefs, recorder)
subTools, err := einomcp.ToolsFromDefinitions(ag, holder, subDefs, recorder, toolOutputChunk)
if err != nil {
return nil, fmt.Errorf("子代理 %q 工具: %w", id, err)
}
@@ -252,7 +268,11 @@ func RunDeepAgent(
WithoutGeneralSubAgent: ma.WithoutGeneralSubAgent,
WithoutWriteTodos: ma.WithoutWriteTodos,
MaxIteration: deepMaxIter,
Handlers: []adk.ChatModelAgentMiddleware{mainSumMw},
// 防止 sub-agent 再调用 task(再委派 sub-agent),形成无限委派链。
Handlers: []adk.ChatModelAgentMiddleware{
newNoNestedTaskMiddleware(),
mainSumMw,
},
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: mainTools,
@@ -451,14 +471,24 @@ func RunDeepAgent(
if toolName == "" {
toolName = mv.ToolName
}
preview := msg.Content
// bridge 工具在 res.IsError=true 时会返回带前缀的内容;这里解析为 success/isError,避免前端误判为成功。
content := msg.Content
isErr := false
if strings.HasPrefix(content, einomcp.ToolErrorPrefix) {
isErr = true
content = strings.TrimPrefix(content, einomcp.ToolErrorPrefix)
}
preview := content
if len(preview) > 200 {
preview = preview[:200] + "..."
}
data := map[string]interface{}{
"toolName": toolName,
"success": true,
"result": msg.Content,
"success": !isErr,
"isError": isErr,
"result": content,
"resultPreview": preview,
"conversationId": conversationID,
"einoAgent": ev.AgentName,
+45
View File
@@ -0,0 +1,45 @@
name: "lightx"
command: "lightx"
enabled: false
short_description: "轻量级资产发现与漏洞扫描工具"
description: |
Lightx 是一个高效的轻量级扫描工具,支持对单个目标、IP 段或文件列表进行快速探测。
**主要功能:**
- 支持多种目标格式(URL, IP, CIDR, 域名)
- 支持从文件批量读取目标
- 快速资产发现与服务识别
- 轻量级并发扫描
**使用场景:**
- 批量资产存活检测
- 网段快速扫描
- 域名信息收集
- 渗透测试前期侦察
**目标格式示例:**
- 单个 URL: http://example.com
- 单个 IP: 192.168.1.1
- IP 段: 192.168.1.1/24
- 域名: example.com
- 文件: targets.txt
parameters:
- name: "target"
type: "string"
description: |
扫描目标,支持多种格式。
**支持的格式:**
- **URL**: "http://example.com" 或 "https://target.com/path"
- **IP 地址**: "192.168.1.1"
- **IP 段 (CIDR)**: "192.168.1.0/24", "10.0.0.0/8"
- **域名**: "example.com" (不带协议头)
- **文件路径**: "/path/to/targets.txt" (文件中每行一个目标)
**示例值:**
- "http://172.16.0.4:9000"
- "192.168.1.1/24"
- "targets.txt"
required: true
flag: "-t"
format: "flag"
+1003 -10
View File
File diff suppressed because it is too large Load Diff
+63 -2
View File
@@ -19,7 +19,8 @@
"copy": "Copy",
"copied": "Copied",
"copyFailed": "Copy failed",
"view": "View"
"view": "View",
"actions": "Actions"
},
"header": {
"title": "CyberStrikeAI",
@@ -146,6 +147,7 @@
"callNumber": "Call #{{n}}",
"iterationRound": "Iteration {{n}}",
"aiThinking": "AI thinking",
"planning": "Planning",
"toolCallsDetected": "Detected {{count}} tool call(s)",
"callTool": "Call tool: {{name}} ({{index}}/{{total}})",
"toolExecComplete": "Tool {{name}} completed",
@@ -371,6 +373,41 @@
"tabTerminal": "Virtual terminal",
"tabFileManager": "File manager",
"tabAiAssistant": "AI Assistant",
"tabDbManager": "Database Manager",
"tabMemo": "Memo",
"dbType": "Database type",
"dbHost": "Host",
"dbPort": "Port",
"dbUsername": "Username",
"dbPassword": "Password",
"dbName": "Database name",
"dbSqlitePath": "SQLite file path",
"dbSqlPlaceholder": "Enter SQL, e.g. SELECT version();",
"dbRunSql": "Run SQL",
"dbTest": "Test connection",
"dbOutput": "Output",
"dbNoConn": "Please select a WebShell connection first",
"dbSqlRequired": "Please enter SQL",
"dbRunning": "Database command is running, please wait",
"dbCliHint": "If command not found appears, install mysql/psql/sqlite3/sqlcmd on the target host first",
"dbExecFailed": "Database execution failed",
"dbSchema": "Database Schema",
"dbLoadSchema": "Load Schema",
"dbNoSchema": "No schema yet, click Load Schema",
"dbSelectTableHint": "Click a table to expand columns and generate SQL",
"dbNoColumns": "No column details",
"dbResultTable": "Result Table",
"dbClearSql": "Clear SQL",
"dbTemplateSql": "SQL Template",
"dbRows": "rows",
"dbColumns": "columns",
"dbSchemaFailed": "Failed to load schema",
"dbAddProfile": "Add connection",
"dbRenameProfile": "Rename",
"dbDeleteProfile": "Delete connection",
"dbDeleteProfileConfirm": "Delete this database connection profile?",
"dbProfileNamePrompt": "Enter profile name",
"dbProfiles": "Database connections",
"aiSystemReadyMessage": "System is ready. Please enter your test requirements, and the system will automatically perform the corresponding security tests.",
"aiNewConversation": "New conversation",
"aiPreviousConversation": "Previous conversation",
@@ -378,6 +415,11 @@
"aiDeleteConversationConfirm": "Delete this conversation?",
"aiPlaceholder": "e.g. List files in the current directory",
"aiSend": "Send",
"aiMemo": "Memo",
"aiMemoPlaceholder": "Save key commands, testing ideas, and repro steps...",
"aiMemoClear": "Clear",
"aiMemoSaving": "Saving...",
"aiMemoSaved": "Saved locally",
"quickCommands": "Quick commands",
"downloadFile": "Download",
"terminalWelcome": "WebShell virtual terminal — type a command and press Enter (Ctrl+L clear)",
@@ -395,6 +437,13 @@
"testFailed": "Connectivity test failed",
"testNoExpectedOutput": "Shell responded but expected output was not found. Check password and command parameter name.",
"clearScreen": "Clear",
"copyTerminalLog": "Copy log",
"terminalIdle": "Idle",
"terminalRunning": "Running",
"terminalCopyOk": "Log copied",
"terminalCopyFail": "Copy failed",
"terminalNewWindow": "New terminal",
"terminalWindowPrefix": "Terminal",
"running": "Running…",
"waitFinish": "Please wait for the current command to finish",
"newDir": "New directory",
@@ -408,7 +457,19 @@
"selectAll": "Select all",
"searchPlaceholder": "Search connections...",
"noMatchConnections": "No matching connections",
"breadcrumbHome": "Root"
"breadcrumbHome": "Root",
"back": "Back",
"moreActions": "More actions",
"batchProbe": "Batch probe",
"probeRunning": "Probing",
"probeOnline": "Online",
"probeOffline": "Offline",
"probeNoConnections": "No connections to probe",
"colModifiedAt": "Modified",
"colPerms": "Permissions",
"colOwner": "Owner",
"colGroup": "Group",
"colType": "Type"
},
"mcp": {
"monitorTitle": "MCP Status Monitor",
+63 -2
View File
@@ -19,7 +19,8 @@
"copy": "复制",
"copied": "已复制",
"copyFailed": "复制失败",
"view": "查看"
"view": "查看",
"actions": "操作"
},
"header": {
"title": "CyberStrikeAI",
@@ -146,6 +147,7 @@
"callNumber": "调用 #{{n}}",
"iterationRound": "第 {{n}} 轮迭代",
"aiThinking": "AI思考",
"planning": "规划中",
"toolCallsDetected": "检测到 {{count}} 个工具调用",
"callTool": "调用工具: {{name}} ({{index}}/{{total}})",
"toolExecComplete": "工具 {{name}} 执行完成",
@@ -371,6 +373,41 @@
"tabTerminal": "虚拟终端",
"tabFileManager": "文件管理",
"tabAiAssistant": "AI 助手",
"tabDbManager": "数据库管理",
"tabMemo": "备忘录",
"dbType": "数据库类型",
"dbHost": "主机",
"dbPort": "端口",
"dbUsername": "用户名",
"dbPassword": "密码",
"dbName": "数据库名",
"dbSqlitePath": "SQLite 文件路径",
"dbSqlPlaceholder": "输入 SQL,例如:SELECT version();",
"dbRunSql": "执行 SQL",
"dbTest": "测试连接",
"dbOutput": "执行输出",
"dbNoConn": "请先选择 WebShell 连接",
"dbSqlRequired": "请输入 SQL",
"dbRunning": "数据库命令执行中,请稍候",
"dbCliHint": "如果提示命令不存在,请先在目标主机安装对应客户端(mysql/psql/sqlite3/sqlcmd",
"dbExecFailed": "数据库执行失败",
"dbSchema": "数据库结构",
"dbLoadSchema": "加载结构",
"dbNoSchema": "暂无数据库结构,请先加载",
"dbSelectTableHint": "点击表名可展开列信息并生成查询 SQL",
"dbNoColumns": "暂无列信息",
"dbResultTable": "结果表格",
"dbClearSql": "清空 SQL",
"dbTemplateSql": "示例 SQL",
"dbRows": "行",
"dbColumns": "列",
"dbSchemaFailed": "加载数据库结构失败",
"dbAddProfile": "新增连接",
"dbRenameProfile": "重命名",
"dbDeleteProfile": "删除连接",
"dbDeleteProfileConfirm": "确定删除该数据库连接配置吗?",
"dbProfileNamePrompt": "请输入连接名称",
"dbProfiles": "数据库连接",
"aiSystemReadyMessage": "系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。",
"aiNewConversation": "新对话",
"aiPreviousConversation": "之前的对话",
@@ -378,6 +415,11 @@
"aiDeleteConversationConfirm": "确定删除当前对话记录?",
"aiPlaceholder": "例如:列出当前目录下的文件",
"aiSend": "发送",
"aiMemo": "备忘录",
"aiMemoPlaceholder": "记录关键命令、测试思路、复现步骤...",
"aiMemoClear": "清空",
"aiMemoSaving": "保存中...",
"aiMemoSaved": "已保存到本地",
"quickCommands": "快捷命令",
"downloadFile": "下载",
"terminalWelcome": "WebShell 虚拟终端 — 输入命令后按回车执行(Ctrl+L 清屏)",
@@ -395,6 +437,13 @@
"testFailed": "连通性测试失败",
"testNoExpectedOutput": "Shell 返回了响应但未得到预期输出,请检查连接密码与命令参数名",
"clearScreen": "清屏",
"copyTerminalLog": "复制日志",
"terminalIdle": "空闲",
"terminalRunning": "执行中",
"terminalCopyOk": "日志已复制",
"terminalCopyFail": "复制失败",
"terminalNewWindow": "新终端",
"terminalWindowPrefix": "终端",
"running": "执行中…",
"waitFinish": "请等待当前命令执行完成",
"newDir": "新建目录",
@@ -408,7 +457,19 @@
"selectAll": "全选",
"searchPlaceholder": "搜索连接...",
"noMatchConnections": "暂无匹配连接",
"breadcrumbHome": "根"
"breadcrumbHome": "根",
"back": "返回",
"moreActions": "更多操作",
"batchProbe": "一键批量探活",
"probeRunning": "探活中",
"probeOnline": "在线",
"probeOffline": "离线",
"probeNoConnections": "暂无可探活连接",
"colModifiedAt": "修改时间",
"colPerms": "权限",
"colOwner": "所有者",
"colGroup": "用户组",
"colType": "类型"
},
"mcp": {
"monitorTitle": "MCP 状态监控",
+34 -19
View File
@@ -1101,16 +1101,16 @@ function handleStreamEvent(event, progressElement, progressId,
loadActiveTasks();
}
// 主回复开始流式输出时隐藏整条进度卡片(迭代阶段默认展开;最终回复时不再占屏)
hideProgressMessageForFinalReply(progressId);
// 已存在则复用;否则创建空助手消息占位,用于增量追加
const existing = responseStreamStateByProgressId.get(progressId);
if (existing && existing.assistantId) break;
const assistantId = addMessage('assistant', '', mcpIds, progressId);
setAssistantId(assistantId);
responseStreamStateByProgressId.set(progressId, { assistantId, buffer: '' });
// 多代理模式下,迭代过程中的输出只显示在时间线中,不创建助手消息气泡
// 创建时间线条目用于显示迭代过程中的输出
const agentPrefix = timelineAgentBracketPrefix(responseData);
const title = agentPrefix + '📝 ' + (typeof window.t === 'function' ? window.t('chat.planning') : '规划中');
const itemId = addTimelineItem(timeline, 'thinking', {
title: title,
message: ' ',
data: responseData
});
responseStreamStateByProgressId.set(progressId, { itemId: itemId, buffer: '' });
break;
}
@@ -1126,19 +1126,31 @@ function handleStreamEvent(event, progressElement, progressId,
}
}
hideProgressMessageForFinalReply(progressId);
// 多代理模式下,迭代过程中的输出只显示在时间线中
// 更新时间线条目内容
let state = responseStreamStateByProgressId.get(progressId);
if (!state || !state.assistantId) {
const mcpIds = responseData.mcpExecutionIds || [];
const assistantId = addMessage('assistant', '', mcpIds, progressId);
setAssistantId(assistantId);
state = { assistantId, buffer: '' };
if (!state) {
state = { itemId: null, buffer: '' };
responseStreamStateByProgressId.set(progressId, state);
}
state.buffer += (event.message || '');
updateAssistantBubbleContent(state.assistantId, state.buffer, false);
const deltaContent = event.message || '';
state.buffer += deltaContent;
// 更新时间线条目内容
if (state.itemId) {
const item = document.getElementById(state.itemId);
if (item) {
const contentEl = item.querySelector('.timeline-item-content');
if (contentEl) {
if (typeof formatMarkdown === 'function') {
contentEl.innerHTML = formatMarkdown(state.buffer);
} else {
contentEl.textContent = state.buffer;
}
}
}
}
break;
}
@@ -1179,6 +1191,9 @@ function handleStreamEvent(event, progressElement, progressId,
updateAssistantBubbleContent(assistantIdFinal, event.message, true);
}
// 最终回复时隐藏进度卡片(多代理模式下,迭代过程已完整展示)
hideProgressMessageForFinalReply(progressId);
// 将进度详情集成到工具调用区域(放在最终 response 之后,保证时间线已完整)
integrateProgressToMCPSection(progressId, assistantIdFinal, mcpIds);
responseStreamStateByProgressId.delete(progressId);
+2002 -57
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -1052,6 +1052,9 @@
data-i18n-attr="placeholder"
placeholder="搜索连接..." />
</div>
<div class="webshell-sidebar-tools">
<button type="button" class="btn-ghost btn-sm" id="webshell-batch-probe-btn" data-i18n="webshell.batchProbe">一键批量探活</button>
</div>
<div id="webshell-list" class="webshell-list">
<div class="webshell-empty" data-i18n="webshell.noConnections">暂无连接,请点击「添加连接」</div>
</div>