Skip to main content

对代码扫描的 SARIF 支持

要在 GitHub 上的仓库中显示第三方静态分析工具的结果,您需要将结果存储在 SARIF 文件中,以支持用于 code scanning 的 SARIF 2.1.0 JSON 架构的特定子集。 如果使用默认 CodeQL 静态分析引擎,结果将自动显示于您在 GitHub 上的仓库中。

谁可以使用此功能?

启用了 GitHub Advanced Security 的组织拥有的存储库

关于 SARIF 支持

SARIF(静态分析结果交换格式)是一种定义输出文件格式的 OASIS 标准。 SARIF 标准用于简化静态分析工具分享其结果的方式。 Code scanning 支持 SARIF 2.1.0 JSON 架构的子集。

要从第三方静态代码分析引擎上传 SARIF 文件,需确保上传的文件使用 SARIF 2.1.0 版本。 GitHub 将剖析 SARIF 文件,并在 code scanning 过程中使用仓库中的结果显示警报。 有关详细信息,请参阅“将 SARIF 文件上传到 GitHub”。 有关 SARIF 2.1.0 JSON 架构的详细信息,请参阅 sarif-schema-2.1.0.json

如果结合使用 GitHub Actions 和 CodeQL 分析工作流程,或者使用 CodeQL CLI,则 code scanning 结果将自动使用受支持的 SARIF 2.1.0 子集。 有关详细信息,请参阅“配置代码扫描的高级设置”或“在现有 CI 系统上使用代码扫描”。

GitHub 使用 SARIF 文件中的属性来显示警报。 例如,shortDescriptionfullDescription 出现在 code scanning 警报的顶部。 location 允许 GitHub 在代码文件中显示注释。 有关详细信息,请参阅“关于代码扫描警报”。

如果你不熟悉 SARIF 并想要了解详细信息,请参阅 Microsoft 的 SARIF tutorials 存储库。

提供用于跨运行跟踪 code scanning 警报的数据

每次上传新代码扫描的结果时,都会处理结果并将警报添加到存储库中。 为防止出现针对同一问题的重复警报,code scanning 使用指纹匹配各个运行的结果,使它们只会出现在所选分支的最新运行中出现一次。 这样可以在编辑文件时将警报与正确的代码行匹配。 结果的 ruleID 必须在分析中相同。

报告一致的文件路径

文件路径必须在运行间保持一致,以实现稳定指纹的计算。 如果文件路径对于相同结果有所不同,则每次进行新分析时,都会创建新警报,并关闭旧警报。 这会导致相同结果具有多个警报。

包含用于生成指纹的数据

GitHub 使用 OASIS 标准中的 partialFingerprints 属性来检测两个结果在逻辑上是否相同。 有关详细信息,请参阅 OASIS 文档中的“partialFingerprints 属性”条目。

通过 CodeQL 分析工作流程 或 CodeQL CLI 创建的 SARIF 文件包含指纹数据。 如果使用 upload-sarif 操作上传 SARIF 文件并且此数据丢失,GitHub 会尝试从源文件填充 partialFingerprints 字段。 有关上传结果的详细信息,请参阅“将 SARIF 文件上传到 GitHub”。

如果使用 /code-scanning/sarifs API 终结点上传无指纹数据的 SARIF 文件,code scanning 警报将被处理并显示,但用户可能会看到重复的警报。 为避免看到重复警报,应在上传 SARIF 文件之前计算指纹数据并填充 partialFingerprints 属性。 你可能会发现 upload-sarif 操作使用一个有用的起点的脚本: https://github.com/github/codeql-action/blob/main/src/fingerprints.ts 。 有关 API 的详细信息,请参阅“适用于代码扫描的 REST API 终结点”。

了解规则和结果

SARIF 文件支持规则和结果。 这些元素中存储的信息相似,但用途不同。

  • 规则是包含在 toolComponent 对象中的 reportingDescriptor 对象数组。 可在此处存储分析过程中运行的规则的详细信息。 这些对象中的信息应该很少更改,通常在更新工具时更改。

  • 结果存储为 run 对象中 results 下的一系列 result 对象。 每个 result 对象都包含代码库中一个警报的详细信息。 在 results 对象中,可以引用检测到警报的规则。

当比较通过使用相同工具和规则分析不同代码库生成的 SARIF 文件时,你应会看到分析结果(而不是规则)存在差异。

指定源文件的位置

指定源文件位置和代码行可确保代码扫描警报准确显示在包含已识别问题的文件内,以便有针对性地解决问题。

这种精准率提高了代码评审和解决过程的效率,简化了开发工作流,使开发人员能够在他们的代码库上下文中直接解决问题。

当警报标识的所有代码行都存在于拉取请求差异中时,Code scanning 还将在拉取请求检查结果中显示警报。

若要在拉取请求检查中显示,警报必须满足以下所有条件:

  • 警报标识的所有代码行(包括警报的第一行)都存在于拉取请求差异中。
  • 警报必须存在于拉取请求中添加或编辑的代码行中,而不是已删除的行中。

提交的 SARIF 文件中的 physicalLocation 对象标识警报的代码行。 有关详细信息,请参阅“physicalLocation 对象”。

指定源文件的根

Code scanning 将使用相对路径报告的结果解释为相对于所分析的存储库的根。 如果结果包含绝对 URI,则 URI 会转换为相对 URI。 随后可以将相对 URI 与提交到存储库的文件进行匹配。

可以通过以下方法之一提供源根,用于从绝对 URI 转换为相对 URI。

如果提供源根,则使用绝对 URI 指定的项目的任何位置都必须使用相同 URI 方案。 如果源根的 URI 方案与一个或多个绝对 URI 不匹配,则上传会被拒绝。

例如,使用 file:///github/workspace 的源根上传 SARIF 文件。

# Conversion of absolute URIs to relative URIs for location artifacts

file:///github/workspace/src/main.go -> src/main.go
file:///tmp/go-build/tmp.go          -> file:///tmp/go-build/tmp.go

文件会成功上传,因为两个绝对 URI 都使用与源根相同的 URI 方案。

如果结果的相对 URI 与使用符号链接定义的文件相匹配,则代码扫描将无法显示结果。 因此,你需要解析任何符号链接的文件,并使用解析的 URI 报告这些文件中的任何结果。

验证 SARIF 文件

您可以根据 GitHub 引入规则测试 SARIF 文件是否兼容 code scanning。 有关详细信息,请访问 Microsoft SARIF 验证程序

对于每个 gzip 压缩 SARIF 文件,SARIF 上传支持的最大大小为 10 MB。 任何超过此限制的上传都将被拒绝。 如果 SARIF 文件由于包含太多结果而过大,则应更新配置,以将重点放在最重要的规则或查询的结果上。 有关详细信息,请参阅“SARIF 结果文件太大”。

Code scanning 支持上传下表中数据对象的最大条目数。 如果这些对象中的任何一个超过其最大值,则 SARIF 文件将遭到拒绝。 对于某些对象,将显示的值的数量还有额外限制。 尽可能显示最重要的值。 当分析包含的数据超出支持的限制时,要充分利用分析,请尝试优化分析配置(例如对于 CodeQL 工具,识别并禁用干扰性最强的查询)。 有关详细信息,请参阅“SARIF 结果超出一个或多个限制”。

SARIF 数据最大值数据截断限制
每个文件的运行次数20
每次运行的结果数25,000只包括前 5,000 条结果,并按严重性确定优先级。
每次运行的规则数25,000
每次运行的工具扩展数100
每个结果的线程流位置数10,000只包括前 1,000 个线程流位置,按优先级排序。
每个结果的位置数1,000只包括 100 个位置。
每个规则的标记数20只包括 10 个标记。
警报限制1,000,000

有关其他错误的信息,请参阅“排查 SARIF 上传问题”。

为提交上传多个 SARIF 文件

您可以为相同的提交上传多个 SARIF 文件,并将每个文件中的数据显示为 code scanning 结果。 为一个提交上传多个 SARIF 文件时,必须为每个分析指定一个“类别”。 指定类别的方式因分析方法而异:

  • 直接使用 CodeQL CLI,在生成 SARIF 文件时将 --sarif-category 参数传递给 codeql database analyze 命令。 有关详细信息,请参阅“关于 CodeQL CLI”。
  • 将 GitHub Actions 与 codeql-action/analyze 结合使用,类别自动从工作流程名称和任何矩阵变量(通常是 language)设置。 可以通过为操作指定 category 输入来覆盖此值,这在单个工作流程中分析单一存储库的不同部分时非常有用。
  • 使用 GitHub Actions 从其他静态分析工具上传结果,如果在一个工作流程中为同一工具上传多个结果文件,则必须指定 category 输入。 有关详细信息,请参阅“将 SARIF 文件上传到 GitHub”。
  • 如果不使用这两种方法中的任何一种,则必须在每个 SARIF 文件中指定要上传的唯一 runAutomationDetails.id。 有关此属性的详细信息,请参阅“runAutomationDetails 对象”。

如果为具有相同类别和来自同一工具的提交上传第二个 SARIF 文件,则之前的结果将被覆盖。 但是,如果尝试在单个 GitHub Actions 工作流程运行中为同一工具和类别上传多个 SARIF 文件,则会检测到配置错误,并且运行将失败。

支持的 SARIF 输出文件属性

如果您使用 CodeQL 以外的代码分析引擎,则可以查看受支持的 SARIF 属性来优化您的分析结果在 GitHub 中的显示方式。

注意:必须为标记为“必填”的任何属性提供显式值。 必填属性不支持空字符串。

任何有效的 SARIF 2.1.0 输出文件都可以上传,但 code scanning 只使用以下受支持的属性。

(属于sarifLog 对象)的父级。

名称必需说明
$schema版本 2.1.0 的 SARIF JSON 架构的 URI。 例如,https://json.schemastore.org/sarif-2.1.0.json
versionCode scanning 仅支持 SARIF 版本 2.1.0
runs[]SARIF 文件包含一个或多个运行的数组。 每个运行代表分析工具的一次运行。 有关 run 的详细信息,请参阅 run 对象

(属于run 对象)的父级。

Code scanning 使用 run 对象按工具筛选结果并提供关于结果来源的信息。 run 对象包含 tool.driver 工具组件对象,该对象包含有关生成结果的工具的信息。 每个 run 只能获得一个分析工具的结果。

名称必需说明
tool.driver描述分析工具的 toolComponent 对象。 有关详细信息,请参阅 toolComponent 对象
tool.extensions[]表示工具在分析期间使用的任何插件或扩展的 toolComponent 对象数组。 有关详细信息,请参阅 toolComponent 对象
invocation.workingDirectory.uri仅当未提供 checkout_uri(仅限 SARIF 上传 API)或 checkout_path(仅限 GitHub Actions)时,才使用此字段。 该值用于将 physicalLocation 对象中使用的绝对 URI 转换为相对 URI。 有关详细信息,请参阅“指定源文件的根”。
results[]分析工具的结果。 Code scanning 在 GitHub 上显示结果。 有关详细信息,请参阅 result 对象

(属于toolComponent 对象)的父级。

名称必需说明
name分析工具的名称。 Code scanning 在 GitHub 上显示名称,以允许按工具筛选结果。
version分析工具的版本。 Code scanning 使用版本号来跟踪结果何时可能由于工具版本更改而不是所分析代码的更改而发生更改。 如果 SARIF 文件包含 semanticVersion 字段,则 code scanning 不使用 version
semanticVersion以语义版本 2.0 格式指定的分析工具版本。 Code scanning 使用版本号来跟踪结果何时可能由于工具版本更改而不是所分析代码的更改而发生更改。 如果 SARIF 文件包含 semanticVersion 字段,则 code scanning 不使用 version。 有关详细信息,请参阅语义版本控制文档中的“语义版本控制 2.0.0”。
rules[]表示规则的 reportingDescriptor 对象数组。 分析工具使用规则来查找所分析代码中的问题。 有关详细信息,请参阅 reportingDescriptor 对象

(属于reportingDescriptor 对象)的父级。

可在此处存储分析过程中运行的规则的详细信息。 这些对象中的信息应该很少更改,通常在更新工具时更改。 有关详细信息,请参阅上面的“了解规则和结果”。

名称必需说明
id规则的唯一标识符。 id 是从 SARIF 文件的其他部分引用的,可能被 code scanning 用于在 GitHub 上显示 URL。
name规则的名称。 Code scanning 显示名称,以允许在 GitHub 上按规则筛选结果。 限制为 255 个字符。
shortDescription.text规则的简介。 Code scanning 在 GitHub 上的相关结果旁边显示简短说明。 限制为 1024 个字符。
fullDescription.text规则的描述。 Code scanning 在 GitHub 上的相关结果旁边显示完整说明。 限制为 1024 个字符。
defaultConfiguration.level规则的默认严重性级别。 Code scanning 使用严重性级别来帮助你了解结果对于给定规则的重要程度。 默认情况下,defaultConfiguration.level 设置为 warning。 但是,可以通过在与结果关联的对象 result 中设置 level 属性,替代默认的规则级别。 有关详细信息,请参考与 result 对象有关的文档。 defaultConfiguration.level 的有效值为:notewarningerror
help.text使用文本格式的规则的文档。 Code scanning 在相关结果旁边显示此帮助文档。
help.markdown(建议)使用 Markdown 格式的规则的文档。 Code scanning 在相关结果旁边显示此帮助文档。 当 help.markdown 可用时,将显示它,而不是 help.text
properties.tags[]字符串数组。 Code scanning 使用 tags 允许你在 GitHub 上筛选结果。 例如,可以筛选带标记 security 的所有结果。
properties.precision(建议)一个字符串,指示此规则指示的结果为 true 的频率。 例如,如果已知某项规则的误报率较高,则其准确性应为 low。 Code scanning 在 GitHub 上按准确性对结果进行排序,使具有最高 level 和最高 precision 的结果显示在最前面。 可以是下述之一:very-highhighmediumlow
properties.problem.severity(建议)一个字符串,指示由非安全查询生成的任何警报的严重性级别。 这与 properties.precision 属性一起确定结果是否默认显示在 GitHub 上,使具有最高 problem.severity 和最高 precision 的结果显示在最前面。 可以是以下选项之一:errorwarningrecommendation
properties.security-severity(建议仅用于安全规则)如果包含此字段的值,规则的结果将被视为安全结果。 一个表示分数的字符串,该分数指示安全查询的严重性级别,该值介于 0.0 到 10.0 之间(@tags 包括 security)。 这与 properties.precision 属性一起确定结果是否默认显示在 GitHub 上,使具有最高 security-severity 和最高 precision 的结果显示在最前面。 Code scanning 转换数值分数如下:超过 9.0 为 critical,7.0 至 8.9 为 high,4.0 至 6.9 为 medium,0.1 至 3.9 为 low。 值 0.0 或给定范围之外的任何其他值被视为没有安全严重性。

(属于result 对象)的父级。

每个 result 对象都包含代码库中一个警报的详细信息。 在 results 对象中,可以引用检测到警报的规则。 有关详细信息,请参阅上面的“了解规则和结果”。

可以检查 SARIF 属性是否具有支持上传的大小,以及该文件是否与代码扫描兼容。 有关详细信息,请参阅“对代码扫描的 SARIF 支持”。

名称必需说明
ruleId规则的唯一标识符 (reportingDescriptor.id)。 有关详细信息,请参阅 reportingDescriptor 对象。 Code scanning 使用规则标识符在 GitHub 上按规则筛选结果。
ruleIndex工具组件 rules 数组中关联规则(reportingDescriptor 对象)的索引。 有关详细信息,请参阅 run 对象。 此属性的允许范围 0 到 2^63 - 1。
rule用于定位此结果的规则(报告描述符)的引用。 有关详细信息,请参阅 reportingDescriptor 对象
level结果的严重性。 此级别覆盖规则定义的默认严重程度。 Code scanning 使用级别在 GitHub 上按严重性筛选结果。
message.text描述结果的消息。 Code scanning 显示消息文本作为结果的标题。 当可见空间有限时,仅显示消息的第一句。
locations[]最多可以检测到 10 个结果的位置集。 应只包含一个位置,除非只能通过在每个指定位置进行更改来更正问题。 注意: code scanning 至少需要一个位置才能显示结果。 Code scanning 将使用此属性来决定要用结果注释哪个文件。 仅使用此数组的第一个值。 所有其他值都被忽略。
partialFingerprints一组字符串,用于跟踪结果的唯一标识。 Code scanning 使用 partialFingerprints 准确地识别在提交和分支之间相同的结果。 Code scanning 将尝试使用 partialFingerprints(如果存在)。 如果使用 upload-action 上传第三方 SARIF 文件,当这些文件未包含在 SARIF 文件中时,该操作将为你创建 partialFingerprints。 有关详细信息,请参阅“提供用于跨运行跟踪代码扫描警报的数据”。 注意:Code scanning 仅使用 primaryLocationLineHash
codeFlows[].threadFlows[].locations[]threadFlow 对象的 location 对象数组,通过执行线程描述程序进度。 codeFlow 对象描述用于检测结果的代码执行模式。 如果提供了代码流,code scanning 将在 GitHub 上扩展代码流以获取相关结果。 有关详细信息,请参阅 location 对象
relatedLocations[]与此结果相关的一组位置。 当相关位置嵌入在结果消息中时,Code scanning 将链接到这些位置。 有关详细信息,请参阅 location 对象

(属于location 对象)的父级。

编程构件中的位置,例如仓库中的文件或在构建过程中生成的文件。

名称必需说明
location.id用于在单个结果对象中区分此位置与所有其他位置的唯一标识符。 此属性的允许范围 0 到 2^63 - 1。
location.physicalLocation标识构件和区域。 有关详细信息,请参阅 physicalLocation
location.message.text与位置相关的消息。

(属于physicalLocation 对象)的父级。

名称必需说明
artifactLocation.uri表示构件位置的 URI,通常是存储库中或在构建期间生成的文件。 为了获得最佳结果,建议这是所分析的 GitHub 存储库根的相对路径。 例如,src/main.js。 有关项目 URI 的详细信息,请参阅“指定源文件的根”。
region.startLine区域中第一个字符的行号。
region.startColumn区域中第一个字符的列号。
region.endLine区域中最后一个字符的行号。
region.endColumn区域末尾后面的字符的列号。

(属于runAutomationDetails 对象)的父级。

runAutomationDetails 对象包含指定运行标识的信息。

名称必需说明
id标识分析和运行 ID 类别的字符串。 如果您想要为同一工具上传多个 SARIF 文件,但在不同语言或代码的不同部分执行,请使用。

使用 runAutomationDetails 对象是可选的。

id 字段可以包含分析类别和运行 ID。 我们不使用 id 字段的运行 ID 部分,但会存储它。

使用类别来区分同一工具或提交的多次分析,但是在不同语言或代码的不同部分进行。 使用运行 ID 来识别分析的特定运行,例如分析的运行日期。

id 解释为 category/run-id。 如果 id 不包含正斜杠 (/),则整个字符串为 run_id,而 category 为空。 否则,category 是字符串中直到最后一个正斜杠的所有内容,而 run_id 是之后的所有内容。

idcategoryrun_id
my-analysis/tool1/2022-01-02my-analysis/tool12022-01-02
my-analysis/tool1/my-analysis/tool1
my-analysis for tool1my-analysis for tool1
  • id 为“my-analysis/tool1/2021-02-01”的运行属于类别“my-analysis/tool1”。
  • id 为“my-analysis/tool1/”的运行属于类别“my-analysis/tool1”,但未与该类别中的其他运行区分。
  • id 为“my-analysis for tool1”的运行具有唯一的标识符,但无法推断属于任何类别。

有关 runAutomationDetails 对象和 id 字段的详细信息,请参阅 OASIS 文档中的 runAutomationDetails 对象

请注意,将忽略其余支持的字段。

SARIF 输出文件示例

这些示例 SARIF 输出文件显示支持的属性和示例值。

具有最少必需属性的示例

此 SARIF 输出文件的示例值显示了 code scanning 结果正常运行所需的最少属性。 如果移除任何属性、省略值或使用空字符串,此数据将无法正确显示或在 GitHub 上同步。

{
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
  "version": "2.1.0",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "Tool Name",
          "rules": [
            {
              "id": "R01"
                      ...
              "properties" : {
                 "id" : "java/unsafe-deserialization",
                 "kind" : "path-problem",
                 "name" : "...",
                 "problem.severity" : "error",
                 "security-severity" : "9.8",
               }
            }
          ]
        }
      },
      "results": [
        {
          "ruleId": "R01",
          "message": {
            "text": "Result text. This result does not have a rule associated."
          },
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "fileURI"
                },
                "region": {
                  "startLine": 2,
                  "startColumn": 7,
                  "endColumn": 10
                }
              }
            }
          ],
          "partialFingerprints": {
            "primaryLocationLineHash": "39fa2ee980eb94b0:1"
          }
        }
      ]
    }
  ]
}

显示所有支持的 SARIF 属性的示例

此 SARIF 输出文件的示例值显示了 code scanning 的所有受支持 SARIF 属性。

{
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
  "version": "2.1.0",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "Tool Name",
          "semanticVersion": "2.0.0",
          "rules": [
            {
              "id": "3f292041e51d22005ce48f39df3585d44ce1b0ad",
              "name": "js/unused-local-variable",
              "shortDescription": {
                "text": "Unused variable, import, function or class"
              },
              "fullDescription": {
                "text": "Unused variables, imports, functions or classes may be a symptom of a bug and should be examined carefully."
              },
              "defaultConfiguration": {
                "level": "note"
              },
              "properties": {
                "tags": [
                  "maintainability"
                ],
                "precision": "very-high"
              }
            },
            {
              "id": "d5b664aefd5ca4b21b52fdc1d744d7d6ab6886d0",
              "name": "js/inconsistent-use-of-new",
              "shortDescription": {
                "text": "Inconsistent use of 'new'"
              },
              "fullDescription": {
                "text": "If a function is intended to be a constructor, it should always be invoked with 'new'. Otherwise, it should always be invoked as a normal function, that is, without 'new'."
              },
              "properties": {
                "tags": [
                  "reliability",
                  "correctness",
                  "language-features"
                ],
                "precision": "very-high"
              }
            },
            {
              "id": "R01"
            }
          ]
        }
      },
      "automationDetails": {
        "id": "my-category/"
      },
      "results": [
        {
          "ruleId": "3f292041e51d22005ce48f39df3585d44ce1b0ad",
          "ruleIndex": 0,
          "message": {
            "text": "Unused variable foo."
          },
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "main.js",
                  "uriBaseId": "%SRCROOT%"
                },
                "region": {
                  "startLine": 2,
                  "startColumn": 7,
                  "endColumn": 10
                }
              }
            }
          ],
          "partialFingerprints": {
            "primaryLocationLineHash": "39fa2ee980eb94b0:1",
            "primaryLocationStartColumnFingerprint": "4"
          }
        },
        {
          "ruleId": "d5b664aefd5ca4b21b52fdc1d744d7d6ab6886d0",
          "ruleIndex": 1,
          "message": {
            "text": "Function resolvingPromise is sometimes invoked as a constructor (for example [here](1)), and sometimes as a normal function (for example [here](2))."
          },
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "src/promises.js",
                  "uriBaseId": "%SRCROOT%"
                },
                "region": {
                  "startLine": 2
                }
              }
            }
          ],
          "partialFingerprints": {
            "primaryLocationLineHash": "5061c3315a741b7d:1",
            "primaryLocationStartColumnFingerprint": "7"
          },
          "relatedLocations": [
            {
              "id": 1,
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "src/ParseObject.js",
                  "uriBaseId": "%SRCROOT%"
                },
                "region": {
                  "startLine": 2281,
                  "startColumn": 33,
                  "endColumn": 55
                }
              },
              "message": {
                "text": "here"
              }
            },
            {
              "id": 2,
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "src/LiveQueryClient.js",
                  "uriBaseId": "%SRCROOT%"
                },
                "region": {
                  "startLine": 166
                }
              },
              "message": {
                "text": "here"
              }
            }
          ]
        },
        {
          "ruleId": "R01",
          "message": {
            "text": "Specifying both [ruleIndex](1) and [ruleID](2) might lead to inconsistencies."
          },
          "level": "error",
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "full.sarif",
                  "uriBaseId": "%SRCROOT%"
                },
                "region": {
                  "startLine": 54,
                  "startColumn": 10,
                  "endLine": 55,
                  "endColumn": 25
                }
              }
            }
          ],
          "relatedLocations": [
            {
              "id": 1,
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "full.sarif"
                },
                "region": {
                  "startLine": 81,
                  "startColumn": 10,
                  "endColumn": 18
                }
              },
              "message": {
                "text": "here"
              }
            },
            {
              "id": 2,
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "full.sarif"
                },
                "region": {
                  "startLine": 82,
                  "startColumn": 10,
                  "endColumn": 21
                }
              },
              "message": {
                "text": "here"
              }
            }
          ],
          "codeFlows": [
            {
              "threadFlows": [
                {
                  "locations": [
                    {
                      "location": {
                        "physicalLocation": {
                          "region": {
                            "startLine": 11,
                            "endLine": 29,
                            "startColumn": 10,
                            "endColumn": 18
                          },
                          "artifactLocation": {
                            "uriBaseId": "%SRCROOT%",
                            "uri": "full.sarif"
                          }
                        },
                        "message": {
                          "text": "Rule has index 0"
                        }
                      }
                    },
                    {
                      "location": {
                        "physicalLocation": {
                          "region": {
                            "endColumn": 47,
                            "startColumn": 12,
                            "startLine": 12
                          },
                          "artifactLocation": {
                            "uriBaseId": "%SRCROOT%",
                            "uri": "full.sarif"
                          }
                        }
                      }
                    }
                  ]
                }
              ]
            }
          ],
          "partialFingerprints": {
            "primaryLocationLineHash": "ABC:2"
          }
        }
      ],
      "columnKind": "utf16CodeUnits"
    }
  ]
}