Files
ARTEX/server/finding_workflow.go
T

158 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package server
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"strconv"
"github.com/Autumn-27/artex/agent"
"github.com/Autumn-27/artex/db"
actool "github.com/Autumn-27/norma/tool"
)
func (s *Server) seedFindingWorkflowTools() {
const flag = "finding_workflow_tools_v2_reporter"
if value, _, _ := s.m.pg.GetSetting(flag); value == "true" {
return
}
for _, key := range []string{"report_finding", "add_hint", "add_task_hint"} {
row, err := s.m.pg.GetTool(key)
if err != nil {
log.Printf("[evidence] load %s: %v", key, err)
return
}
if row == nil || !row.System {
continue
}
var schema map[string]any
if err := json.Unmarshal(row.Schema, &schema); err != nil {
log.Printf("[evidence] invalid schema for %s: %v", key, err)
return
}
if schema == nil {
log.Printf("[evidence] missing object schema for %s", key)
return
}
props := objectProperty(schema, "properties")
if key == "report_finding" {
if _, exists := props["evidence_hint_id"]; !exists {
props["evidence_hint_id"] = map[string]any{"type": "integer", "description": "可选:本任务中对应此漏洞的 hint ID;读取该提示保存的 traffic_refs 一并绑定,无提示时省略"}
}
} else {
if _, exists := props["traffic_refs"]; !exists {
props["traffic_refs"] = agent.HintTrafficSchema()
}
hints := objectProperty(props, "hints")
if _, exists := hints["type"]; !exists {
hints["type"] = "array"
}
items := objectProperty(hints, "items")
if _, ok := items["type"]; !ok {
items["type"] = "object"
}
itemProps := objectProperty(items, "properties")
for name, value := range map[string]any{"text": strParam("提示内容"), "asset_ids": map[string]any{"type": "array", "items": map[string]any{"type": "integer"}}, "traffic_refs": agent.HintTrafficSchema()} {
if _, exists := itemProps[name]; !exists {
itemProps[name] = value
}
}
}
raw, _ := json.Marshal(schema)
result, err := s.m.pg.Exec(`UPDATE tools SET schema=$2::jsonb,updated_at=now() WHERE key=$1 AND system AND schema=$3::jsonb`, key, string(raw), string(row.Schema))
if err != nil {
log.Printf("[evidence] upgrade %s: %v", key, err)
return
}
if n, _ := result.RowsAffected(); n != 1 {
return
} // preserve concurrent user edits
}
// Upgrade only the original default binding. Customized lists and enabled
// flags survive; the one-time flag also preserves future user unbinding.
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 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
}
// 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")
}
func objectProperty(parent map[string]any, key string) map[string]any {
value, ok := parent[key].(map[string]any)
if !ok {
value = map[string]any{}
parent[key] = value
}
return value
}
func (s *Server) agentFindingTrafficAccess(ctx context.Context, id int64, write bool) error {
if id <= 0 {
return errors.New("finding_id 必须为独立漏洞记录 ID;不是探索节点 ID")
}
f, err := s.m.pg.GetFinding(id)
if err != nil {
return err
}
if f == nil {
return fmt.Errorf("%w:finding_id=%d。证据工具使用独立漏洞记录 ID,请从 list_task_findings / get_task_node_detail 的 finding_id 字段读取;不要传 id / finding_node_id", db.ErrFindingNotFound, id)
}
if ri := agent.RunInfoFrom(ctx); ri.TaskID > 0 {
task := s.m.ResolveTask(strconv.FormatInt(ri.TaskID, 10))
if task == nil {
return errors.New("任务不存在")
}
_, inherited, allowed := findingProvenanceInTask(task, f.TaskID)
if !allowed {
return errors.New("当前任务不可读取该漏洞")
}
if write && inherited {
return errors.New("继承漏洞的流量证据只读,请到来源任务修改")
}
}
return nil
}
func (s *Server) toolBindFindingTraffic() actool.CoreTool {
return wrTool("bind_finding_traffic", "为已登记漏洞补绑经核实的真实 HTTP 流量。finding_id 使用独立漏洞记录 ID;不要传探索节点 ID。同批引用全部成功或全部失败,重复引用不覆盖已有说明。补绑会使已有报告标记待更新;不要为补包重新探测或重复创建漏洞。",
objSchema(map[string]any{"finding_id": strParam("独立漏洞记录 ID,从 list_task_findings / get_task_node_detail 的 finding_id 字段读取"), "traffic_refs": agent.HintTrafficSchema()}, "finding_id", "traffic_refs"),
func(ctx context.Context, raw json.RawMessage) (actool.Result, error) {
if !s.m.pg.GetBool(settingAgentTrafficBinding, false) {
return actool.Errorf("Agent 自动绑定流量已关闭;请在系统设置开启,或使用页面人工绑定。"), nil
}
var args struct {
FindingID json.RawMessage `json:"finding_id"`
Refs []db.TrafficRef `json:"traffic_refs"`
}
if err := json.Unmarshal(raw, &args); err != nil {
return actool.Errorf(err.Error()), nil
}
id := parseProfileID(args.FindingID)
if err := s.agentFindingTrafficAccess(ctx, id, true); err != nil {
return actool.Errorf(err.Error()), nil
}
if len(args.Refs) == 0 {
return actool.Errorf("补绑需要至少一条已核实的 traffic_refs;无流量无需调用此工具"), nil
}
list, err := s.evidenceStore().Bind(ctx, id, args.Refs)
if err != nil {
return actool.Errorf(err.Error()), nil
}
return jsonResult(trafficSummary(list))
})
}