feat: 由报告 Agent 自动关联流量证据
This commit is contained in:
1 parent
a3bb2c7959
commit
dbd00b846e
7 files changed
+99
-31
No files matched your search
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user