feat: 新增 update_finding_report 工具,写入漏洞 report 字段
- 新增 agent 写工具 update_finding_report(finding_id + report[Markdown]), 整段覆盖该漏洞的详细报告;finding_id 传 report_finding 返回的 id - 绑定 worker / planner / mainagent 三个 agent,经 BuiltinToolSeeds 自动入 tools 目录(首插不覆盖,旧库升级重启即出现,可在 UI 管理/勾选) - db.SetFindingReportByNodeID / ExplorationStore.SetFindingReport:按 finding 节点 id 更新独立 findings 行的 report - 详情页概览已渲染 report,写入后即可展示 go build / agent / server 测试通过。
This commit is contained in:
1 parent
8092167ae5
commit
c0ac501c4b
4 files changed
+53
-3
No files matched your search
+34
-1
@@ -696,6 +696,39 @@ 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
|
||||
@@ -1250,7 +1283,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.addFinding(), t.updateFindingReport(),
|
||||
// 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.recordFact(),
|
||||
t.addFinding(), t.updateFindingReport(), 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.recordFact(),
|
||||
t.addFinding(), t.updateFindingReport(), t.recordFact(),
|
||||
t.addTaskScope(),
|
||||
// list_untested_assets:按需查本任务范围内未测资产(类型+分页),自行决定补测。
|
||||
t.listUntestedAssets(),
|
||||
|
||||
@@ -889,3 +889,9 @@ FROM activity WHERE exploration_id=$1 AND kind<>'thinking' AND id IN (`+strings.
|
||||
func (s *ExplorationStore) AddStandaloneFinding(taskID, nodeID int64, vulnclass, name, severity, summary, evidence, worker string, assetIDs []int64) (int64, error) {
|
||||
return s.db.AddFinding(taskID, nodeID, vulnclass, name, severity, summary, evidence, worker, assetIDs)
|
||||
}
|
||||
|
||||
// SetFindingReport sets a finding's Markdown detail report, addressed by its
|
||||
// finding node id (the id report_finding returns). Returns rows affected.
|
||||
func (s *ExplorationStore) SetFindingReport(nodeID int64, report string) (int64, error) {
|
||||
return s.db.SetFindingReportByNodeID(nodeID, report)
|
||||
}
|
||||
@@ -277,6 +277,17 @@ func (d *DB) SetFindingStatus(id int64, status string) (int64, error) {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
// SetFindingReportByNodeID sets the Markdown report on the standalone finding row
|
||||
// whose node_id matches — report_finding returns that node id, so an agent tool
|
||||
// can address the finding it just created. Returns rows affected (0 when no row).
|
||||
func (d *DB) SetFindingReportByNodeID(nodeID int64, report string) (int64, error) {
|
||||
res, err := d.Exec(`UPDATE findings SET report=$1 WHERE node_id=$2`, report, nodeID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
// SetFindingSeverity updates one finding's severity in the standalone table AND
|
||||
// keeps the originating exploration node's payload in sync (the per-task view
|
||||
// reads severity from the node payload, not this table). Returns rows affected
|
||||
|
||||
Reference in new issue
Block a user