feat: 由报告 Agent 自动关联流量证据

This commit is contained in:
ji ruo committed 2026-09-10 14:26:15 +08:00
1 parent a3bb2c7959
commit dbd00b846e
7 files changed
+99 -31

No files matched your search

+9 -12
View File
@@ -14,7 +14,7 @@ import (
)
func (s *Server) seedFindingWorkflowTools() {
const flag = "finding_workflow_tools_v1"
const flag = "finding_workflow_tools_v2_reporter"
if value, _, _ := s.m.pg.GetSetting(flag); value == "true" {
return
}
@@ -72,24 +72,21 @@ func (s *Server) seedFindingWorkflowTools() {
}
// Upgrade only the original default binding. Customized lists and enabled
// flags survive; the one-time flag also preserves future user unbinding.
readers := `["worker","planner","mainagent","auto","pentest"]`
readers := `["worker","reporter"]`
for _, key := range []string{"traffic_search", "traffic_get", "traffic_blob"} {
if _, err := s.m.pg.Exec(`UPDATE tools SET agents=$2::jsonb WHERE key=$1 AND system AND agents='["worker"]'::jsonb`, key, readers); err != nil {
if _, err := s.m.pg.Exec(`UPDATE tools SET agents=$2::jsonb WHERE key=$1 AND system AND (agents='["worker"]'::jsonb OR (agents @> '["worker","planner","mainagent","auto","pentest"]'::jsonb AND jsonb_array_length(agents)=5))`, key, readers); err != nil {
return
}
}
if _, err := s.m.pg.Exec(`UPDATE tools SET agents=$1::jsonb WHERE key='get_finding_traffic' AND system AND agents @> '["auto","reporter"]'::jsonb AND jsonb_array_length(agents)=2`, `["auto","reporter","worker","planner","mainagent","pentest"]`); err != nil {
return
}
if report, err := s.m.pg.GetTool("report_finding"); err == nil && report != nil {
binding, _ := s.m.pg.GetTool("bind_finding_traffic")
if binding != nil && binding.System {
for _, key := range report.Agents {
if err := s.m.pg.AddAgentToToolBinding(key, []string{"bind_finding_traffic"}); err != nil {
return
}
}
}
// Replace the previous code default only; preserve customized binding lists.
if _, err := s.m.pg.Exec(`UPDATE tools SET agents='["reporter"]'::jsonb WHERE key='bind_finding_traffic' AND system AND agents @> '["worker","planner","mainagent","auto","pentest"]'::jsonb AND jsonb_array_length(agents)=5`); err != nil {
return
}
if err := s.m.pg.AddAgentToToolBinding("reporter", []string{"bind_finding_traffic"}); err != nil {
return
}
_ = s.m.pg.SetSetting(flag, "true")
}
+69 -4
View File
@@ -211,7 +211,7 @@ func TestFindingWorkflowMigrationPreservesUserConfiguration(t *testing.T) {
t.Fatal(err)
}
}
if err := pg.SetSetting("finding_workflow_tools_v1", "false"); err != nil {
if err := pg.SetSetting("finding_workflow_tools_v2_reporter", "false"); err != nil {
t.Fatal(err)
}
s.seedFindingWorkflowTools()
@@ -233,15 +233,80 @@ func TestFindingWorkflowMigrationPreservesUserConfiguration(t *testing.T) {
}
search, _ := pg.GetTool("traffic_search")
get, _ := pg.GetTool("traffic_get")
if search.Enabled || !contains(search.Agents, "planner") || get.Enabled || len(get.Agents) != 1 || get.Agents[0] != "custom-agent" {
if search.Enabled || !contains(search.Agents, "reporter") || get.Enabled || len(get.Agents) != 1 || get.Agents[0] != "custom-agent" {
t.Fatal("default/custom reader binding migration incorrect")
}
if err := pg.RemoveAgentFromTool("planner", "traffic_search"); err != nil {
if err := pg.RemoveAgentFromTool("reporter", "traffic_search"); err != nil {
t.Fatal(err)
}
s.seedFindingWorkflowTools()
search, _ = pg.GetTool("traffic_search")
if contains(search.Agents, "planner") {
if contains(search.Agents, "reporter") {
t.Fatal("one-time migration undid later unbinding")
}
}
func TestFindingWorkflowReporterBindsBeforeWritingReport(t *testing.T) {
s, f, request := trafficEvidenceServer(t)
pg := s.m.pg
ctx := context.Background()
if err := pg.SetBool(settingAgentTrafficBinding, true); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { pg.SetBool(settingAgentTrafficBinding, false); s.m.SetTrafficEnabled(false) })
if err := s.m.SetTrafficEnabled(true); err != nil {
t.Fatal(err)
}
seedServerEvidenceFlow(t, s, "reporter-proof", []byte("local proof payload"))
seedServerEvidenceFlow(t, s, "reporter-baseline", []byte("local normal response"))
tools, def, cleanup := agent.AugmentTools(ctx, "reporter", nil)
defer cleanup()
if !strings.Contains(def.FindingGuidance, "报告前自动关联流量") || !strings.Contains(def.FindingGuidance, "绑定成功后重新调用") {
t.Fatal("reporter did not receive binding workflow")
}
for _, name := range []string{"traffic_search", "traffic_get", "get_task_worker_trace", "get_task_node_detail", "bind_finding_traffic", "get_finding_traffic", "update_finding_report"} {
workflowTool(t, tools, name)
}
finding, _ := pg.GetFinding(f.FindingID)
detail := workflowCall(t, ctx, workflowTool(t, tools, "get_task_node_detail"), map[string]any{"task_id": fmt.Sprint(*finding.TaskID), "id": f.NodeID}, false)
if !strings.Contains(detail, `"finding_id"`) || !strings.Contains(detail, `"finding_node_id"`) {
t.Fatal("reporter lacks explicit ID mapping")
}
workflowCall(t, ctx, workflowTool(t, tools, "traffic_search"), map[string]any{"host": "evidence.local"}, false)
for _, id := range []string{"reporter-baseline", "reporter-proof"} {
packet := workflowCall(t, ctx, workflowTool(t, tools, "traffic_get"), map[string]any{"id": id}, false)
if !strings.Contains(packet, "local") {
t.Fatal("reporter cannot inspect packet", packet)
}
}
refs := []db.TrafficRef{{TrafficID: "reporter-baseline", Role: "baseline"}, {TrafficID: "reporter-proof", Role: "proof", Note: "confirmed from recorded response"}}
workflowCall(t, ctx, workflowTool(t, tools, "bind_finding_traffic"), map[string]any{"finding_id": f.FindingID, "traffic_refs": refs}, false)
list := workflowCall(t, ctx, workflowTool(t, tools, "get_finding_traffic"), map[string]any{"finding_id": f.FindingID}, false)
var summary struct {
Version int64 `json:"version"`
}
if err := json.Unmarshal([]byte(list), &summary); err != nil || summary.Version != 1 {
t.Fatal(list, err)
}
workflowCall(t, ctx, workflowTool(t, tools, "update_finding_report"), map[string]any{"finding_id": f.NodeID, "evidence_version": summary.Version, "report": "## Local report\n\nVerified baseline and proof using saved evidence."}, false)
updated, err := pg.GetFinding(f.FindingID)
if err != nil || updated.ReportEvidenceVersion != summary.Version || updated.EvidenceVersion != summary.Version {
t.Fatal("report did not cover post-binding version", updated, err)
}
// Disabling automatic binding still lets Reporter read manually bound snapshots.
if r := request("PUT", "/api/settings", `{"agent_traffic_binding":false}`); r.Code != 200 {
t.Fatal(r.Code, r.Body)
}
off, offDef, closeOff := agent.AugmentTools(ctx, "reporter", nil)
defer closeOff()
if offDef.FindingGuidance != "" {
t.Fatal("off reporter still receives auto-binding guidance")
}
for _, tool := range off {
if tool.Name() == "traffic_search" || tool.Name() == "traffic_get" || tool.Name() == "traffic_blob" || tool.Name() == "bind_finding_traffic" {
t.Fatal("auto-binding tool exposed while off", tool.Name())
}
}
workflowCall(t, ctx, workflowTool(t, off, "get_finding_traffic"), map[string]any{"finding_id": f.FindingID}, false)
workflowTool(t, off, "update_finding_report")
}
+6 -2
View File
@@ -444,7 +444,11 @@ func (s *Server) seedOrchestrationTools() {
autoAgents, _ := json.Marshal([]string{"auto"})
for _, t := range s.orchestrationTools() {
schema, _ := json.Marshal(t.InputSchema())
_ = s.m.PG().SeedTool(t.Name(), t.Description(), schema, autoAgents)
bindings := autoAgents
if t.Name() == "bind_finding_traffic" {
bindings = json.RawMessage(`["reporter"]`)
}
_ = s.m.PG().SeedTool(t.Name(), t.Description(), schema, bindings)
}
for _, t := range s.platformTools() {
schema, _ := json.Marshal(t.InputSchema())
@@ -691,7 +695,7 @@ func (s *Server) seedReporterAgent() {
OnToolCall: true,
ToolNames: []string{"report_finding"},
ToolCallMessage: "上面刚有一个漏洞被 report_finding 登记。请读取返回 JSON 的 finding_id(独立漏洞记录 ID)与 finding_node_id(探索节点 ID)," +
"用 get_finding_traffic(finding_id) 读取证据;节点详情使用 finding_node_id。" +
"若运行指引启用自动绑定,先核实并关联本次漏洞的流量,再用 get_finding_traffic(finding_id) 读取最新证据;节点详情使用 finding_node_id。" +
"最后调用 update_finding_report(finding_id=finding_node_id, report, evidence_version=实际读取版本) 保存。不要混用两种编号。",
}); err != nil {
log.Printf("[reporter] 创建触发器失败: %v", err)