背景
当前 full 和 delta pipeline 中:
- 多个
scan-target task 可能从不同 target、调用路径或扫描入口产出同一个 candidate
- 每个 candidate 随后独立进入 analysis、verification 和 triage,重复 candidate 会浪费大量 Agent token
- 即使 candidate 表面不同,最终也可能指向同一个安全漏洞,产生重复
security_issue
因此需要在 pipeline 中设置两个去重关口:
scan-target 之后对 candidate 去重
- 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²) 全量比较:
- 先根据规范化代码位置、符号、漏洞类型、source/sink 和 fingerprint 建立候选组。
- 仅在候选组内部调用 Agent 做语义判断。
- 优先选择证据最完整、路径最准确的 candidate 或 finding 作为 canonical。
- 不能可靠判断时保持独立,不强制合并。
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 竞态测试
背景
当前 full 和 delta pipeline 中:
scan-targettask 可能从不同 target、调用路径或扫描入口产出同一个 candidatesecurity_issue因此需要在 pipeline 中设置两个去重关口:
scan-target之后对 candidate 去重本 issue 负责单个 scan job 内的 pipeline 去重。跨 job、project/system 级漏洞归并和持续管理继续由 #3 负责。
目标
deduplicate-candidatesstagededuplicate-findingsstagePipeline 结构
两个 stage 都是 job 级 fan-in/barrier:
full pipeline 必须等待所有可能产生 candidate 的
scan-target分支收敛后运行 candidate 去重。delta pipeline 同样适用。当前 YAML edge 只有 map/fan-out 语义;需要增加明确的
fanIn/finalizerbarrier 表达能力,不能通过轮询或硬编码 stage 名称实现。Candidate 去重 Stage
输入
收集本 job 所有有效
scan-target输出中的 candidate:输出
每个 group 至少包含:
只有
canonicalCandidates通过 fan-out edge 进入analyze-finding。没有 candidate 时输出空数组并正常完成。Candidate 判重原则
优先依据:
不同 scan target 指向同一危险操作和根因时可以合并;仅漏洞类型相同但代码位置、根因或修复方式不同的 candidate 必须保持独立。
Security Issue 去重 Stage
输入
等待所有 triage 分支收敛,只收集:
result=security_issue,或isSecurityIssue=true每个 finding 引用其 candidate、analysis、verification、triage 和关键 artifact。
输出
每个 group 至少包含 canonical finding、duplicate findings、共同根因、差异、修复位置、confidence 和 uncertainty。没有 security issue 时输出空集合并正常完成 pipeline。
去重策略
两个 stage 均采用“确定性预分组 + Agent 语义判断”,避免 O(n²) 全量比较:
Candidate 去重侧重“是否值得重复分析”;最终 finding 去重侧重“是否属于同一个根因和修复项”。两者不能互相替代。
持久化与展示
验收标准
scan-target后执行一次 candidate 去重