Skip to content

在 Pipeline 中增加 Candidate 与 Security Issue 去重 Stage #24

Description

@Kherrisan

背景

当前 full 和 delta pipeline 中:

  • 多个 scan-target task 可能从不同 target、调用路径或扫描入口产出同一个 candidate
  • 每个 candidate 随后独立进入 analysis、verification 和 triage,重复 candidate 会浪费大量 Agent token
  • 即使 candidate 表面不同,最终也可能指向同一个安全漏洞,产生重复 security_issue

因此需要在 pipeline 中设置两个去重关口:

  1. scan-target 之后对 candidate 去重
  2. pipeline 最后对 triage 为 security issue 的 finding 再次去重

本 issue 负责单个 scan job 内的 pipeline 去重。跨 job、project/system 级漏洞归并和持续管理继续由 #3 负责。

目标

  • 增加 job 级 deduplicate-candidates stage
  • 增加 job 级 deduplicate-findings stage
  • 重复 candidate 只选择 canonical candidate 进入昂贵的下游分析
  • 最终 security issue 再按根因和修复位置归并
  • 保留所有原始 candidate、analysis、verification 和 triage 数据,不物理删除
  • 输出稳定、结构化、可追溯的 canonical/duplicate 关系

Pipeline 结构

两个 stage 都是 job 级 fan-in/barrier:

scan-target (fan-out)
  -> deduplicate-candidates (fan-in, once per job)
  -> analyze-finding (fan-out canonical candidates)
  -> critique-finding
  -> verify-finding
  -> triage-finding
  -> deduplicate-findings (fan-in, once per job)
  -> pipeline complete

full pipeline 必须等待所有可能产生 candidate 的 scan-target 分支收敛后运行 candidate 去重。delta pipeline 同样适用。

当前 YAML edge 只有 map/fan-out 语义;需要增加明确的 fanIn/finalizer barrier 表达能力,不能通过轮询或硬编码 stage 名称实现。

Candidate 去重 Stage

输入

收集本 job 所有有效 scan-target 输出中的 candidate:

DeduplicateCandidatesStageInput:
  type: object
  required: [scanJob, candidates]
  properties:
    scanJob:
      $ref: "#/schemas/ScanJobSnapshot"
    candidates:
      type: array
      items:
        $pathOf: "#/schemas/Candidate"

输出

CandidateDeduplicationResult:
  type: object
  required: [groups, canonicalCandidates]
  properties:
    groups:
      type: array
      items:
        $ref: "#/schemas/CandidateDeduplicationGroup"
    canonicalCandidates:
      type: array
      items:
        $pathOf: "#/schemas/Candidate"

每个 group 至少包含:

  • 稳定 group ID / fingerprint
  • canonical candidate ID
  • duplicate candidate IDs
  • 判定依据、共同代码位置和根因线索
  • confidence 和 uncertainty

只有 canonicalCandidates 通过 fan-out edge 进入 analyze-finding。没有 candidate 时输出空数组并正常完成。

Candidate 判重原则

优先依据:

  • repository/module 和规范化代码位置
  • source、sink、关键调用链和危险操作
  • 漏洞类别/CWE 与攻击前置条件
  • 根因符号、修复位置和受影响数据流

不同 scan target 指向同一危险操作和根因时可以合并;仅漏洞类型相同但代码位置、根因或修复方式不同的 candidate 必须保持独立。

Security Issue 去重 Stage

输入

等待所有 triage 分支收敛,只收集:

  • result=security_issue,或
  • isSecurityIssue=true
DeduplicateFindingsStageInput:
  type: object
  required: [scanJob, findings]
  properties:
    scanJob:
      $ref: "#/schemas/ScanJobSnapshot"
    findings:
      type: array
      items:
        $pathOf: "#/schemas/SecurityIssueFinding"

每个 finding 引用其 candidate、analysis、verification、triage 和关键 artifact。

输出

FindingDeduplicationResult:
  type: object
  required: [groups, canonicalFindings]
  properties:
    groups:
      type: array
      items:
        $ref: "#/schemas/FindingDeduplicationGroup"
    canonicalFindings:
      type: array
      items:
        $pathOf: "#/schemas/SecurityIssueFinding"

每个 group 至少包含 canonical finding、duplicate findings、共同根因、差异、修复位置、confidence 和 uncertainty。没有 security issue 时输出空集合并正常完成 pipeline。

去重策略

两个 stage 均采用“确定性预分组 + Agent 语义判断”,避免 O(n²) 全量比较:

  1. 先根据规范化代码位置、符号、漏洞类型、source/sink 和 fingerprint 建立候选组。
  2. 仅在候选组内部调用 Agent 做语义判断。
  3. 优先选择证据最完整、路径最准确的 candidate 或 finding 作为 canonical。
  4. 不能可靠判断时保持独立,不强制合并。

Candidate 去重侧重“是否值得重复分析”;最终 finding 去重侧重“是否属于同一个根因和修复项”。两者不能互相替代。

持久化与展示

  • 分别保存 candidate dedup group 和 finding dedup group
  • 不修改或删除原始 candidate/finding,仅记录 canonical/duplicate 关系
  • Candidates 页面可切换查看 raw candidates、canonical candidates 和 duplicates
  • Job 统计展示:
    • raw candidates / deduplicated candidates
    • raw security issues / deduplicated security issues
  • Task detail 可查看两个 dedup stage 的输入、输出、prompt、日志和分组依据
  • Rerun 去重 stage 时原子替换该 job 当前有效的 dedup projection,保留历史 task output

验收标准

  • full/delta pipeline 均在 scan-target 后执行一次 candidate 去重
  • 只有 canonical candidate 进入 analysis,duplicate candidate 不再消耗下游 Agent token
  • pipeline 末端执行一次 security issue 去重
  • 两个 stage 都不会在各自上游分支尚未收敛时提前运行
  • 空 candidate 或空 security issue 均能输出空结果并完成 pipeline
  • 原始数据完整保留,canonical/duplicate 关系可追溯
  • UI 同时展示 candidate 和 security issue 去重前后数量
  • pipeline 重启、Pause/Resume 和 stage rerun 不会重复创建有效 dedup group
  • 覆盖空输入、单项、明确重复、相似但独立、部分上游失败及 fan-in 竞态测试

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions