位置漂移匹配(专业版) (Pro)
位置漂移匹配(Location Drift Matching)让重新导入能够将位置发生变化的发现项识别为同一个发现项。如果没有此功能,重新导入会通过包含位置字段的精确身份哈希来匹配发现项——因此任何位置变动都会关闭旧的发现项并创建一个完全相同的新发现项:
- 提交代码后发现项的行号发生了变化。
- 重构导致文件被重命名或移动。
- Web 应用程序的 URL、端口或主机在两次 DAST 扫描之间发生了变化。
- 依赖项的版本升级改变了 SCA 工具报告的存在漏洞的软件包版本。
以往,上述每种情况都会产生一个已关闭的发现项加上一个"新"发现项——原发现项上的状态、备注、SLA 时钟、风险接受和 JIRA 关联信息随之丢失,并产生虚假的"新的严重发现项"噪音。启用位置漂移匹配后,同一个发现项会被原地维护:其位置会根据最新扫描更新,历史记录也会得以保留。
位置漂移匹配是 DefectDojo Pro 的功能。该功能默认关闭,需要按安全工具逐个启用。
启用位置跟踪
位置跟踪按工具在以下位置配置: Settings > Finding Workflow > Reimport Deduplication(在仍使用旧版菜单布局的实例中为 Settings > Pro Settings > Deduplication Settings > Reimport Deduplication)
- 选择安全工具(Security Tool)。
- 将**去重算法(Deduplication Algorithm)设置为 Hash Code。位置跟踪仅适用于 Hash Code 算法——具备可靠的工具唯一 ID(Unique ID From Tool)**的工具已经能够通过其稳定 ID 跟踪位置变动,不需要此功能。
- 启用随位置变化跟踪发现项(Track findings as locations change)。
保存该设置会自动触发对该工具现有发现项的后台重新哈希(见下文的在现有数据上启用(升级)),因此在切换该选项之前导入的发现项也会立即参与其中。
匹配的工作原理
启用跟踪后,重新导入的匹配分两个阶段进行:
- 稳定身份。 重新导入哈希的计算不包含易变的位置字段(行号、文件路径、描述、组件名称/版本、端点)——因此发现项的身份体现的是该发现项是什么,而不是它目前位于何处。未发生移动的发现项仍然会首先精确匹配,且不会受到任何干扰。
- 证据配对。 在每一组共享相同稳定身份的发现项中,位置匹配器会使用位置证据,按从强到弱的确定性顺序,将新导入的发现项与现有发现项配对。每个发现项会根据其携带的位置数据被路由到唯一一个匹配器。
代码类发现项(SAST)
| 阶段 | 配对条件 | 说明 |
|---|---|---|
| 精确匹配 | 相同文件和行号 | 始终优先;移动的相邻项永远不能"窃取"未移动发现项的匹配 |
| 数据流 | 相同的源/汇对象(sast_source_object / sast_sink_object) | 适用于报告数据流的工具;不受行号重新编号影响 |
| 最近行号 | 相同文件,最接近的行号 | 贪心算法,就近优先;仅限同一文件内 |
| 文件重命名 | 不同文件 | 仅当新导入和现有发现项各恰好剩余一个时才生效——存在歧义则匹配失败 |
URL 类发现项(DAST)
| 阶段 | 配对条件 |
|---|---|
| 精确匹配 | 端点集合完全相同 |
| 端点集合漂移 | 端点集合有重叠(端点被增加/删除) |
| 端口变动 | 相同主机和路径,端口不同 |
| 路径漂移 | 相同主机,路径相似(双向最佳片段相似度) |
| 主机变动 | 不同主机——仅在明确的一对一配对且具备通配符 DNS 防护时生效 |
依赖类发现项(SCA)
| 阶段 | 配对条件 |
|---|---|
| 精确匹配 | 相同软件包、版本和清单文件 |
| 版本升级 | 相同软件包,不同版本 |
| 清单文件移动 | 相同软件包,不同的锁定文件/清单文件路径 |
当同一个存在漏洞的软件包出现在多个清单文件中时,每个清单文件对应的发现项会被独立跟踪——一个锁定文件中的版本升级永远不会吞并另一个清单文件中的发现项。
严重程度重新评分
安全工具会随着其规则引擎的演进重新评估严重程度。启用跟踪后,工具报告的严重程度变化不会拆分发现项的身份:该发现项仍会匹配,其严重程度会根据扫描结果更新——除非有人已手动重新分类了该严重程度,此时人工设置的值始终优先(见下文)。
哪些内容会保留,哪些内容会刷新
经过漂移匹配的发现项会保留其生命周期中所有重要的信息:状态、备注、风险接受、SLA 日期、JIRA 关联,以及其发现项 ID。
其位置字段(文件路径、行号、数据流字段、端点、组件版本)会根据新导入的扫描结果刷新。
其描述性字段(标题、描述、严重程度、组件版本)仅当扫描仍然拥有该字段的所有权时才会根据扫描结果刷新:DefectDojo 会记录每个字段在导入/重新导入时最后写入的摘要值。如果当前值仍与该摘要匹配,说明该字段是由工具写入的,扫描可以更新它;如果有人在此之后编辑过该字段,则人工设置的值会被永久保留。此功能启用之前创建的发现项没有摘要值,会被视为人工拥有——重新导入永远不会覆盖其描述性字段。唯一的例外是组件版本,它属于扫描遥测数据,人工几乎从不手动编辑:即使没有摘要值它也会刷新,因此已迁移的 SCA 发现项仍能获得版本更新。
身份始终跟踪工具的报告
当一个匹配的发现项被刷新时,其存储的身份哈希会采用新导入扫描的值——而不会根据发现项当前的字段重新计算。这一区别很重要:刷新后发现项的字段是扫描值与人工编辑的合并结果,如果根据这个合并结果计算哈希,其中会包含任何扫描都不会再报告的值,从而在后续所有重新导入中悄无声息地破坏该发现项的匹配。采用扫描值可以保证,即使有人重命名了发现项、重新分类了其严重程度或编辑了其描述,也不会影响它匹配下一次扫描的能力。
位置历史
在位置(Locations)(测试版)下,每一次漂移匹配都会记录该发现项曾经所处的位置:被取代的源代码位置、URL 或依赖项版本会作为参考信息保留在该发现项上,并标注其移动到何处以及原因。该发现项的位置时间线——“此发现项曾位于 auth.py:42,然后是 auth.py:57,然后是 session.py:31"——可在发现项页面上查看。请参阅源代码位置。
位置漂移匹配本身在启用或未启用位置功能的情况下都能工作:匹配基于发现项自身的字段和端点进行配对,因此无论哪种情况发现项都能在移动后存续下来。位置功能在此基础上增加了可记录、可查看的历史信息。历史记录从位置功能启用的那一刻开始记录——更早发生的移动虽已应用,但未被记录。
在现有数据上启用(升级)
该功能被设计为可自我迁移:
- 在您选择启用之前,一切保持不变。 开关关闭时,重新导入哈希的计算方式与以往完全相同。
- 保存开关设置会重新哈希现有发现项。 后台任务会使用新的(不含位置信息的)身份重新计算该工具已存储的重新导入哈希值,并为从开源版本迁移过来的数据创建缺失的 Pro 发现项记录。任务完成后,新旧发现项将使用相同的身份识别语言——几个月前导入的发现项与昨天导入的发现项会被完全相同地跟踪。
- 在大型实例上应选择两次扫描运行之间的空档启用。 重新哈希是针对该工具全部发现项的后台任务。如果在任务运行期间恰好有一次重新导入到达,可能会同时看到新旧两种哈希,并在处理未完成的部分时产生一次额外的变动。建议在系统空闲时切换该开关,并在下一次计划的重新导入之前让任务完成。
- 手动编辑过的标题。 选择启用后的重新哈希基于当前数据库中的值计算。所有常被编辑的字段都已从被跟踪的身份中排除——严重程度的编辑实际上会在重新哈希过程中被修复——但如果有人重命名了某个发现项的标题(且标题对该工具而言是哈希字段之一),该发现项会在下一次重新导入时产生一次变动,此后趋于稳定。
为受跟踪的工具选择哈希字段
位置跟踪会自动将易变的位置字段从重新导入哈希中移除——您不需要自行从工具的哈希配置中移除 line 或 file_path。有两种配置需要特别注意:
- 全易变字段配置。 如果某个工具的哈希字段完全由位置字段组成(例如仅有
file_path+line),移除它们后将不剩任何字段,此时哈希会回退到传统的"标题 + CWE"身份识别方式。匹配仍然有效——证据匹配阶段承担了区分工作——但身份识别的粒度会粗糙得多。建议优先选择至少保留一个稳定内容字段的配置。 - 位置信息嵌入在稳定字段中。 当位置信息隐藏在必须保留在哈希中的字段内部时,字段排除机制将无能为力。例如某工具将发现项标题命名为"queries.py:42 中的 SQL 注入”,那么每次行号移动都会导致标题变化——身份因此被拆分,跟踪功能无法察觉这是同一项。对于此类工具,应选择不会泄露位置信息的哈希字段;CWE + 内容指纹(Content Fingerprint) 是一个有效的组合(见内容指纹)。
与去重功能的交互
位置跟踪是一项重新导入功能:同工具去重(Same Tool)和跨工具去重(Cross Tool Deduplication)不受影响——它们的哈希计算方式与以往完全相同,字段排除规则也不适用于它们。有两处刻意实现的联动:
- 版本升级不再阻碍依赖项去重。 去重的位置门槛通常要求两个 SCA 发现项引用完全相同的软件包版本。对于启用了跟踪功能的工具,共享的软件包身份(生态系统 + 软件包名称,若双方都携带命名空间信息则一并比较)即已足够——这与重新导入将版本升级视为同一发现项的处理方式保持一致。此规则仅适用于"位置(Locations)“功能下的同工具去重。
- 纯净的身份输入。 由于匹配的发现项会采用扫描报告的哈希值,去重所使用的值始终反映工具最后一次报告的内容——人工编辑不再可能污染这些值。
整合历史累积的变动
多年来在未启用跟踪功能的情况下运行的实例会积累"关闭后重建"的链条:同一个发现项每次移动时都会被关闭并作为新记录重新打开。管理命令会查找这些链条(通过相同的匹配器逐跳关联,并设有生命周期重叠防护,确保确实同时存在的发现项永远不会被合并),并将每条链条整合到其最新的发现项上,将较旧的副本标记为该存留项的重复项:
# Dry run — reports what would be consolidated, changes nothing
./manage.py consolidate_location_churn --product <id>
# Apply, with a confirmation prompt
./manage.py consolidate_location_churn --product <id> --apply该命令默认以试运行模式执行,绝不会自动运行,并可通过 --test 或 --product 限定范围。在"位置(Locations)“功能下,存留项的位置历史会根据链条重新构建。
保护机制与限制
- 精确匹配始终优先。 未移动的发现项会在任何模糊匹配阶段运行之前完成精确配对;移动的发现项永远无法窃取其匹配结果。
- 存在歧义则匹配失败。 文件重命名和主机变动仅在双方各恰好剩余一个候选项时才会配对。如果同时有两个发现项消失、两个新发现项出现,系统会保持不匹配状态,而不会去猜测。
- 超大分组会优雅降级。 如果单个身份分组超过配对上限(40,000 次比较),该分组的匹配会降级为仅精确匹配,而不会消耗无限的时间。
- 可接受的权衡: 一对一的重命名/主机变动匹配阶段,在某个发现项消失、而同一次重新导入中出现了具有相同稳定身份的无关发现项时,可能产生虚假的连续性。这是为了跟踪重命名而刻意付出的代价;稳定身份(相同工具、标题、CWE、严重程度……)限制了配对可能出错的程度。
无需开关即可刷新位置
与位置跟踪功能无关,重新导入会在所有算法下让每个匹配的发现项保持位置信息最新:通过工具唯一 ID(或任何其他算法)匹配的发现项,会根据新导入的报告刷新其 line、file_path、数据流字段和 component_version,新报告的端点会被附加,已消失的端点会被标记为已缓解。扫描中省略的值永远不会覆盖现有数据,人工锁定的组件版本会被保留。此改动填补了长期存在的问题:此前通过 uid 匹配的 SAST 发现项会永远显示其首次导入时的行号。可通过 DD_REIMPORT_REFRESH_LOCATION_FIELDS=False 在整个实例范围内禁用此功能。