From c0ac501c4b27b651bbcde769d39c2ffe364a9327 Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:56:43 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20update=5Ffinding?= =?UTF-8?q?=5Freport=20=E5=B7=A5=E5=85=B7,=E5=86=99=E5=85=A5=E6=BC=8F?= =?UTF-8?q?=E6=B4=9E=20report=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 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 测试通过。 --- agent/tools.go | 35 ++++++++++++++++++++++++++++++++++- agent/tools_insert.go | 4 ++-- db/exploration.go | 6 ++++++ db/findings.go | 11 +++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/agent/tools.go b/agent/tools.go index a0bd49e..f6adf62 100644 --- a/agent/tools.go +++ b/agent/tools.go @@ -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 纳入本任务测试范围(覆盖度分母)。 diff --git a/agent/tools_insert.go b/agent/tools_insert.go index c808988..a0109e7 100644 --- a/agent/tools_insert.go +++ b/agent/tools_insert.go @@ -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(), diff --git a/db/exploration.go b/db/exploration.go index 00f6024..c3f70a5 100644 --- a/db/exploration.go +++ b/db/exploration.go @@ -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) +} diff --git a/db/findings.go b/db/findings.go index 1549e06..b77ac3f 100644 --- a/db/findings.go +++ b/db/findings.go @@ -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