feat(overview): graph_overview 全量输出 finding_list
findings 是任务最高价值产物且单任务通常不多,改为在 graph_overview
里全量带出 finding_list(不像 facts 只给最近窗口),planner/worker 每轮
即可一眼看全所有确认漏洞,无需再调 list_findings。
每条精简为 {id, summary, evidence?, from_intent?, assets?}:
- evidence 取 finding payload 的 evidence.poc
- from_intent 为产生该漏洞的意图(yields 边)
- assets 直接给受影响资产的可读内容(url/域名/ip:port 等,不再是裸 id),
经 FindingMetaByNodeID 反查锚定资产、批量 GetByIDs 解析
- vulnclass/severity/state 等仍走 list_findings / node_detail
新增 AssetStore.FindingMetaByNodeID 薄封装,让只持有 AssetStore 的
ToolSet 能拿到 node_id→资产映射(读 anchors 表,覆盖新旧漏洞)。
This commit is contained in:
1 parent
f270ca66bc
commit
d728e509dc
3 files changed
+94
-1
No files matched your search
+1
-1
@@ -279,7 +279,7 @@ const plannerDefaultTmpl = `你是一个 ARTEX 平台授权渗透测试系统的
|
||||
|
||||
**每次唤醒的决策流程**:
|
||||
|
||||
1. **完整态势已附在本提示下方**(就是 graph_overview 的返回,无需再调它):task(原始标题+目标/根节点)、资产计数、goals+状态、open/running/recent_done 意图、sites_without_endpoints(无端点的站点,提示可能待探的方向)、findings(确认漏洞数)、facts(探索事实数,与漏洞是两类)、recent_facts({id,summary,confidence?})。
|
||||
1. **完整态势已附在本提示下方**(就是 graph_overview 的返回,无需再调它):task(原始标题+目标/根节点)、资产计数、goals+状态、open/running/recent_done 意图、sites_without_endpoints(无端点的站点,提示可能待探的方向)、facts(探索事实数,与漏洞是两类)、recent_facts({id,summary,confidence?})。
|
||||
- **范围**:探索节点(goals/意图/facts/findings)只含本任务;**资产图全局共享**(多任务同一份,资产计数是全局在范围内的、非本任务独有)——出现非本任务相关的资产时忽略。
|
||||
- **血缘**:每个意图带 parents(上游:派生自哪些事实/意图)和 yields(下游:产生了哪些事实/发现),recent_facts 每条带 from_intent;据此理解"哪些事实来自哪个方向、能否综合出新方向"。
|
||||
- **否定/存疑观察**(recent_facts 里"端口关闭/不可注入"等)是 worker 的观察、不是定论:采信前先 node_detail(id) 看 evidence——evidence 扎实、confidence=observed 且手段已穷尽的才视为该方向暂时封住;evidence 缺失、只是"看起来像/只探一次"、或 confidence=inferred 的,按【尚未探明】处理,若在范围内且无其它意图覆盖,默认派一条复核意图去证实或推翻(**同一否定方向至多复核一次**;复核后仍为否定、且证据合理,就尊重该结论、不再派)。
|
||||
|
||||
@@ -427,6 +427,63 @@ func (t *ToolSet) graphOverviewData() map[string]any {
|
||||
factNodes, _ := t.ts.ListByKind(db.KindFact, 1000) // newest first
|
||||
out["findings"] = len(vulnNodes) // 确认漏洞数(目标判定看它)
|
||||
out["facts"] = len(factNodes) // 探索事实/结论数(含否定结论)
|
||||
// findings 是任务里最高价值的产物、单任务通常也不多 → 直接全量带进概览(不像 facts 那样
|
||||
// 只给最近窗口),让 planner 每轮判目标时一眼看全所有确认漏洞,无需再调 list_findings。
|
||||
// 每条只留 {id, summary, evidence?, from_intent?, assets?}:evidence 是 report_finding 的
|
||||
// PoC 文本(payload.evidence.poc);from_intent 是产生本漏洞的意图;assets 直接给受影响资产
|
||||
// 的可读内容(url/域名/ip:port 等,不再是裸 id)——锚定关系存于 exploration_anchors、经 findings
|
||||
// 表回填。vulnclass/severity/state 等仍可用 list_findings / node_detail(id) 取。
|
||||
var findingMeta map[int64]db.FindingMeta // node_id -> 锚定资产等;仅任务上下文可查
|
||||
assetByID := map[int64]*db.Asset{}
|
||||
if t.as != nil && t.taskID > 0 {
|
||||
findingMeta, _ = t.as.FindingMetaByNodeID(t.taskID)
|
||||
idSet := map[int64]struct{}{}
|
||||
for _, meta := range findingMeta {
|
||||
for _, aid := range meta.AssetIDs {
|
||||
idSet[aid] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(idSet) > 0 {
|
||||
ids := make([]int64, 0, len(idSet))
|
||||
for aid := range idSet {
|
||||
ids = append(ids, aid)
|
||||
}
|
||||
if assets, err := t.as.GetByIDs(ids); err == nil {
|
||||
for _, a := range assets {
|
||||
assetByID[a.ID] = a
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findingList := make([]map[string]any, 0, len(vulnNodes))
|
||||
for _, n := range vulnNodes {
|
||||
var fp map[string]any
|
||||
_ = json.Unmarshal(n.Payload, &fp)
|
||||
m := map[string]any{"id": n.ID, "summary": fp["summary"]}
|
||||
if ev, ok := fp["evidence"].(map[string]any); ok {
|
||||
if poc, ok := ev["poc"].(string); ok && poc != "" {
|
||||
m["evidence"] = poc
|
||||
}
|
||||
}
|
||||
if from := factFrom[n.ID]; from > 0 {
|
||||
m["from_intent"] = from // 本漏洞由哪个意图产生
|
||||
}
|
||||
if meta, ok := findingMeta[n.ID]; ok && len(meta.AssetIDs) > 0 {
|
||||
assets := make([]string, 0, len(meta.AssetIDs))
|
||||
for _, aid := range meta.AssetIDs {
|
||||
if a := assetByID[aid]; a != nil {
|
||||
if v := assetValue(a); v != "" {
|
||||
assets = append(assets, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
assets = append(assets, fmt.Sprintf("#%d", aid)) // 资产已删/查不到 → 退回 id 标记,别丢信息
|
||||
}
|
||||
m["assets"] = assets // 受影响资产的可读内容
|
||||
}
|
||||
findingList = append(findingList, m)
|
||||
}
|
||||
out["finding_list"] = findingList
|
||||
recentFacts := make([]map[string]any, 0, 20)
|
||||
for _, n := range factNodes {
|
||||
if len(recentFacts) >= 20 {
|
||||
@@ -853,6 +910,35 @@ func compactNode(n *db.Node) map[string]any {
|
||||
return m
|
||||
}
|
||||
|
||||
// assetValue distills an asset to its most identifying human-readable string
|
||||
// (url / domain / ip[:port] / app / service name) so finding_list can show the
|
||||
// affected asset's content inline instead of a bare id. Empty when nothing
|
||||
// identifying is set (caller falls back to #id).
|
||||
func assetValue(a *db.Asset) string {
|
||||
switch {
|
||||
case a.URL != "":
|
||||
if a.Method != "" {
|
||||
return a.Method + " " + a.URL // 接口:带上 HTTP 方法
|
||||
}
|
||||
return a.URL
|
||||
case a.Domain != "":
|
||||
if a.Port != nil {
|
||||
return fmt.Sprintf("%s:%d", a.Domain, *a.Port)
|
||||
}
|
||||
return a.Domain
|
||||
case a.IP != "":
|
||||
if a.Port != nil {
|
||||
return fmt.Sprintf("%s:%d", a.IP, *a.Port)
|
||||
}
|
||||
return a.IP
|
||||
case a.AppName != "":
|
||||
return a.AppName
|
||||
case a.ServiceName != "":
|
||||
return a.ServiceName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// compactFinding is compactNode plus the vuln-specific vulnclass/severity.
|
||||
func compactFinding(n *db.Node) map[string]any {
|
||||
var p map[string]any
|
||||
|
||||
@@ -742,6 +742,13 @@ type FindingMeta struct {
|
||||
AssetIDs []int64
|
||||
}
|
||||
|
||||
// FindingMetaByNodeID maps a task's finding node ids to their standalone-row
|
||||
// metadata (status + anchored asset ids) via the asset store, so callers holding
|
||||
// only an AssetStore (e.g. the agent ToolSet) can reach it without a raw *DB.
|
||||
func (a *AssetStore) FindingMetaByNodeID(taskID int64) (map[int64]FindingMeta, error) {
|
||||
return a.db.FindingMetaByNodeID(taskID)
|
||||
}
|
||||
|
||||
// FindingMetaByNodeID maps a task's finding node ids to their standalone-row
|
||||
// metadata, so the per-task view (which reads exploration nodes) can show and
|
||||
// edit the same status — and the same anchored assets — as the global 发现 page.
|
||||
|
||||
Reference in new issue
Block a user