GitHub 对代码扫描的 SARIF 支持
要在 GitHub 上的仓库中显示第三方静态分析工具的结果,您需要将结果存储在 SARIF 文件中,以支持用于代码扫描的 SARIF 2.1.0 JSON 架构的特定子集。 如果使用默认 CodeQL 静态分析引擎,结果将自动显示于您在 GitHub 上的仓库中。
注意:代码扫描 目前处于测试阶段,可能会更改。 要申请访问测试版,请加入等待列表。
关于 SARIF 支持
SARIF(数据分析结果交换格式)是定义输出文件格式的 OASIS 标准。 SARIF 标准用于简化静态分析工具分享其结果的方式。 代码扫描 支持 SARIF 2.1.0 JSON 架构的子集。
要从第三方静态代码分析引擎上传 SARIF 文件,需确保上传的文件使用 SARIF 2.1.0 版本。 GitHub 将剖析 SARIF 文件,并在代码扫描过程中使用仓库中的结果显示警报。 更多信息请参阅“将 SARIF 文件上传到 GitHub”。 有关 SARIF 2.1.0 JSON 架构的更多信息,请参阅 sarif-schema-2.1.0.json
。
如果您使用的是 GitHub 的语义代码分析引擎 CodeQL,则代码分析输出将自动使用支持的 SARIF 2.1.0 子集。
GitHub 使用 SARIF 文件中的属性来显示警报。 例如,shortDescription
和 fullDescription
出现在 代码扫描 警报的顶部。 location
允许 GitHub 在代码文件中显示注释。 更多信息请参阅“管理来自 代码扫描 的警报”。
如果您是 SARIF 的新用户,想了解更多信息,请参阅 Microsoft 的SARIF 教程
库。
使用指纹防止重复警报
每当 GitHub 操作 工作流程运行新代码扫描时,都会处理每个运行的结果并将警报添加到仓库中。 为防止出现针对同一问题的重复警报,代码扫描 使用指纹匹配各个运行的结果,使它们只会出现在所选分支的最新运行中出现一次。
GitHub 使用 OASIS 标准中的 partialFingerprints
属性来检测两个结果在逻辑上是否相同。 更多信息请参阅 OASIS 文档中的 "partialFingerprints property" 条目。
如果您的 SARIF 文件不包含 partialFingerprints
,则当您使用 GitHub 操作 上传 SARIF 文件时,将计算 partialFingerprints
字段。 更多信息请参阅“将 SARIF 文件上传到 GitHub”。
支持的 SARIF 输出文件属性
如果您使用 CodeQL 以外的代码分析引擎,则可以查看受支持的 SARIF 属性来优化您的分析结果在 GitHub 中的显示方式。
任何有效的 SARIF 2.1.0 输出文件都可以上传,但 代码扫描 只使用以下受支持的属性。
sarifLog
对象
名称 | 描述 |
---|---|
$schema |
必需。2.1.0 版 SARIFJSON 架构的 URI。 例如,https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json 。 |
version |
必选。 代码扫描 只支持 SARIF 版本 2.1.0 。 |
runs[] |
必选。 SARIF 文件包含一个或多个运行的数组。 每个运行代表分析工具的一次运行。 有关 run 的更多信息,请参阅 run 对象。 |
run
对象
代码扫描 使用 run
对象按工具过滤结果并提供关于结果来源的信息。 run
对象包含 tool.driver
工具组件对象,该对象包含有关生成结果的工具的信息。 每个 run
只能获得一个分析工具的结果。
名称 | Description |
---|---|
tool.driver.name |
必需。分析工具的名称。 代码扫描 在 GitHub 上显示名称,以允许您按工具过滤结果。 |
tool.driver.version |
可选。分析工具的版本。 代码扫描 使用版本号来跟踪何时可能因工具版本的变更而不是所分析代码的变更而导致了结果变化。 如果 SARIF 文件包含 semanticVersion 字段,则 代码扫描 不使用 version 。 |
tool.driver.semanticVersion |
可选。以语义版本 2.0 格式指定的分析工具版本。 代码扫描 使用版本号来跟踪何时可能因工具版本的变更而不是所分析代码的变更而导致了结果变化。 如果 SARIF 文件包含 semanticVersion 字段,则 代码扫描 不使用 version 。 更多信息请参阅语义版本文档中的“语义版本 2.0.0”。 |
tool.driver.rules[] |
必需。用于表示规则的 reportingDescriptor 对象数组。 分析工具使用规则来查找所分析代码中的问题。 更多信息请参阅 reportingDescriptor 对象。 |
results[] |
必需。分析工具的结果。 代码扫描 在 GitHub 上显示结果。 更多信息请参阅 result 对象。 |
reportingDescriptor
对象
名称 | 描述 |
---|---|
id |
必需。规则的唯一标识符。 id 是从 SARIF 文件的其他部分引用的,可能被 代码扫描 用于在 GitHub 上显示 URL。 |
name |
可选。规则的名称。 代码扫描 显示名称,以允许按 GitHub 上的规则过滤结果。 |
shortDescription.text |
必需。规则的简要说明。 代码扫描 在 GitHub 上的相关结果旁边显示简短说明。 |
fullDescription.text |
必需。规则的说明。 代码扫描 在 GitHub 上的相关结果旁边显示完整说明。 最大字符数限制为 1000。 |
defaultConfiguration.level |
可选。规则的默认严重级别。 代码扫描 使用严重级别帮助您了解结果对于给定规则的严重程度。 此值可用 result 对象中的 level 属性进行覆盖。 更多信息请参阅 result 对象。 默认值:warning 。 |
help.text |
可选。使用文本格式的规则文档。 代码扫描 在相关结果旁边显示此帮助文档。 |
help.markdown |
推荐。使用 Markdown 格式的规则文档。 代码扫描 在相关结果旁边显示此帮助文档。 Markdown 帮助优先于 help.text 。 |
properties.tags[] |
可选。字符串数组。 代码扫描 使用 tags 允许您在 GitHub 上过滤结果。 例如,可以过滤带标记 security 的所有结果。 |
properties.precision |
推荐。一个字符串,表示此规则指示的结果为真的频率。 例如,如果已知某项规则的误报率较高,则其准确性应为 low 。 代码扫描 在 GitHub 上按准确性对结果进行排序,使具有最高 level 和最高 precision 的结果显示在最前面。 可以是以下值之一:very-high 、high 、medium 、low 或 unknown 。 |
result
对象
名称 | 描述 |
---|---|
ruleId |
可选。规则的唯一标识符 (reportingDescriptor.id )。 更多信息请参阅 reportingDescriptor 对象。 代码扫描 使用规则标识符在 GitHub 上按规则过滤结果。 |
ruleIndex |
可选。工具组件 rules 数组中相关规则(reportingDescriptor 对象)的索引。 更多信息请参阅 run 对象。 |
rule |
可选。用于定位此结果的规则 (reportingdescriptor) 的引用。 更多信息请参阅 reportingDescriptor 对象。 |
level |
可选。结果的严重程度。 此级别覆盖规则定义的默认严重程度。 代码扫描 使用级别在 GitHub 上按严重程度过滤结果。 |
message.text |
必选。描述结果的消息。 代码扫描 显示消息文本作为结果的标题。 当可见空间有限时,仅显示消息的第一句。 |
locations[] |
必选。检测到结果的位置集。 应只包含一个位置,除非只能通过在每个指定位置进行更改来更正问题。 注:代码扫描 至少需要一个位置才能显示结果。 代码扫描 将使用此属性来决定要用结果注释哪个文件。 仅使用此数组的第一个值。 所有其他值都被忽略。 |
partialFingerprints |
必选。用于跟踪结果的唯一标识的一组字符串。 代码扫描 使用 partialFingerprints 准确地识别在提交和分支之间相同的结果。 代码扫描 将尝试使用 partialFingerprints (如果存在)。 如果您使用 upload-action 上传第三方 SARIF 文件,该操作将为您创建 partialFingerprints (如果它们未包含在 SARIF 文件中)。 更多信息请参阅“使用指纹防止重复警报”。 注:代码扫描 只使用 primaryLocationLineHash 。 |
codeFlows[].threadFlows[].locations[] |
可选。threadFlow 对象的 location 对象数组,它描述程序通过执行线程的进度。 codeFlow 对象描述用于检测结果的代码执行模式。 如果提供了代码流,代码扫描 将在 GitHub 上扩展代码流以获取相关结果。 更多信息请参阅 location 对象。 |
relatedLocations[] |
与此结果相关的一组位置。 当相关位置嵌入在结果消息中时,代码扫描 将链接到这些位置。 更多信息请参阅 location 对象。 |
suppressions[].state |
可选。当 state 设置为 accepted 时,代码扫描 将在 GitHub 上将结果的状态更新为 Closed 。 |
location
对象
编程构件中的位置,例如仓库中的文件或在构建过程中生成的文件。
名称 | 描述 |
---|---|
location.id |
可选。用于在单个结果对象中区分此位置与所有其他位置的唯一标识符。 |
location.physicalLocation |
必选。标识构件和区域。 更多信息请参阅 physicalLocation 。 |
location.message.text |
可选。与位置相关的消息。 |
physicalLocation
对象
名称 | 描述 |
---|---|
artifactLocation.uri |
必选。表示构件位置的 URI,通常是仓库中或在构建期间生成的文件。 如果 URI 是相对的,它应相对于正在分析的 GitHub 仓库的根目录。 例如,main.js 或 src/script.js 相对于仓库的根目录。 如果 URI 是绝对的,则 代码扫描 可使用 URI 检出构件并匹配仓库中的文件。 例如,https://github.com/github/example/blob/00/src/promiseUtils.js 。 |
region.startLine |
必选。区域中第一个字符的行号。 |
region.startColumn |
必选。区域中第一个字符的列编号。 |
region.endLine |
必选。区域中最后一个字符的行号。 |
region.endColumn |
必选。区域结束后字符的列编号。 |
SARIF 输出文件示例
这些示例 SARIF 输出文件显示支持的属性和示例值。
具有最少必需属性的示例
此 SARIF 输出文件的示例值显示了 代码扫描 结果正常运行所需的最少属性。 如果您删除任何属性或不包含值,此数据将无法正确显示或在 GitHub 上同步。
{
"$schema" : "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version" : "2.1.0",
"runs" :
[
{
"tool" : {
"driver" : {
"name" : "Tool Name"
}
},
"results" : [ {
"message" : {
"text" : "Result text. This result does not have a rule associated."
},
"locations" : [ {
"physicalLocation" : {
"artifactLocation" : {
"uri" : "src/build.cmd"
},
"region" : {
"startLine" : 2,
"startColumn" : 7,
"endColumn" : 10
}
}
} ],
"partialFingerprints" : {
"primaryLocationLineHash" : "39fa2ee980eb94b0:1"
}
}]
}
]
}
显示所有支持的 SARIF 属性的示例
此 SARIF 输出文件的示例值显示了 代码扫描 的所有受支持 SARIF 属性。
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Tool Name",
"semanticVersion": "2.0.0",
"rules": [
{
"id": "js/unused-local-variable",
"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": "js/inconsistent-use-of-new",
"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'."
},
"defaultConfiguration": null,
"properties": {
"tags": [
"reliability",
"correctness",
"language-features"
],
"precision": "very-high"
}
}
]
}
},
"results": [
{
"ruleId": "js/unused-local-variable",
"ruleIndex": 0,
"message": {
"text": "Unused variable foo."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "main.js",
"uriBaseId": "%SRCROOT%",
"index": 0
},
"region": {
"startLine": 2,
"startColumn": 7,
"endColumn": 10
}
}
}
],
"partialFingerprints": {
"primaryLocationLineHash": "39fa2ee980eb94b0:1",
"primaryLocationStartColumnFingerprint": "4"
}
},
{
"ruleId": "js/inconsistent-use-of-new",
"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": "https://github.com/github/example/blob/0000000000000000000000000000000000000000/src/promiseUtils.js",
"index": 1
},
"region": {
"startLine": 2
}
}
}
],
"partialFingerprints": {
"primaryLocationLineHash": "5061c3315a741b7d:1",
"primaryLocationStartColumnFingerprint": "7"
},
"relatedLocations": [
{
"id": 1,
"physicalLocation": {
"artifactLocation": {
"uri": "src/ParseObject.js",
"uriBaseId": "%SRCROOT%",
"index": 3
},
"region": {
"startLine": 2281,
"startColumn": 33,
"endColumn": 55
}
},
"message": {
"text": "here"
}
},
{
"id": 2,
"physicalLocation": {
"artifactLocation": {
"uri": "src/LiveQueryClient.js",
"uriBaseId": "%SRCROOT%",
"index": 2
},
"region": {
"startLine": 166
}
},
"message": {
"text": "here"
}
}
]
},
{
"message": {
"text": "Specifying both [ruleIndex](1) and [ruleID](2) might lead to inconsistencies."
},
"level": "error",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "full.sarif",
"uriBaseId": "%SRCROOT%",
"index": 0
},
"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"
}
}
],
"newlineSequences": [
"\r\n",
"\n",
"",
"
"
],
"columnKind": "utf16CodeUnits"
}
]
}