feat: 预置「报告撰写」自定义 agent,发现漏洞时自动写详细报告
- 新增 host 工具 update_finding_report(finding_id, report):按 node_id 写 findings.report(任务无关,无需 task 上下文);get_task_node_detail(task_id, id) 读取漏洞完整证据/PoC。均注册进 orchestrationTools。 - promptcatalog 新增 ReporterDefaultPrompt:详述报告应含的结构(概述/影响/ 受影响范围/复现步骤/证据PoC/根因/修复建议)与只据实证撰写的纪律。 - seedReporterAgent(一次性,flag 守卫):预置 builtin=false 的自定义 agent 「报告撰写」(reporter),UI 可编辑/删除;绑定 update_finding_report + 任务 查询工具(node_detail/worker_trace/findings/graph);建 on_tool_call 触发器 (tool_names=[report_finding]) —— 每登记漏洞即唤起它写报告并回写。 - 移除上一提交里绑到 worker/planner/mainagent 的 in-task update_finding_report 工具及 ExplorationStore 包装(触发态是会话上下文,须用 host 工具);保留 db.SetFindingReportByNodeID;新增 ToolSet.NodeDetailTool 访问器。 go build / agent / server 测试通过。reporter 为自定义 agent,不影响内置 agent 计数。
This commit is contained in:
1 parent
c0ac501c4b
commit
6cad678699
5 files changed
+139
-47
No files matched your search
@@ -67,6 +67,41 @@ const pentestDefaultTmpl = `你是一个授权渗透测试系统的"独立渗透
|
||||
// render fallback in RunChat when the DB prompt is somehow missing.
|
||||
const DefaultAssistantPrompt = `你是一个乐于助人的 AI 助手。请用简洁、准确的中文回答用户的问题;在需要时使用可用的工具来完成任务。只做用户要求的事,不臆造信息。`
|
||||
|
||||
// ReporterDefaultPrompt is the seeded prompt for the "报告撰写"(reporter) custom
|
||||
// agent — triggered when report_finding fires. It gathers the finding's full
|
||||
// evidence + how it was found, writes a Markdown vulnerability report, and saves
|
||||
// it via update_finding_report.
|
||||
const ReporterDefaultPrompt = `你是一个授权渗透测试系统里的**漏洞报告撰写 agent**。你不亲自渗透、不做利用——你的唯一职责是:为**刚刚被确认登记的某一个漏洞**撰写一份专业、可复现、面向修复的**详细报告(Markdown)**,并保存回该漏洞。
|
||||
|
||||
━━ 你是怎么被唤起的 ━━
|
||||
每当有 worker 调用 report_finding 登记了一个漏洞,系统就会用一段【由工具调用触发】的上下文唤起你,其中包含:
|
||||
- **任务 id**(task_id,见上下文"任务: #<id>")
|
||||
- report_finding 的**入参**(vulnclass / severity / summary / evidence 等)
|
||||
- report_finding 的**返回**:形如 "finding recorded: <id>" —— 这个 **<id> 就是本漏洞的 finding_id**,是你后面保存报告的句柄。
|
||||
|
||||
先从上下文里**准确抽取 finding_id 和 task_id**。抽取不到 finding_id 就不要瞎写,说明情况即可。
|
||||
|
||||
━━ 工作步骤 ━━
|
||||
1. **取全证据**:用 get_task_node_detail(task_id, id=<finding_id>) 读该漏洞节点的**完整证据/PoC**(触发上下文里的 evidence 可能被截断)。
|
||||
2. **还原过程**:用 list_task_worker_traces(task_id) 找到相关的 work,再用 get_task_worker_trace(task_id, intent_id[, step_ids]) 或 search_task_worker_traces(task_id, q) 看这个漏洞**是怎么被发现和验证的**(用了什么请求/命令、目标怎么响应)。必要时 get_task_graph(task_id) 看整体态势、list_task_findings(task_id) 看是否有关联漏洞。
|
||||
3. **写报告**:综合以上,写一份结构化 Markdown 报告(见下方模板)。
|
||||
4. **保存**:调用 **update_finding_report(finding_id=<那个 id>, report=<Markdown 全文>)** 保存。这是你的最终产物——不写进去等于没做。
|
||||
|
||||
━━ 报告结构(Markdown,按需裁剪,但证据/复现/修复必须有)━━
|
||||
- ` + "`## 概述`" + `:一句话说清是什么漏洞、在哪、能造成什么。
|
||||
- ` + "`## 影响与危害`" + `:结合业务讲清最坏后果(数据泄露/接管/RCE/横向…),给出**严重等级**判断及理由。
|
||||
- ` + "`## 受影响范围`" + `:受影响的资产/接口/参数/版本。
|
||||
- ` + "`## 复现步骤`" + `:**可照做复现**的分步操作(请求/命令/参数),能贴 PoC 就贴。
|
||||
- ` + "`## 证据 / PoC`" + `:关键请求响应片段、命令输出、回显——用代码块。
|
||||
- ` + "`## 根因分析`" + `:为什么会有这个漏洞(缺校验/危险函数/配置错误…)。
|
||||
- ` + "`## 修复建议`" + `:具体、可落地的整改措施(不是空话),可含加固与长期建议。
|
||||
|
||||
━━ 纪律 ━━
|
||||
- **只基于真实证据**:报告里的每一条都要能从 finding 证据或 work 执行过程里找到支撑;**绝不臆造**请求、响应、CVE 或结论。证据不足的地方如实标注"未验证/需进一步确认"。
|
||||
- **面向修复、可核验**:复现步骤要能照做,修复建议要能落地。
|
||||
- **精炼**:不写套话废话、不复述模板本身。
|
||||
- 全程**中文**。做完(已成功调用 update_finding_report)就结束,用一两句话说明你为哪个漏洞写了报告即可。`
|
||||
|
||||
// BuiltinPromptSeeds returns each built-in agent's default EDITABLE prompt body
|
||||
// keyed by agent key. The server seeds these into agent_prompts on startup (only
|
||||
// when an agent has no prompt yet), so the DB becomes the authoritative, editable
|
||||
|
||||
+2
-34
@@ -156,6 +156,7 @@ func (t *ToolSet) ListFindingsTool() actool.CoreTool { return t.listFindin
|
||||
func (t *ToolSet) GetWorkerTraceTool() actool.CoreTool { return t.getWorkerTrace() }
|
||||
func (t *ToolSet) ListWorkerTracesTool() actool.CoreTool { return t.listWorkerTraces() }
|
||||
func (t *ToolSet) SearchWorkerTracesTool() actool.CoreTool { return t.searchAllWorkerTraces() }
|
||||
func (t *ToolSet) NodeDetailTool() actool.CoreTool { return t.nodeDetail() }
|
||||
func (t *ToolSet) AddHintTool() actool.CoreTool { return t.addHint() }
|
||||
|
||||
// SetEnrich wires the async enrichment engine (DNS/HTTP auto-completion).
|
||||
@@ -696,39 +697,6 @@ func (t *ToolSet) addFinding() actool.CoreTool {
|
||||
})
|
||||
}
|
||||
|
||||
// updateFindingReport sets/overwrites a finding's detailed Markdown report
|
||||
// (findings.report), addressed by the id report_finding returned.
|
||||
func (t *ToolSet) updateFindingReport() actool.CoreTool {
|
||||
return writeTool("update_finding_report",
|
||||
"为已登记的漏洞补充/更新【详细报告】。report 传 Markdown 全文(建议含:漏洞概述、影响、复现步骤、证据/PoC、修复建议)。finding_id 传 report_finding 返回的那个 id。整段覆盖该漏洞已有的报告。",
|
||||
obj(map[string]any{
|
||||
"finding_id": idp("目标漏洞 id(report_finding 返回的 id)"),
|
||||
"report": str("详细报告全文,Markdown 格式"),
|
||||
}, "finding_id", "report"),
|
||||
func(_ context.Context, in json.RawMessage) (actool.Result, error) {
|
||||
var a struct {
|
||||
FindingID json.RawMessage `json:"finding_id"`
|
||||
Report string `json:"report"`
|
||||
}
|
||||
_ = json.Unmarshal(in, &a)
|
||||
nodeID := pid(a.FindingID)
|
||||
if nodeID <= 0 {
|
||||
return actool.Errorf("finding_id 无效"), nil
|
||||
}
|
||||
if t.ts == nil {
|
||||
return actool.Errorf("update_finding_report 需要任务上下文(exploration store 未初始化)"), nil
|
||||
}
|
||||
n, err := t.ts.SetFindingReport(nodeID, a.Report)
|
||||
if err != nil {
|
||||
return actool.Errorf(err.Error()), nil
|
||||
}
|
||||
if n == 0 {
|
||||
return actool.Errorf(fmt.Sprintf("未找到 finding_id=%d 对应的漏洞记录(先用 report_finding 登记)", nodeID)), nil
|
||||
}
|
||||
return actool.Text(fmt.Sprintf("finding %d report updated (%d chars)", nodeID, len(a.Report))), nil
|
||||
})
|
||||
}
|
||||
|
||||
// recordFact writes a general exploration RESULT/conclusion (not a vuln, not a
|
||||
// new asset) into the EXPLORATION graph, chained to the intent that produced it.
|
||||
// This is the home for observations and — importantly — negative results
|
||||
@@ -1283,7 +1251,7 @@ func (t *ToolSet) PlannerTools() []actool.CoreTool {
|
||||
t.getWorkerOutput(), t.getWorkerTrace(), t.searchAllWorkerTraces(), t.listGoals(), t.addIntent(), t.proveGoal(), t.goalMet(),
|
||||
t.killWorkTool(), t.steerWorkTool(),
|
||||
// report_finding:规划态势研判时若自身已确证漏洞,可直接登记(与 worker 同工具)。
|
||||
t.addFinding(), t.updateFindingReport(),
|
||||
t.addFinding(),
|
||||
// list_companies:查看企业列表 + scope + 资产数(拿 company_id / 理解归属范围)。
|
||||
t.listCompanies(),
|
||||
// add_task_scope:主动把整根域/整公司/某子域/IP 纳入本任务测试范围(覆盖度分母)。
|
||||
|
||||
@@ -582,7 +582,7 @@ func splitLines(s string) []string {
|
||||
func (t *ToolSet) WorkerTools() []actool.CoreTool {
|
||||
return []actool.CoreTool{
|
||||
t.listFindings(), t.listFacts(), t.nodeDetail(),
|
||||
t.addFinding(), t.updateFindingReport(), t.recordFact(),
|
||||
t.addFinding(), t.recordFact(),
|
||||
// cross-work retrieval
|
||||
t.searchAllWorkerTraces(), t.listWorkerTraces(), t.getWorkerTrace(),
|
||||
// asset management (handlers guard nil store internally)
|
||||
@@ -599,7 +599,7 @@ func (t *ToolSet) MainAgentTools() []actool.CoreTool {
|
||||
t.setGoals(),
|
||||
// asset management (handlers guard nil store internally)
|
||||
t.insertAssets(), t.addCompanyScope(), t.listAssets(),
|
||||
t.addFinding(), t.updateFindingReport(), t.recordFact(),
|
||||
t.addFinding(), t.recordFact(),
|
||||
t.addTaskScope(),
|
||||
// list_untested_assets:按需查本任务范围内未测资产(类型+分页),自行决定补测。
|
||||
t.listUntestedAssets(),
|
||||
|
||||
Reference in new issue
Block a user