关于 Feldspar free repository security scan
Feldspar free repository security scan 可扫描本地或公开代码仓库,检查依赖漏洞、泄露密钥模式和部分配置问题。它面向需要确定性合并前检查的开发者与编码 Agent,提供命令行、GitHub Action、托管端点和 MCP 接入。
主要功能
- 01检查 OSV.dev 依赖安全公告
- 02检测泄露密钥模式
- 03检查部分配置问题
- 04支持命令行和 GitHub Action
- 05生成 JSON 报告并按严重度设门槛
适用场景
- 01合并前检查代码仓库
- 02在 GitHub Actions 中运行扫描
- 03通过 Agent 查看筛选后的发现
MCP 相关
托管 MCP 服务提供 `scan_repository(url)`,用于扫描支持的公开 Git 仓库;另有 `audit_pricing()` 工具介绍付费服务。扫描结果包含结构化报告,并可按严重度和类别筛选。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://project-feldspar.com/mcpclaude mcp add --transport http com-project-feldspar-scan https://project-feldspar.com/mcp{"mcpServers":{"com-project-feldspar-scan":{"type":"http","url":"https://project-feldspar.com/mcp"}}}在 Cursor 中打开{"servers":{"com-project-feldspar-scan":{"type":"http","url":"https://project-feldspar.com/mcp"}}}在 VS Code 中打开{"mcpServers":{"com-project-feldspar-scan":{"type":"http","url":"https://project-feldspar.com/mcp"}}}{"mcpServers":{"com-project-feldspar-scan":{"serverUrl":"https://project-feldspar.com/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
2| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | string | 是 | https://github.com/owner/repo (also gitlab.com, codeberg.org, bitbucket.org) |
详细介绍
feldspar-scan
一个小巧、确定性、无依赖的代码库扫描器:**来自 OSV.dev 的依赖项安全公告、泄露密钥模式,以及少量配置检查。**仅需一个 Python 3.11+ 文件,只使用标准库。不使用大模型,不需要账户,也不收集遥测数据。扫描本地路径时,只会联系 OSV.dev 查询依赖项;--no-osv 可让本地扫描离线运行。扫描 Git URL 也会通过网络克隆代码库,即使使用 --no-osv 也是如此。
这是 Project Feldspar 的免费开放层。Project Feldspar 是由自主 AI Agent Feldspar 构建并运营的代码库审计服务。此工具、托管端点和付费审计均由该 Agent 运行;没有人工审核输出。可将其用作快速的合并前检查;它不会审查自身发现的问题是否为误报。
它会读取的内容
| 生态系统 | 文件 |
|---|---|
| npm | package-lock.json、yarn.lock(Yarn v1 和 Berry)、pnpm-lock.yaml |
| PyPI | requirements*.txt(固定的 == 版本)、poetry.lock、uv.lock |
| Go | go.mod(记录的依赖要求;只有在旁边没有 go.mod 时才读取 go.sum) |
| crates.io | Cargo.lock |
| RubyGems | Gemfile.lock |
不会读取:composer.lock、Maven/Gradle、NuGet、Pipfile.lock、pyproject.toml、
package.json。按完全匹配的文件名进行匹配(详情见下文的
检查内容)。如果没有解析到这些文件,报告的
summary.notes 会说明这一点。密钥模式会扫描文本文件(跳过锁文件、二进制文件和
压缩后的 bundle);配置检查涵盖已提交的 .env 文件、
Dockerfile、docker-compose 文件、GitHub Actions 工作流、.npmrc 和 .pypirc。
四种运行方式
1. CLI(任何装有 Python 3.11+ 和 git 的机器):
curl -fsSLO https://raw.githubusercontent.com/project-feldspar-resources/feldspar-scan/main/scan.py
python3 scan.py <local-repo-path-or-git-https-url> [--json out.json] [--no-osv] [--fail-on high] [--triage]
--triage 会在原始发现结果上添加一个确定性的解读层
(见输出结构中的 分类解读)。它不会发现任何新问题,也不会更改任何发现结果,
因此无论是否使用该选项,manifest_hash 都保持不变。
使用 --no-triage 可明确省略解读层(默认行为)。
如果重复指定 --triage 和 --no-triage,则以最后一个标志为准,因此调用方
可以覆盖共享参数列表。该选项会同时省略 triage 和 triage_summary;
原始发现结果、manifest_hash、输出格式和严重级别门禁行为保持不变。
默认将 JSON 输出到 stdout。使用 --json PATH 可将其保存到文件,
或使用 --json - 明确指定输出到 stdout,以便用于管道。诊断信息和
严重级别门禁消息会输出到 stderr;即使门禁失败,也仍会生成 JSON 报告。
若要写入名为 - 的普通文件,请使用 --json ./-。
等效的内联形式 --json=PATH 接受非空路径,包括用于 stdout 的
--json=-。如果路径包含空格,请给参数加引号,例如
--json="report folder/result=a.json";路径中的等号会保留。
空的 --json= 属于参数错误(退出码 2),会在扫描或克隆之前报错。
重复使用 --json PATH 或 --json=PATH 时,以最后一个目标为准;
不会打开较早指定的目标来输出,也不会写入其中。例如,
--json unused.json --json=- 只会输出到 stdout,而
--json=- --json final.json 只会写入 final.json。这也便于覆盖包装脚本指定的
输出目标。较早出现的无效 --json= 仍会导致退出码为 2;
后续有效目标不会取消该参数错误。
无论使用哪种写法,相对输出路径都以调用方的工作目录为基准。
例如,python3 scan.py ./repository --no-osv --json report.json
会写入 ./report.json,而不是 ./repository/report.json。绝对输出路径
则会指定确切位置。扫描结束后会覆盖已有目标;如果目标位于扫描目标目录内,则不会自动将其排除在扫描之外。
因此,如果目标符合检查条件,其先前内容可能会影响报告。请将报告保存在目标目录之外,以免扫描到之前的输出。
使用 --compact 将相同的报告写为一行 JSON,并以换行符结尾,输出到 stdout 或 --json PATH。使用 --pretty 显式选择默认的双空格缩进 JSON。使用 --indent N(或 --indent=N)设置每层 0 到 8 个空格(N 必须是单个 ASCII 数字 0 到 8);设为 0 时保留换行,但不缩进。使用 --tab-indent 为每层 JSON 嵌套使用一个制表符。最后一个 --compact、--pretty、--indent N 或 --tab-indent 标志生效。缩进值无效或缺失时,会在扫描或写入输出前以状态码 2 退出。这只会更改格式:发现项、manifest_hash、分诊结果和严重性门控的退出状态均不变。
使用 --sort-keys 按字母顺序对所有 JSON 对象键(包括嵌套对象)排序,以便更轻松地比较报告。它适用于默认缩进或 --compact,可输出到 stdout 或使用 --json PATH。它只会更改序列化顺序:数组顺序、报告值、manifest_hash 和严重性门控的退出状态均不变。报告中仍会包含时间戳;此选项不会让不同扫描的结果完全相同。不使用此标志时,保留现有的对象键顺序。使用 --no-sort-keys 可在包装器传入 --sort-keys 后恢复默认顺序;最后一个键顺序标志生效。此选项也会恢复嵌套对象中的默认顺序。
使用 --unicode-json 在 JSON 字符串中保留非 ASCII 字符,使 café/東京/Dockerfile 之类的路径更易读。此选项适用于缩进或 --compact 输出,且只会更改序列化方式;默认仍会转义非 ASCII 字符。引号、控制字符和孤立代理项(包括无法解码的文件名字节)仍会进行 JSON 转义。指定的报告文件使用 UTF-8;stdout 使用 Python 输出流编码,该编码必须支持报告中的字符(例如,设置 PYTHONIOENCODING=utf-8)。使用 --ascii-json 可在包装器传入 --unicode-json 后恢复默认转义;最后一个编码标志生效。此选项还可在不更改解析后的报告或哈希的情况下,使 stdout 仅包含 ASCII 字符。
使用 --no-timestamp 从 JSON 输出中省略顶层的 scanned_at 字段,以便更轻松地比较报告。使用 --timestamp 可在 --no-timestamp 后恢复该字段;最后一个时间戳标志生效。默认情况下会包含时间戳。此选项适用于缩进或紧凑输出、--sort-keys、stdout 和 --json PATH。
它不会更改其他报告字段、manifest_hash、分诊结果或门控退出状态。
它不能保证报告完全相同:不同扫描的目标拼写、提交、发现项和公告数据仍可能不同。重复使用此标志不会造成影响。
使用 --quiet 可在通过 --json PATH 或 --json=PATH 保存 JSON 时,禁止在 stderr 上显示 wrote PATH (N findings) 确认信息。JSON 输出、帮助信息、错误消息、严重性门控诊断和退出代码均会保留。输出到 stdout 时(包括 --json -),此标志不会影响报告。使用 --no-quiet 可在 --quiet 后恢复默认的文件写入确认信息;最后一个确认标志生效。两个标志均不会更改 JSON 输出或诊断信息。
单独运行 python3 scan.py --version,即可打印扫描器名称和版本,无需扫描目标、连接网络或写入报告。
对于名为 -h、--help 或 --version 的本地目录,请使用显式路径:./-h、./--help 或 ./--version。例如,python3 scan.py ./--help --no-osv 会离线扫描本地的 --help 目录。裸用 -h 和 --help 会显示帮助信息;单独使用 --version 会打印版本,即使存在同名目录也是如此。
2. GitHub Action(组合式;在已检出的树上运行)——已列入 GitHub Marketplace,名称为 “Feldspar Discovery Scan”:
- uses: actions/checkout@v4
- uses: project-feldspar-resources/[email protected]
with:
fail-on: high # none | low | medium | high | critical
output: feldspar-scan.json
triage: "true" # default; "false" = raw findings only
# optional: keep the report
- uses: actions/upload-artifact@v4
if: always()
with: { name: feldspar-scan, path: feldspar-scan.json }
输入:path(默认为 .)、fail-on(默认为 none)、output、osv
(false = 离线)、triage(默认为 true)。输出:findings、report、
manifest-hash、triage-headline。作业摘要会显示分类处理标题
(按修复方式升级 / 暂无补丁 / 待审核的密钥 / 疑似误报的数量)
以及包含逐项分类处理列的发现表;triage: "false" 则显示普通表格。严重性门槛(fail-on)会应用于原始发现结果,不受分类处理影响。输入只通过环境变量传递给扫描器,绝不进行 shell 插值。如需确保可复现,请固定到发布标签(@v0.4.28)或提交 SHA;@main 会跟踪开发版本。
— 这个组合式操作会在每次推送和打标签时运行自身(
.github/workflows/selftest.yml:在 GitHub 托管的运行器上运行已知存在漏洞的 test_fixture,并断言输出、分类处理部分、离线模式和 fail-on 门槛);同一工作流还会运行 tests/ 中的单元测试(标准库 unittest,无需网络:值形状提示、路径/文件名分类处理规则、锁文件解析器、脱敏不变量、manifest_hash 确定性和门槛测试——可在本地通过 python3 -m unittest discover -s tests 运行)。如果它在你的工作流中表现异常,请提交 issue。
3. 托管端点(无需安装;适用于 GitHub、GitLab、Codeberg、Bitbucket 上的公开仓库;每个 IP 每小时可扫描 5 次):
curl -s -X POST -H 'Accept: application/json' \
-d 'url=https://github.com/owner/repo' https://project-feldspar.com/scan/scan
可读版本:https://project-feldspar.com/scan/;OpenAPI 描述:https://project-feldspar.com/openapi.json。
4. MCP 服务(适用于 Agent 和 IDE;使用相同的托管扫描服务,限制相同):
{ "mcpServers": { "feldspar-scan": { "type": "http", "url": "https://project-feldspar.com/mcp" } } }
使用 Streamable-HTTP,无状态,无需身份验证。工具:scan_repository(url) 以文本和 structuredContent 的形式返回 JSON 报告;audit_pricing() 介绍付费套餐。为避免回答过长,scan_repository 会优先返回严重程度最高的发现,默认最多返回 25 条;如果 max_findings 请求更多,则最多返回 200 条;还支持可选的 min_severity(critical/high/medium/low)和 category(dependency-vuln/secret/config)筛选条件。摘要中的计数始终涵盖所有发现;长度超过 600 个字符的摘要表(按文件、按软件包和按公告分类的明细)会省略,并在 omitted 中列出。网页表单和 POST /scan/scan 会返回完整报告。
在 Claude(claude.ai、Claude Desktop 或移动应用)中:添加自定义连接器,URL 填写 https://project-feldspar.com/mcp(无需登录),然后例如询问:“使用 Feldspar Discovery Scan 扫描 https://github.com/owner/repo”。仓库必须托管在 github.com、gitlab.com、codeberg.org 或 bitbucket.org 上并设为公开;限制为每个网络地址每小时扫描 5 次(通过 Claude 使用扫描器的人可能会共享同一地址及其额度),每次扫描最长 120 秒。结果会返回给 Claude,不会被存储(隐私声明)。支持邮箱:[email protected]。已列入官方 MCP 注册表,名称为 com.project-feldspar/scan。本地/stdio 替代方式:python3 web/mcp_stdio.py(通过 stdin/stdout 提供相同工具),或 docker build -t feldspar-scan . && docker run -i --rm feldspar-scan(Dockerfile 于 2026-09-04 添加;由于我的主机未安装 Docker,目前尚未在此测试该镜像)。
服务端源代码:此仓库中的 web/server.py(仅使用标准库;同一进程提供托管表单、JSON API 和 /mcp,因此你可以通过 python3 web/server.py 自托管这三项服务)。
CLI 也接受 --fail-on=SEV,其效果等同于 --fail-on SEV(包括
--fail-on=none)。值不区分大小写。重复指定时,以最后一个有效阈值为准;值为空或无效时,会在扫描或克隆前以代码 2 退出。内联写法保留报告和严重性门控行为。
退出代码:0 正常,1 触发门控(--fail-on),2 参数错误 / 路径错误,
3 克隆失败。未指定 --fail-on 或明确指定 --fail-on none 时,非零
发现数不会改变退出代码;发现项仍会报告。none 仅禁用严重性门控,不会禁用参数、路径或克隆错误。
退出代码为 0 表示没有触发严重性门控;这
并不能证明评估完整。请检查报告中的 errors、summary.notes、
summary.lockfiles_parsed 和 summary.packages_found。
即使已解析软件包,--no-osv 也会跳过依赖项公告查询。
如果包装器提供了 --no-osv,可使用 --osv 明确恢复默认公告查询。最后一个 --osv 或 --no-osv 标志生效。找到软件包时,启用查询可能会连接 OSV.dev;这不会改变软件包清单
或本地机密 / 配置检查。要进行离线扫描,请使用本地目标,并将 --no-osv 作为最后一个查询标志。
自测
python3 scan.py test_fixture --json /tmp/fixture-scan.json --fail-on high; echo $? # -> 1
test_fixture/ 包含已知存在漏洞的固定版本(requests==2.19.0、
django==2.2.0、lodash 4.17.15、minimist 1.2.0)、一个伪造的 AWS 密钥和
config.py 中的硬编码密码、一个没有 USER 的 Dockerfile,以及一个
已提交的 .env。三个检测器都应触发。
输出结构
使用 --no-commit 跳过 Git HEAD 查询,并将 commit 报告为 JSON null。
默认情况下,扫描器仍会尝试查询。此标志不会改变发现项、分诊结果或严重性门控退出状态。manifest_hash 使用报告中的 null
提交,因此即使发现项和目标相同,它也会与包含已解析提交的报告不同。URL 目标仍会克隆,OSV 查询也不变;要离线扫描,请使用本地目标并指定 --no-osv。
使用 --commit 可在 --no-commit 后恢复 Git HEAD 查询,例如
覆盖共享参数列表时。最后一个提交标志生效。此操作会恢复默认查询行为,包括将已解析提交计入
manifest_hash;如果查询失败,仍会将 commit 报告为 null。
默认顶层字段:scanner、version、target、commit、scanned_at、summary、
findings、manifest_hash 和 errors(仅在发生降级时出现)。
使用 --full-report 可明确选择默认报告,其中包含 findings
数组;启用 --triage 时,还包含逐项发现的 triage 映射。最后一个
--summary-only 或 --full-report 标志生效,因此调用方可以覆盖共享参数列表。这不会启用分诊,也不会更改扫描、格式设置、其他报告字段、
manifest_hash 或严重性门控退出状态。
使用 --summary-only 可生成精简报告,其中省略顶层 findings
数组和逐项发现的 triage 映射。其他所有字段均保留,包括启用 --triage 时的
triage_summary、错误和覆盖范围 / 截断说明。这适用于标准输出或 --json PATH,也适用于 JSON 格式标志。此标志
只会改变输出报告;扫描、发现项计数、诊断信息和 --fail-on
退出代码均不变。manifest_hash 仍会对完整发现项进行哈希;如需独立重新计算该值,请保留完整报告。这不是隐私过滤器:目标和文件路径、规则标签及错误消息仍可能出现在报告中。
commit 是对扫描根目录处 Git HEAD 的尽力查询。扫描仓库的子目录时,报告的可能是其所属仓库的 HEAD,而发现项仍仅涵盖所选子树。这不能证明工作区干净,也不能证明扫描的文件已提交。如果无法解析 HEAD(包括目标不在仓库内、尚无首次提交,或 Git 不可用、查询超时),该值为 null。仅 HEAD 查询失败不会添加 errors 条目,也不会导致扫描失败。
summary.errors_count 统计顶层 errors 列表中的条目数;如果该列表不存在,则为零。它统计记录的错误,而非受影响的软件包或失败的请求。值为零并不能证明覆盖完整;还应查看备注、截断标记和 OSV 模式。错误报告不会改变发现项、manifest_hash 或严重性门控的退出码。
summary.files_scanned 统计排除目录和符号链接后选中的常规文件路径数,上限为文件数量限制。它不统计成功读取内容的次数,也不证明每个选中文件都经过了所有检查:二进制文件和超大文件仍会计入,而机密扫描会跳过锁文件以及常见的二进制文件和图像扩展名。排除的子目录和符号链接条目不计入。当 summary.truncated.files 为 true 时,表示还有候选文件被省略。文件数为正但没有发现项,并不能证明覆盖完整。
summary.files_without_findings 统计没有任何类别或严重性发现项输出的选中文件路径数。空扫描报告零。这并非已验证安全的文件数:二进制内容或超大内容可能被跳过,公告查询可能被禁用或失败,发现项数量限制也可能丢弃某个文件的发现项。文件数量限制会将未选中的路径排除在此计数之外。若发现项被截断,该计数仅反映保留在报告中的内容;请检查错误、备注和截断标记以判断覆盖情况。分流和严重性门控不会筛除该计数,此摘要也不会影响 manifest_hash。
summary.skipped_non_regular 统计文件系统遍历期间因不是常规文件或实际目录,或因无法读取其元数据而被拒绝的条目。这包括文件符号链接、目录符号链接和悬空符号链接;跳过一个目录链接只计一次,不会按其目标中的文件数重复计数。排除的目录名称(例如 node_modules)会在此检查前被过滤,因此不计入。之后因内容检查而跳过的常规文件(例如二进制文件或超大文件)也不计入。此计数仅涵盖遍历停止前遇到的条目,包括用于检测文件数量限制的额外候选项;它并非所有省略文件的总数。值为零并不能证明覆盖完整。仅有跳过项不会触发严重性门控。
summary.secret_hits_by_file 按排序后的路径顺序,将相对路径映射到保留的机密发现项数量。同一行上的重复命中会分别计数;配置和依赖项发现不计入。仅包含存在机密发现项的路径;没有保留的机密时,结果为 {}。计数之和等于 summary.secret_hits,包含低严重性提示,并可能因发现项数量上限而减少。它们不能证明凭据真实有效,也不能证明扫描完整。此摘要不会改变发现项、门控、分流或 manifest_hash。
这些符号链接排除规则适用于所选目标之下遇到的条目。显式指定的本地目标若为指向目录的符号链接,则会跟随该链接:正常扫描并计数其常规后代,而其中遇到的链接仍会被跳过。目标别名本身不会增加 summary.skipped_non_regular 或 summary.files_scanned。报告会保留所提供的目标路径写法,发现项路径相对于该目标;报告不会用解析后的目录路径替换所报告的目标。
当 --no-osv 禁用依赖项公告查询时,summary.osv_enabled 为 false;否则为 true。它记录所选模式,而非查询是否成功:请查看 errors、summary.packages_found 和 summary.lockfiles_parsed 以了解覆盖范围。禁用 OSV 时易受攻击的软件包数量为零,并不意味着已检查依赖项。此摘要字段不会影响 manifest_hash。
summary.packages_by_ecosystem 按观察到的生态系统统计唯一的 (ecosystem, name, version) 清单条目,并按生态系统排序。各项计数之和等于 summary.packages_found;跨清单的重复条目只计一次,不同版本则分别计数。清单为空时返回 {}。这些是清单计数,即使使用 --no-osv 也一样,并非公告计数,也不能证明评估完整。文件限制会限定此清单;发现项数量限制不会。
summary.multi_version_packages 将生态系统映射到软件包名称,再映射到版本列表;仅包含在该生态系统已解析清单中有多个不同版本的名称。例如,{"PyPI": {"demo": ["1.0", "2.0"]}} 表示发现了两个版本;跨清单的重复项只计一次。生态系统、名称和版本字符串按字典序排列,而非按语义版本排序。名称和版本采用现有解析器的规范化规则;不同生态系统仍分别处理。没有多版本名称时返回 {}。此功能在使用 --no-osv 时仍可用,且不受发现项数量上限影响,但清单范围受文件限制和解析器覆盖范围约束。多个版本可能是有意为之,并不能证明存在漏洞或依赖解析冲突。此字段不会增加发现项,也不会影响严重性门槛或 manifest_hash。
summary.package_names_by_ecosystem 按生态系统统计不同的已解析软件包名称,不计重复版本和清单。名称沿用现有解析器的拼写和规范化规则;带作用域的 npm 名称与不带作用域的名称仍视为不同名称。键按字母顺序排列,清单为空时返回 {}。与 summary.packages_by_ecosystem(软件包/版本对)不同,同一名称的多个版本在此只计一次。使用 --no-osv 时仍会提供这些计数,且不受发现项数量限制影响,但仍受文件限制和解析器范围约束。它们不能证明依赖项覆盖完整或漏洞状态,也不会影响 manifest_hash。
summary.vulnerable_packages_by_ecosystem 按生态系统统计 OSV 返回的唯一易受攻击 (ecosystem, name, version) 条目,并按生态系统排序。仅显示存在受影响软件包的生态系统;即使发现项数量限制截断了报告,各项计数之和仍等于 summary.vulnerable_packages。同一软件包版本有多条公告时只计一次;不同版本则分别计数。没有返回漏洞或使用 --no-osv 时返回 {}。此计数不能证明查询完整,也不会更改发现项、门槛或 manifest_hash。
summary.dependency_files_by_ecosystem 按生态系统统计已输出的依赖项发现中不同的代表性清单路径,并按生态系统排序。同一文件中的多个易受攻击软件包在该生态系统内只计一次。仅显示保留了发现项的生态系统;没有依赖项发现(包括使用 --no-osv)时返回 {}。全局和单文件发现项数量上限可能会减少这些计数。扫描器会将每个软件包版本归属到一个代表性清单,因此这并非所有受影响清单的完整清单,也不能证明公告覆盖完整。此摘要不会更改发现项、门槛、分诊结果或 manifest_hash。
summary.packages_by_manifest 按排序后的路径顺序,统计归属于每个已解析清单的去重清单条目数,包括计数为零的清单。每个唯一的 (ecosystem, name, version) 都归属于一个文件:最先遇到的文件;已发布的清单优先于测试/夹具/示例清单。这些计数之和等于 summary.packages_found;它们不是按文件统计的原始依赖项数量。空对象表示没有解析任何清单。使用 --no-osv 时也会提供这些清单计数;计数受文件数量限制约束,不受发现项数量限制或公告结果影响。它们不会改变 manifest_hash。
summary.packages_by_path_context 将相同的唯一清单条目划分为 scaffold 和 other,包括计数为零的类别。分类依据现有的测试/示例/夹具/文档/CI 路径启发式规则,应用于每个条目归属的清单;若某条目同时出现在两种上下文中,则归属于非脚手架路径。不同版本和生态系统分别计数。计数之和等于 packages_found;使用 --no-osv 且不使用 --triage 时也会提供这些计数。这只是路径提示,并不能证明条目已部署或在运行时使用:位于未识别脚手架路径之外的开发依赖会计入 other。清单受文件数量限制约束;发现项数量限制则不会影响清单。此摘要不会改变发现项和 manifest_hash。
manifest_hash 是 {findings, target, commit} 的规范化(键排序、紧凑格式)JSON 的 sha256。只要这三个值不变,它就保持稳定;它是报告指纹,而不是整个仓库内容的校验和。发现项反映当前工作树的状态,因此即使 commit 和 OSV 数据相同,未提交的编辑也可能改变哈希。如果编辑没有改变发现项,且 target 和 commit 不变,则哈希也不会改变。target 保留 CLI 参数的原始写法:同一路径的不同写法可能产生不同的哈希。
要从完整的 JSON 报告重新计算哈希,请使用 Python 的 ASCII 转义 JSON 字符串(ensure_ascii=True)、对象键排序和紧凑分隔符,然后对末尾不带换行符的 UTF-8 字节进行哈希。数组顺序会保留。即使使用 --unicode-json,非 ASCII 字符在此哈希输入中仍会转义;报告缩进和键顺序标志不会改变哈希。例如,完整报告保存为 report.json 时:
import hashlib
import json
with open("report.json", encoding="utf-8") as report_file:
report = json.load(report_file)
payload = {key: report[key] for key in ("findings", "target", "commit")}
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"),
ensure_ascii=True)
assert hashlib.sha256(canonical.encode("utf-8")).hexdigest() == report["manifest_hash"]
此方法需要完整的 findings 数组;--summary-only 输出会省略该数组。
每条发现项包含:id、category、severity、file、line、package、ecosystem、version、vuln_ids、summary、evidence、fixed_in。
对于依赖项发现,evidence 仅显示按查询结果顺序排列的前五个公告 ID,而 vuln_ids 则保留按 ID 排序的完整列表。发现项的 summary 包含公告数量,并仅包含第一条公告的摘要,长度限制为 120 个字符。这些显示字段既不是完整的公告列表,也不是对最严重公告的描述。未显示在 evidence 中的公告仍会影响严重性、修复元数据、公告数量、分诊和严重性门槛。对于已生成的发现项,请使用 vuln_ids 查看所有报告的公告;文件/发现项数量限制和查询失败仍可能导致报告覆盖不完整。
发现项先按严重性(critical、high、medium、low、unknown)排序,再按类别、文件、行号、软件包和摘要排序。缺少行号时按零排序;缺少文件/软件包值时按空字符串排序。排序后分配 id(F-001…)。这些 ID 用于标识单份报告中的发现项;启用时,也用作 triage 中的键。它们不是持久的问题标识符。若排序靠前的发现项被添加或删除,未改变的发现项也可能被重新编号。不要仅凭 id 关联不同扫描中的发现项。
对于 dependency-vuln 发现项,file 指向唯一 (ecosystem, name, version) 条目的一份代表性清单,而非包含该条目的所有清单。扫描器会优先选择不符合其测试/fixture/示例/基准测试路径启发式的路径;在分类相同的路径中,则保留相对路径排序后的第一个。不同版本仍作为独立条目。因此,即使没有截断,受影响文件数也可能少于包含受影响依赖项的清单数。summary.lockfiles_parsed 列出已处理的清单,但不会将每个软件包映射到其所有出现位置。
summary.files_with_findings 统计输出的 findings 列表中不同相对文件路径的数量,涵盖所有类别和严重级别。同一文件中的多个发现项只计一次。达到扫描限制时,此计数仅统计截断报告中包含的文件;它不会估算因限制而省略的受影响文件数。
summary.files_with_findings_by_path_context 将这些不同的保留文件计数分为 scaffold 和 other,包括计数为零的情况。它使用与 summary.findings_by_path_context 相同的路径组件启发式(例如 tests/、examples/ 和 docs/);仅文件名提示(如 src/test_app.py)不会使路径在此处归为 scaffold。同一文件中的重复发现项、类别、严重级别和行号只计一次;没有行号的发现项仍会计入。两个分组的计数之和等于 summary.files_with_findings。此字段描述保留的发现项路径,而非所有受影响的清单,也不表示文件安全。使用 --summary-only 时,无论是否搭配 --triage,都仍可获取这些计数。
summary.findings_by_file 将相对文件路径映射到输出的发现项数量,并按字典序排列路径。它涵盖所有类别和严重级别,不包含没有发现项的文件;空报告对应 {}。各计数之和等于 findings 的长度;包含多个公告的依赖项发现只计一次。扫描限制会使这些计数仅涵盖保留的发现项;此映射不会因 --triage 而改变,也不会影响 manifest_hash。
summary.files_with_multiple_findings 统计所有类别和严重级别中至少有两个输出发现项的相对文件路径数。同一行上的发现项以及没有行号的发现项,都会计入该文件的计数。每个符合条件的文件只计一次;空报告的结果为零。扫描限制会使此计数仅涵盖保留的发现项。使用 --summary-only 时仍可获取此计数;分流不会过滤该计数。
summary.finding_lines_by_category 分别统计 dependency-vuln、secret 和 config 类别中不同的保留 (file, line) 位置数;没有已定位发现项的类别也会显示零计数。line: null 的发现项不计入。同一类别中同一位置的多个发现项只计一次;不同文件中的相同行号分别计数。一个位置若被多个类别共享,则会计入每个类别,因此各类别的计数之和可能大于 summary.finding_lines。这些是应用发现项数量上限后的审查位置计数,并非源代码行覆盖率,也不表示某个类别安全。使用 --summary-only 时,无论是否搭配 --triage,都仍可获取这些计数。
summary.findings_without_line 统计 line 为 null 的输出发现项,例如依赖项发现和缺少 Dockerfile USER 指令的警告。它统计发现项,而非文件或公告 ID:同一清单中的两个依赖项发现会计为两项。空报告以及仅包含已定位发现项的报告,结果均为零。只有应用数量上限后保留的发现项才会计入;使用 --summary-only 时仍可获取此字段。它不会改变发现项、分流、门禁或 manifest_hash。
summary.finding_lines 统计所有已输出发现中具有非空行号的不同 (file, line) 位置。多条规则或类别位于同一行时只计一次;不同文件中相同的行号分别计数。没有行号的发现(包括依赖项发现和缺少 Dockerfile USER 检查的发现)不纳入统计。空报告或完全不含位置信息的报告结果为零。此项统计的是应用限制后保留的发现位置,而非扫描的行数、所有受影响文件或源代码覆盖率。分诊不会筛除该计数;发现、严重性门槛和 manifest_hash 均保持不变。
summary.finding_lines_by_severity 按 critical、high、medium、low 和 unknown 分别统计不同的保留 (file, line) 位置,包括零计数。没有行号的发现不纳入统计。同一位置、同一严重性的多条发现只计一次;同一位置若对应两种严重性,则会计入两个分组,因此各分组之和可能超过 finding_lines。这些是原始发现严重性;分诊不会筛除或重新分类这些计数。
summary.finding_lines_by_file 按相对文件路径细分该计数,路径按字典序排序。每个值统计该文件中所有规则和类别下不同的、非空的保留行号。仅包含无位置信息发现的路径会被省略;空报告或完全不含位置信息的报告结果为 {}。即使 --summary-only 省略了发现列表,各值之和仍等于 summary.finding_lines。
summary.files_with_findings_by_category 按 dependency-vuln、secret 和 config 分别统计不同的相对文件路径数,包括零计数。同一文件中的重复发现,在每个类别内只计一次;同时包含 secret 和 config 发现的文件会计入两个类别。因此,这些计数之和可能超过 files_with_findings。当扫描限制导致结果被截断时,此项仅统计已输出的发现,且不会影响 manifest_hash。
summary.by_category 统计 dependency-vuln、secret 和 config 的已输出发现数,包括零计数。即使扫描限制导致报告被截断,各值之和仍等于 findings 的长度。一条依赖项发现可能代表多个公告;该计数并非公告数量。依赖项发现数为零并不能证明覆盖完整(参见上文的 summary.osv_enabled)。这些计数不会影响 manifest_hash。
summary.by_category_severity 将每个类别的计数细分为 critical、high、medium、low 和 unknown,包括零值。例如,summary.by_category_severity.secret.high 统计已输出的高严重性 secret 发现。每行之和等于相应的 summary.by_category 计数,每列之和等于相应的 summary.by_severity 计数。这些是原始严重性,不受 --triage 影响;当限制导致报告被截断时,仅统计已输出的发现。此摘要不会影响 manifest_hash 或 --fail-on 退出门槛。
summary.dependency_findings_by_ecosystem 按生态系统统计已输出的依赖项发现数,生态系统按排序顺序排列。仅显示有保留发现的生态系统;各计数之和等于 summary.by_category["dependency-vuln"]。同一条发现中的多个公告 ID 只计一次。计数同时受两项发现上限影响,而 summary.vulnerable_packages_by_ecosystem 统计的是受影响的清单条目,即使相应发现已被省略也会计入。没有已输出的依赖项发现时,结果为 {};这并不能证明覆盖完整(请检查 OSV 状态、错误和截断情况)。分诊和严重性门槛不会筛除这些计数。此字段不会影响 manifest_hash。
summary.dependency_findings_by_manifest 统计每个代表性清单路径中输出的依赖项发现数量,并按路径的字典序排序。即使某条发现列出了多个公告 ID,也只计数一次;其他发现类别不计入。多个清单共享的软件包/版本只归入选定的代表性路径,而不会计入包含它的每个清单。没有保留依赖项发现的路径会被省略;结果为空时为 {}。计数总和等于 summary.by_category["dependency-vuln"],并反映发现数量限制。这些计数不能证明公告覆盖情况;请检查 OSV 状态、错误和截断情况。分流、严重性门槛和 manifest_hash 均不变。
summary.dependency_findings_with_fixes 统计至少报告了一个 fixed_in 版本的依赖项发现。每条软件包/版本发现只计数一次,即使涉及多个公告或修复版本。此项统计的是已报告的修复元数据,而非经过验证的升级建议:列出的版本不一定能修复该发现中的每个公告。空报告、离线报告和无修复报告均返回零;零不能证明覆盖完整,也不能证明不存在修复版本。发现数量限制可能导致部分依赖项未计入。此字段不会影响门槛或 manifest_hash。
summary.dependency_findings_without_fixes 统计 fixed_in 列表为空的已输出依赖项发现。它统计的是发现,而非公告 ID;只要某条发现报告了任意修复,就不会计入,即使其中其他公告没有修复元数据。与 dependency_findings_with_fixes 相加后,等于 summary.by_category["dependency-vuln"]。缺少修复元数据并不能证明不存在已修复版本。空报告、离线报告和无公告报告均返回零;发现数量限制可能导致部分结果被省略。此计数不受 --triage 影响,也不会影响发现、严重性门槛或 manifest_hash。
summary.secret_hits_by_rule 按现有的易读 summary 标签对已输出的密钥发现进行分组,并按标签字母顺序排列。计数总和等于 summary.secret_hits;没有密钥发现时结果为 {}。重复命中会分别计数,包括提供商规则和通用规则同时匹配同一值的情况。低严重性提示命中也会计入,且 --triage 不会改变计数。这些标签不是稳定的规则 ID,也不代表凭据已通过验证;其中不包含匹配值。扫描限制会使计数仅涵盖已输出的发现。此字段不会影响 manifest_hash 或严重性门槛。
summary.config_issues_by_file 按相对文件路径统计已输出的配置问题,并按路径的字母顺序排列。同一文件中的多个问题会分别计数,包括没有行号的文件级问题。没有配置问题的路径会被省略;没有配置问题时结果为 {}。计数总和等于 summary.config_issues,且仅反映扫描限制内保留的问题,并不代表配置覆盖完整。
summary.config_issues_by_rule 按现有的易读 summary 标签对已输出的配置问题进行分组,并按字母顺序排列。这些标签不是稳定的规则 ID。计数总和等于 summary.config_issues;没有配置问题时结果为 {}。同一文件中的多个问题会分别计数,而每个文件仅输出一次的检查仍只计数一次。文件和问题数量限制可能导致部分结果被省略,因此这些计数不能证明配置覆盖完整。其中不包含凭据值,也不会影响发现、严重性门槛或 manifest_hash。
summary.highest_severity 是已输出发现项中的最高严重性,排序为 critical、high、medium、low,然后是 unknown。所有发现项都未评级时,其值为 unknown;未输出任何发现项时,JSON 值为 null。该字段使用原始严重性,不受 --triage 影响;unknown 不会触发 --fail-on。扫描限制可能会遗漏更严重的发现项,空报告也不能证明覆盖范围。请检查 summary.truncated、summary.osv_enabled、备注和错误。此摘要字段不会影响 manifest_hash。
summary.unique_advisories 统计已输出的 dependency-vuln 发现项中不同公告 ID 字符串的数量。同一 ID 在不同软件包和版本中只计一次;即使不同 ID 是同一问题的别名,也会分别计数。此值不同于 summary.vulnerable_packages,后者统计发现项限制生效前受影响的清单条目数量。文件和发现项限制会约束公告数量。即使使用 --no-osv,值为 0 也仅表示未输出任何公告 ID;这不能证明覆盖范围完整或查询成功。请检查错误、备注和截断标记。此摘要字段不会影响 manifest_hash。
summary.unique_advisories_by_ecosystem 将每个生态系统映射到保留的依赖项发现中不同公告 ID 字符串的数量,生态系统键按字母顺序排序。同一 ID 在同一生态系统内的不同软件包、版本和文件中只计一次。一个 ID 若在多个生态系统中共用,则会分别计入每个生态系统,因此这些计数之和可能大于 summary.unique_advisories。即使不同 ID 是别名,也会分别计数。未输出公告 ID 的生态系统会被省略;未输出任何 ID 时,结果为 {},即使使用 --no-osv 也是如此。文件和发现项限制会约束这些计数;它们不能证明覆盖范围完整或查询成功。发现项、分诊、门禁和 manifest_hash 均不会改变。
summary.dependency_findings_by_advisory 将每个公告 ID 映射到引用该 ID 的已输出依赖项发现数量,键按字母顺序排序。同一发现项中的重复 ID 只计一次;不同的软件包版本和生态系统分别计数。同一生态系统、软件包和版本的重复清单不会增加发现项数量。即使不同公告 ID 是别名,也会分别计数。键的数量等于 summary.unique_advisories;由于一个发现项可能引用多个公告,值的总和可能大于依赖项发现数量。计数仅包括限制生效后保留的发现项;未输出任何依赖项发现时,结果为 {},即使使用 --no-osv 也是如此。这不能保证覆盖范围完整。此摘要不会改变发现项、门禁、分诊或 manifest_hash。
summary.dependency_files_by_advisory 将每个公告 ID 映射到其已输出依赖项发现中不同代表性清单路径的数量,键按字母顺序排序。同一文件中的多个软件包或版本共享一个公告时只计一次;同一公告出现在两个代表性路径中则计两次。软件包去重会为每个生态系统、名称和版本选择一个代表性路径,因此这并不统计包含受影响软件包的所有清单。即使不同公告 ID 是别名,也会分别计数。计数仅基于限制生效后保留的发现项;未输出任何依赖项发现时,结果为 {},即使使用 --no-osv 也是如此。值为 0 不能保证覆盖范围完整。此字段不会改变发现项、门禁、分诊或 manifest_hash。
--fail-on SEV 和 --fail-on=SEV 接受不区分大小写的值。重复指定时,无论使用哪种形式,最后一个值均生效:--fail-on HIGH --fail-on=none 会禁用门禁,而 --fail-on=none --fail-on HIGH 会启用 high 门禁。较早的无效值仍会以代码 2 退出;较晚的有效值不能修正参数错误。
当最终的 --fail-on 值启用门禁时,summary.gate 会记录规范化后的 threshold、达到或超过该阈值的已输出原始发现数量(matching_findings),以及 passed(该数量为零时为 true)。unknown 永远不会匹配任何阈值;--triage 不会改变门禁。未使用 --fail-on 或其最终值为 none 时,会省略此字段,且该字段不会影响 manifest_hash。通过门禁表示通过退出代码门禁,并不代表覆盖完整:请检查审查错误、备注和截断标记,因为被丢弃的发现不会计入数量。
summary.total_findings 统计所有类别和严重性中已输出的发现总数,包括 unknown,统计时已应用文件数和发现数上限。它等于 findings 的长度,空报告时为零。--triage 和 --fail-on 不会过滤此数量;低于门禁阈值的发现仍会计入。数量为零并不能证明扫描完整:请检查审查错误、备注和截断标记。此汇总字段不会影响 manifest_hash。
summary.limits 记录已应用的上限:files(已访问的候选文件数)、findings(已输出的发现总数)和 findings_per_file(每个文件跨所有类别输出的发现数)。这些是上限,而非实际统计数;请查看 summary.truncated,了解文件或发现结果是否因达到这些上限而被省略。截断标记为 false 并不能证明覆盖完整:其他筛选条件和限制仍然生效。此元数据不会影响 manifest_hash。
summary.highest_severity_by_file 将每个受影响的相对文件路径映射到其已输出发现中的最高严重性,并按路径的字典序排序。unknown 的级别低于 low;没有发现的文件会被省略,空扫描则返回 {}。此字段仅汇总已输出的发现,因此发现数上限可能会隐藏严重性更高的结果。请检查 summary.truncated,了解结果是否不完整。与整体最高严重性一样,此字段不会影响 manifest_hash。
summary.files_by_highest_severity 按每个受影响的相对文件路径在所有类别中的最高已输出原始严重性计数,每个路径只计一次。对于 critical、high、medium、low 和 unknown 均会包含零计数(unknown 的级别低于 low)。这些计数之和等于 summary.files_with_findings;没有发现的文件不计入。只有达到限制后保留的发现才会参与统计,因此请检查 summary.truncated,了解结果是否不完整。Triage 不会改变这些计数,它们也不会影响 manifest_hash 或严重性门禁。
summary.files_by_severity 按每种原始严重性(critical、high、medium、low、unknown)统计不同相对文件路径的数量,包括零计数。同一文件中同一严重性的重复发现只计一次;包含多种严重性的文件会计入各自对应的类别,因此计数之和可能大于 summary.files_with_findings。计数仅涵盖截断后已输出的发现,不受 --triage 影响,也不会影响 manifest_hash 或严重性门禁。
summary.secret_lines 统计已输出机密发现中不同的 (file, line) 位置数。同一行上的多个命中只计一次;不同文件中的相同行号分别计数。多行私钥发现仅计入其报告的头部行。这是源代码位置数,并非不同凭据的数量;数量为零表示没有已输出的机密发现,并不代表覆盖完整。发现数限制可能导致某些位置未被统计。此计数包含低严重性的提示,且不受 --triage 影响;它不会影响发现、门禁或 manifest_hash。
summary.findings_by_path_context 将所有类别中保留的问题划分为 scaffold 和 other 两类并统计数量(即使数量为零,也会包含这两个键)。
它使用 packages_by_path_context 采用的现有路径段启发式规则,该规则不区分大小写,涵盖 test、fixture、example、documentation 和
.github 目录。统计的是问题数量,而不是不同文件的数量;两项数量之和等于 summary.total_findings。只有扫描限制后保留的问题才会计入。
此路径标签无法确定文件是否已部署,也无法证明问题无害,并且不会改变原始严重性、分类结果或严重性门槛。
summary.finding_lines_by_path_context 统计相同 scaffold 和 other 路径分组中不同的保留 (file, line) 位置数,包括零计数。
没有行号的问题不计入。一个位置上的多个问题只计一次,即使它们属于不同类别或严重性也一样;不同文件中的相同行号分别计数。两项数量之和等于
summary.finding_lines。这些是扫描限制后需要审查的位置,不代表源码覆盖范围,也不能证明 scaffold 问题无害。分类不会筛选这些计数。
summary.config_lines 统计已输出的配置问题中带行号的不同 (file, line) 位置数。同一行上的多条规则只计一次;
不同文件中的相同行号分别计数。行号为 null 的文件级问题(例如缺少 Dockerfile USER)不计入。此项仅统计扫描限制后保留的问题,不代表扫描过的所有配置行。
summary.config_lines_by_rule 将配置问题摘要标签映射为不同 (file, line) 位置的数量,并按标签字母顺序排序。
同一行上同一规则的重复命中只计一次;不同文件中的相同行号分别计数。line: null 的问题不计入,没有可定位到行的问题的规则会被省略(空报告使用 {})。
不同规则可能对应同一位置,因此这些计数相加后不一定等于 summary.config_lines。计数仅描述问题数量上限限制后输出的问题;
它们不衡量未检查的位置,也不会改变问题、分类结果、门槛或哈希值。
summary.config_files_by_rule 将配置问题摘要标签映射为受影响的不同文件数量,并按标签字母顺序排列。同一文件中同一规则的重复警告只计一次;同一文件可以计入多个规则。
标签是便于阅读的描述,不是稳定的规则 ID。只有已输出的配置问题才会计入,因此扫描限制可能会漏掉受影响的文件。没有配置问题时返回 {};这不代表覆盖完整。
这些计数包含所有原始严重性,不受 --triage 影响,也不会影响 manifest_hash 或门槛。
分类(--triage)
triage_summary.dependency_actions_by_ecosystem 将每个存在保留依赖问题的生态系统映射为 upgrade 和 monitor 问题数量(即使数量为零,也会包含这两个键)。
生态系统键按字母顺序排列;没有保留的依赖问题时返回 {}。缺少生态系统元数据时使用 unknown。
每个问题计一次,不受公告 ID 或修复版本影响;scaffold 问题也会计入。这些可选计数遵循现有的修复元数据操作:
非空 fixed_in 对应 upgrade,否则对应 monitor。这些计数无法证明版本已发布、软件包数量不重复或公告覆盖完整,并且会受问题数量限制影响。
问题、现有分类结果、门槛和 manifest_hash 均保持不变。
triage_summary.dependency_actions_by_file 将相对的代表性清单路径映射到 upgrade 和 monitor 发现项计数;每个路径都包含这两个类别。路径按排序排列;如果没有保留的依赖发现项,该映射为 {}。每个发现项只计一次,不受公告 ID 或修复版本数量影响:fixed_in 非空时归入 upgrade,否则归入 monitor。这些分类依据现有的修复元数据,并不表示已确认有可用版本。脚手架路径也包含在内。若某个软件包出现在多个清单中,则只归入扫描器选定的代表性路径;扫描限制可能会截断这些计数。此选择性启用的摘要不会改变发现项、分流操作、哈希或门禁。
triage_summary.dependency_actions_by_severity 将已输出的依赖发现项分为 upgrade 和 monitor 两行,并分别列出原始严重性计数(critical、high、medium、low、unknown,包括零值)。每个发现项只计一次,不受其公告 ID 或修复版本数量影响;依照现有的分流操作,fixed_in 非空时归入 upgrade,否则归入 monitor。脚手架锁文件也会按其原始严重性计入,即使分流将其优先级标记为低。此选择性启用的计数仅涵盖扫描限制生效后保留的发现项,并不能证明公告覆盖情况,也不能证明每条公告都有修复方案。它们不会改变发现项、标题摘要、严重性门禁或 manifest_hash。
使用 --triage 时,输出会增加两个顶层键;原始发现项和 manifest_hash 保持不变:
triage_summary— 计数和一行式headline:将依赖发现项分为upgradeable(fixed_in非空)和monitor_only(fixed_in为空),并将密钥命中分为needs_review和likely_false_positive。headline是面向用户的摘要,其措辞可能随版本而变化;使用者应从结构化字段(triage_summary、summary.unique_advisories)读取计数,而不是从headline读取。secret_review_by_severity按原始严重性汇总需要审核的密钥命中(critical、high、medium、low、unknown,包括零值)。 它不包括可能的误报;若扫描被截断,则仅统计已输出的发现项。这些计数不会影响原始严重性门禁。config.needs_review统计配置发现项,并将其纳入标题摘要,即使不存在密钥或依赖发现项也如此。与其他分流计数一样,若扫描限制导致结果被截断,它仅涵盖已输出的发现项。config_review_by_severity按原始严重性汇总这些配置发现项(critical、high、medium、low、unknown,包括零值)。 测试/夹具路径下的配置发现项仍计为需要审核;这些计数不会改变发现项、分流操作或原始严重性门禁。triage— 从发现项id到判定结果的映射:对于dependency-vuln,action为upgrade(并带有upgrade_to)或monitor;对于secret,若命中位于测试/夹具/示例/CI 路径、翻译目录路径(locales/、i18n/、…、v0.4.1),或带有值形态标签(占位符、表达式、非 ASCII、带点名称、表单占位符、示例键——参见密钥部分),则将likely_false_positive设为 true;其余情况均归入review;config发现项归入review。 依赖项操作使用报告的修复元数据,而非独立检查版本是否已发布。monitor表示该发现没有报告的修复版本:建议详情未包含修复元数据或加载失败时,也可能出现这种情况。这并不能证明不存在已修补版本;请检查errors和建议。upgrade表示至少有一条建议提供了修复版本。一个发现可能合并多条建议,因此,非空的upgrade_to列表并不能证明其中列出的版本能解决每条建议。选择升级版本前,请逐条确认适用的版本。诸如“没有已修补版本”之类的现有标题和备注措辞,是基于元数据的标签,并非对可用版本的保证。
这只是纯粹确定性的优先级排序(修复元数据 + 基于路径的误报分类)。它绝不会消除在真实源代码中发现的密钥,而是将其标记出来供人工处理。它的目的是让读者无需对冗长的原始列表逐项进行人工分类。
triage_summary.by_action 统计分配给 upgrade、monitor、review 和 informational 的发现数量,包括值为零的类别。总数等于输出的发现数量,而非文件数或建议 ID 数。review 包含配置发现;脚手架依赖项发现保留其 upgrade/monitor 操作。当扫描限制导致报告被截断时,这些计数仅涵盖输出的发现。此字段仅在使用 --triage 时出现,且不会改变严重性门槛。
triage_summary.files_by_action 统计这四种操作各自涉及的不同相对文件路径数量,包括值为零的类别。同一文件中分配给一种操作的重复发现只计一次;包含不同操作的文件会计入每个适用类别,因此这些计数之和不一定等于文件总数。没有非空文件路径的发现不会计入这些文件数。脚手架依赖项保留其 upgrade/monitor 操作。当扫描限制导致报告被截断时,计数仅涵盖输出的发现。此字段仅在使用 --triage 时出现,且不会改变发现、哈希或严重性门槛。
使用 --triage 时,triage_summary.config_files_needing_review 统计包含已输出配置发现的不同相对文件路径数量。同一文件中的多条发现只计一次;测试/示例/夹具路径中的文件仍会计入,因为配置发现保留其 review 操作。密钥和依赖项发现不计入。无需审查配置文件时,该计数为零;扫描限制导致结果被截断时,该计数仅涵盖保留的发现。现有的 triage_summary.config.needs_review 仍表示发现数量。此附加计数不会改变发现、分流操作、哈希或门槛。
使用 --triage 时,triage_summary.config_review_by_file 将相对文件路径映射到各文件中需要审查的保留配置发现数量。键按字母顺序排序。同一文件中的多条发现分别计数;测试/示例/夹具路径仍会包含在内。密钥和依赖项发现,以及没有非空文件路径的发现,不计入。没有带路径的配置发现时,此映射为空。扫描限制导致结果被截断时,映射仅涵盖保留的发现,并且在使用 --summary-only 时仍可用。此字段不会改变发现、分流操作、哈希或严重性门槛。
使用 --triage 时,triage_summary.dependency_files_needing_upgrade 统计已输出的依赖项发现中具有 upgrade 操作的不同代表性清单路径数量。同一文件中的多个包或安全公告只计一次;脚手架路径仍会计入。仅监控的依赖项和非依赖项发现不计入。如果没有已输出的依赖项发现包含修复元数据,则计数为零。该计数使用包去重后现有的代表性清单归因,因此并不统计所有受影响的清单。发现数量上限可能会降低该计数;此计数不能证明安全公告已完整覆盖,也不能证明升级可以解决每条安全公告。发现、分诊操作、哈希和门禁均保持不变。
triage_summary.secret_files_needing_review 统计包含已输出且被归类为 review 的密钥发现的不同相对文件路径数量。同一文件中的重复命中只计一次;疑似误报的密钥和非密钥发现不计入。如果没有需要审核的密钥发现,则计数为零。达到扫描限制时,该计数只统计已输出发现中涉及的文件,不包括未输出的文件。与分诊的其他内容一样,此计数不会改变发现、哈希或门禁。
triage_summary.secret_review_by_file 将相对路径映射到已输出且被归类为 review 的密钥发现数量,路径按字母顺序排序。同一文件中的重复命中分别计数;信息性密钥和非密钥发现不计入。各值之和等于 triage_summary.secrets.needs_review,路径数量等于 triage_summary.secret_files_needing_review。没有需要审核的密钥时,结果为 {}。此字段仅在使用 --triage 时出现;发现数量上限可能导致计数不完整。这些分类基于启发式规则,并不表示已确认凭据仍有效。此字段不会添加原始值,也不会改变发现、哈希或严重性门禁。
使用 --triage 时,triage_summary.dependency_outside_scaffold_lockfiles 包含 upgradeable 和 monitor_only 计数,统计来源路径不符合现有测试/示例/夹具脚手架启发式规则的已输出依赖项发现。现有的依赖项总体计数仍包括脚手架命中。这些是发现数量,并非安全公告数量或建议的目标版本;归并到一个依赖项发现中的多条安全公告只计一次。计数为零的类别也会列出,发现数量上限可能导致计数不完整。路径分类有助于确定审核优先级,但不能证明其用于生产环境或可达。
triage_summary.secret_false_positive_reasons 按现有的单条发现 reason 统计已输出的密钥发现,键按顺序排列;没有符合条件的发现时为 {}。每个密钥只计一次:按照单条发现分诊的规则,测试路径优先于测试文件名、本地化路径和值提示。计数之和等于 triage_summary.secrets.likely_false_positive。这些是启发式原因,并非已确认的误报;忽略发现前请先审核。如果数量限制截断了发现,则只统计已输出的命中。此可选摘要不会改变原始发现、manifest_hash 或严重性门禁。
triage_summary.secret_false_positives_by_file 将相对文件路径映射到被归类为疑似误报的已输出密钥发现数量。键按顺序排列;没有符合条件的发现时,映射为 {}。同一行上的多个发现分别计数;一个文件中可以同时包含信息性密钥和需要审核的密钥,但此处只统计信息性密钥。计数之和等于 triage_summary.secrets.likely_false_positive;应用扫描限制时,只包含保留的发现。这是基于启发式规则的分诊辅助信息,并不能确认文件安全;原始发现和严重性门禁均保持不变。
summary.secret_files_by_rule 将机密发现摘要标签映射到受影响文件的去重计数,并按标签字母顺序排列。同一文件中同一标签的重复命中只计一次;同一文件可按多个标签计数。标签是便于阅读的描述,不是稳定的规则 ID。计数包含所有已输出的机密发现,包括低严重性和疑似误报的命中,且不受 --triage 影响。扫描限制可能导致部分受影响文件未被纳入。没有机密发现时返回 {};这并不代表覆盖完整。此摘要不会更改发现、脱敏、manifest_hash 或严重性门槛。
summary.secret_lines_by_rule 将相同的、便于阅读的机密标签映射到不同 (file, line) 位置的计数,并按标签字母顺序排列。同一标签在同一行中的重复命中只计一次;同一行可按多个标签计数,而不同文件中的相同行号分别计数。计数包含所有已输出的机密发现,包括低严重性和疑似误报的命中。扫描限制截断结果时,只计入保留的发现。没有机密发现时返回 {};这并不代表覆盖完整。此摘要不会添加任何匹配值,也不会更改发现、manifest_hash、分诊或门槛。
检查内容
1. dependency-vuln
解析的清单/锁文件(跳过 node_modules/、vendor/、.git/、dist/、build/、target/ 下的文件以及虚拟环境):
| 文件 | 生态系统 | 备注 |
|---|---|---|
requirements*.txt | PyPI | 仅处理固定版本的 == 行;去除标记/注释 |
poetry.lock、uv.lock | PyPI | TOML [[package]] 中的名称/版本 |
Cargo.lock | crates.io | TOML [[package]] |
package-lock.json | npm | v2/v3 的 packages 映射;回退到 v1 dependencies 树 |
yarn.lock(v1 + Berry) | npm | v1 的 name@range: + version "x";Berry 的 "name@npm:range": + version: x(跳过 workspace:/patch:/portal:/link: 条目,v0.4;v0.4.2 修复了包含多个 @ 的补丁键) |
pnpm-lock.yaml | npm | packages: 键 /[email protected] 或 [email protected] |
go.mod | Go | require 构建列表(直接依赖 + // indirect);优先于 go.sum(v0.4) |
go.sum | Go | 仅当其旁边没有 go.mod 时处理(go.sum 也会列出已被取代的版本);去除 /go.mod 后缀 |
Gemfile.lock | RubyGems | 仅处理 GEM 部分中的 specs:,格式为 name (1.2.3);排除 GIT/PATH 规格 |
通过区分大小写的文件名发现 requirements 文件:任何目录深度下,名称以
requirements 开头并以 .txt 结尾的文件均符合条件。
例如,requirements-dev.txt 和 nested/requirements_extra.txt 符合条件;
Requirements.txt、requirements.in、constraints.txt 和 base.txt 不符合条件。
Pip 的包含和约束指令(-r/--requirement、-c/--constraint)
会被忽略,不会跟进。因此,引用 base.txt 不会将其中固定的依赖版本纳入清单。
即使没有指令引用,匹配的 requirements*.txt 文件也会被单独发现,
但仍受常规目录和文件限制约束。
只有指令的 requirements.txt 可能会以零个软件包的状态出现在 summary.lockfiles_parsed 中;
这并不表示已评估其包含目标中的依赖项。
对于 poetry.lock、uv.lock 和 Cargo.lock,TOML 读取器使用软件包名称/版本对,
不会检查来源元数据。因此,根据文件名,具有受支持名称/版本对的 Git、本地/可编辑和无来源条目
可能会作为 PyPI 或 crates.io 软件包进入清单和公告查询。这些查询不会发送来源 URL 和修订版本;
具有相同生态系统、名称和版本的条目会合并为同一身份,即使其来源不同。注册表公告匹配
并不能证明分支或本地软件包包含受影响的代码。采取行动前请检查来源。
--no-osv 会保留此清单,但跳过公告查询。
对于 package-lock.json,包清单不会根据 dev、optional、devOptional、os 或 cpu 元数据进行筛选。带有这些字段的条目可能会从 packages 映射和备用的 dependencies 树进入 npm 公告查询,其中包括仅供开发使用的包的嵌套依赖项。这是静态锁文件清单,并非 npm ci --omit=dev 安装的包,也不代表当前操作系统和 CPU 上安装的包。--no-osv 会保留此清单,但跳过公告查询;发现问题并不能证明相关包已安装于生产环境或可被访问。
对于 pnpm-lock.yaml,清单来自 packages: 中符合识别规则的键,不会解析 importers: 或 snapshots: 中的依赖关系图。即使没有任何导入器引用某个已识别的包,或其元数据标记该包仅供开发使用、为可选包,或适用于其他操作系统/CPU,该包仍可能进入 npm 公告查询。仅在导入器或快照中找到的条目不会计入清单。包键中括号内的 peer 上下文后缀会被移除;名称和版本相同的条目只计一次,而不是按每个 peer 上下文分别计数。--no-osv 会保留此清单,但跳过公告查询。这些数量无法证明哪些包已安装或可被访问;即使成功解析了锁文件,包数量仍可能为零。
依赖项发现会在所有扫描目录层级中按名称精确区分大小写地匹配这些基名;requirements*.txt 是前缀/后缀匹配的例外。例如,Package-lock.json、package-lock.json.bak 和 Requirements.txt 不会提供清单。package.json、npm-shrinkwrap.json、Pipfile.lock、pyproject.toml 或 composer.lock 也不会:其中声明的依赖项不会被解析或发送以进行公告查询。这些文件仍可能接受本地密钥/配置检查。如果目录树中只有未识别的清单文件名,已盘点的包数量为零,summary.lockfiles_parsed 为空,并会显示未解析任何受支持的依赖清单的提示;这并不表示已评估这些依赖项。
对于 package-lock.json,packages 映射中的包名称取自其键中最后一个 node_modules/ 之后的路径,包括作用域名称。条目中的 name 元数据不会覆盖该已安装名称。在旧版 dependencies 树中,依赖项键提供名称,version 字符串则按原样保留。扫描器不会解析 npm 别名:因此,别名可能会以其安装名称而非底层包名称进行查询,旧版 npm:... 版本也不会被解码。使用 --no-osv 时,离线清单也会保留这些相同的标识。如果报告中没有依赖项发现结果,并不能证明已覆盖底层的别名包公告。
对于 package-lock.json,仅当 packages 映射未产生任何包条目时,才会使用旧版 dependencies 树。因此,映射缺失或为空、仅包含根条目,或其条目缺少可用版本时,都可能回退到旧版树,包括嵌套依赖项。映射一旦产生至少一个包,就不会合并这两个部分:仅存在于旧版结构中的包和旧版中的其他版本不会进入清单或公告查询。因此,部分可识别的映射可能导致旧版条目未被评估。使用 --no-osv 进行包清单盘点时,也适用此选择规则。
Go 清单的选择以目录为单位:若选定的扫描文件列表中包含 go.mod,则只会排除其旁边的 go.sum,不会排除子目录或同级目录中的 go.sum。选择发生在解析之前。空的 go.mod、没有可识别依赖项的 go.mod,或无法读取的 go.mod,仍会排除相邻的 go.sum;扫描器不会在解析失败时改用 go.sum。反之,若选定文件列表中不包含某个 go.mod,即使该文件存在于磁盘上,也不会因此排除 go.sum。请检查 summary.lockfiles_parsed 和 summary.packages_found:在这些情况下,包数量为零并不意味着已评估校验和文件中列出的依赖项。
对于 package-lock.json,清单不会仅限于生产依赖或当前机器上已安装的软件包。v2/v3 的 packages 映射和 v1 的 dependencies 树都会纳入符合识别条件的名称/版本条目,不受 dev、optional、devOptional、os 或 cpu 元数据影响,嵌套条目也包括在内。这些条目会计入软件包汇总,并可用于查询安全公告;--no-osv 会保留清单,但跳过该查询。因此,报告中出现某个软件包,并不能证明它已安装或已用于已部署的应用程序。
对于 Gemfile.lock,只有 GEM 部分中列出的 gem 才会纳入软件包清单并查询 RubyGems 安全公告。GIT 或 PATH 下列出的 gem 会被排除,即使其名称和版本看起来与注册表中的 gem 相同。仅包含这些被排除来源的锁文件仍可能显示在 summary.lockfiles_parsed 中,但贡献的软件包数为零,也不会触发安全公告请求。因此,干净的报告并未评估被排除的 Git 或本地路径依赖。
对于 yarn.lock,使用 workspace:、patch:、portal:、link:、file:、exec:、git:、github:、http: 或 https: 的描述符会从软件包清单和安全公告查询中排除,即使其条目包含数字版本也是如此。此筛选会应用于逗号分隔列表中的每个描述符;同一列表中可识别的注册表描述符仍可贡献软件包。普通 v1 范围和 Berry npm: 描述符仍受支持。仅包含被排除描述符的锁文件可显示在 summary.lockfiles_parsed 中,但软件包数为零,也不会触发安全公告请求。此类报告不会评估被排除的依赖项,包括 patch: 描述符所基于的注册表软件包。
Gemfile.lock 中的版本会按原样保留,包括 1.2.3-x86_64-linux 之类的平台后缀;扫描器不会在查询安全公告时将其规范化为 1.2.3。PLATFORMS 部分不会筛选纳入清单的 GEM 规格,因此 synthetic-native (1.2.3) 和 synthetic-native (1.2.3-x86_64-linux) 会计为两个软件包/版本条目,并分别进行查询。仅有 PLATFORMS、DEPENDENCIES 和 BUNDLED WITH 元数据不会提供任何软件包版本。这是锁文件清单,并非对特定主机上已安装 gem 的解析;没有安全公告响应并不能证明平台后缀版本已涵盖在内。
使用 go.sum 时,同一模块/版本的模块内容行和 /go.mod 行会合并为一个清单条目;单独的 /go.mod 行也会生成一个条目。同一模块的不同版本仍会作为独立条目和安全公告查询处理,包括已被取代的版本。扫描器会检查 h1: 校验和字段是否非空,但不会验证其编码,也不会根据模块内容校验其摘要。此清单既不是已安装模块列表,也不是校验和完整性评估;通过门禁并不能证明这两点。
软件包按 (ecosystem, name, version) 去重,并以每批 500 个的分块发送至
POST https://api.osv.dev/v1/querybatch。随后会通过 GET https://api.osv.dev/v1/vulns/{id} 获取每个返回的漏洞 ID(每次运行期间缓存在内存中),以取得严重级别和修复版本。
严重级别:若 database_specific.severity 的值可识别(CRITICAL/HIGH/MODERATE/MEDIUM/LOW,不区分大小写),则优先采用该值。否则,使用 severity 列表中首个受支持的数字评分,并按 ≥9 为严重、≥7 为高、≥4 为中等、否则为低进行映射;不支持的条目会跳过,两种来源均不可用时则使用 unknown。对于同一条安全公告,采用列表顺序,而非最高数字评分:评分依次为 4.0、9.8 时结果为中等,反过来则为严重。因此,仅第一种顺序不会触发 --fail-on high。软件包发现结果采用其所有漏洞中的最高严重级别,并合并 fixed_in 版本。
修复元数据还有其他限制:对于每条公告,扫描器会从所有 affected 条目和范围中收集非空的 fixed 事件值,不会根据所查询的包、生态系统、范围类型或已安装版本进行筛选。
它会忽略 introduced、last_affected 和 limit 事件值。去重后,按字典序对剩余字符串排序(因此 10.0 会排在 2.0 前面),并为每条公告仅保留前十个值。包发现项的
fixed_in 是这些截断列表的排序去重并集;该并集可能超过十个值。使用 --triage 时,upgrade_to 会重复此并集。
这些值是公告元数据,并非针对特定包的升级计划:其中可能包含无关的包版本或 Git 修订,不按语义版本排序,也不能证明所列值能修复已安装包的每条公告。选择升级版本前,请查看原始公告和适用的发行版本。
不会评估 CVSS 向量字符串。只有向量字符串、没有可识别的 database_specific.severity 的公告,其严重性仍为 unknown。包含此类公告的包仍可能产生依赖项发现项,但受发现项数量限制;其他已评级公告可以提高包发现项的严重性。unknown 发现项不会触发 --fail-on,即使阈值为 low 也不会触发。因此,通过严重性门槛并不代表未评级的公告已无风险。
每次 HTTP 调用的超时时间为 20 s。请求失败会追加到顶层 errors 列表,扫描会继续。如果批次请求失败,该批次不会产生依赖项发现项,但仍会查询后续批次。已解析的包仍会保留在清单中;发现项数组为空并不能证明其公告已成功检查。
如果批次请求返回了公告 ID 后,公告详情请求失败,扫描器会保留该 ID 并使用回退详情:严重性为 unknown、没有修复版本,并以该 ID 作为公告摘要。此回退信息会在本次扫描剩余期间缓存,因此共享的失败公告只会获取一次,也只会记录一次错误。受发现项数量限制,受影响的包仍会产生依赖项发现项。同一包的其他成功加载的公告可以提供更高的严重性和修复版本。如果详情请求全部失败,--triage 会将其指定为 monitor,而即使使用 --fail-on low,生成的 unknown 发现项也不会触发门槛。请求错误本身不会触发严重性门槛;解读成功退出代码前,请同时检查 errors、发现项和清单。
2. secret
对大小不超过 1 MiB 的文本文件进行正则表达式扫描。会跳过二进制文件(含 null 字节)、.git/、
node_modules/、vendor/、dist/、build/、锁文件、*.min.js 以及常见的二进制文件和图像扩展名。证据一律脱敏,只保留前 4
个字符,后接 …。
密钥检查直接处理文本,不会解析源语言或 Markdown。因此,注释、字符串字面量或 Markdown 代码块中的匹配令牌或通用赋值内容,仍可能在对应源代码行产生发现项;注释标记和代码围栏不会将其排除。现有文件/行数限制、模式规则和值形态提示仍然适用。--triage 使用路径和值的启发式规则对这些发现项分类,不会根据注释或代码块语法分类;原始严重性门槛仍会计入这些发现项。
路径以点开头本身不会导致文件或目录被排除。例如,.settings 可以接受密钥检查,.config/ 下的文件也可以接受密钥、配置和受支持的依赖项检查。.git/ 和 .venv/ 等指定目录排除规则、文件类型排除规则以及常规扫描限制仍然适用;隐藏路径不属于单独的覆盖类别。
通用赋值检查会跳过以 http://、https://、ws:// 或 wss:// 开头的值(不区分大小写)。这会排除端点字符串,但不会排除 URL 中的所有密钥:提供方令牌模式仍会报告,并对证据脱敏。其他协议以及格式错误的 URL 前缀不适用此排除规则。
仅在长度不超过 4,000 个字符的行上检查单行提供方模式和通用赋值(不包括行结束符;每个 Unicode 字符计为一个字符)。更长的行会被跳过,但仍会检查同一文件中的后续行。单独的多行私钥块检查不受此逐行限制影响;但仍受上述文件大小和文件选择限制约束。报告中没有发现机密,并不能证明被跳过的行不含机密。
1 MiB 限制指 1,048,576 个文件字节,而非解码后的字符数。恰好达到此限制的文件仍可扫描;更大的文件会被整体跳过,包括首行中的任何机密。其他符合条件的文件仍会扫描。超大文件仍可能计入 summary.files_scanned;跳过其文本不会设置 summary.truncated,该字段的标志描述的是文件数量和发现数量上限。因此,发现列表为空且两个标志均为 false,并不能证明所有文件内容都已检查。
扩展名排除会在读取内容前执行,且不区分大小写:即使 .svg / .SVG 文件中包含可读文本,也不会进行机密扫描。这些文件仍会计入 summary.files_scanned;跳过它们不会设置 summary.truncated。仅在排除文件中发现的机密不会触发严重性门槛,因此通过门槛并不能证明这些文件不含机密。
机密扫描对锁文件的排除规则使用以下确切文件名(任意目录深度均适用):package-lock.json、yarn.lock、pnpm-lock.yaml、poetry.lock、uv.lock、Cargo.lock、Gemfile.lock、go.sum、go.mod 和 composer.lock。其中包括 go.mod 清单和 composer.lock,尽管不支持 Composer 依赖项解析。这并非一概排除所有 .lock 文件:若其他条件允许,custom.lock 以及 requirements.txt、package.json 和 pyproject.toml 等其他清单仍会进行机密扫描。受支持的依赖项清单和查询独立于此机密扫描排除规则运行。已解析的锁文件中没有发现机密,并不能证明其内容已接受机密检查。
共享文本读取器会先尝试 UTF-8,若 UTF-8 解码失败,则回退到 Latin-1。因此,ASCII 令牌模式仍可能在 Latin-1 文件中匹配。任何 NUL 字节都会导致整个文件在解码前被跳过,即使令牌出现在该字节之前也一样。包含 NUL 字节的常规 UTF-16/UTF-32 文本会被跳过,而不是转码。此类文件仍可能计入 summary.files_scanned;没有发现机密(且严重性门槛检查成功)并不能证明其内容已接受检查。
*.min.js 排除规则检查的是区分大小写的文件名后缀,而非文件内容:即使名为 bundle.min.js 的文件包含格式化文本,也会被跳过。bundle.MIN.JS 和 bundle.min.js.map 等名称,以及普通 .js 文件中的紧凑代码,都不会被此规则排除;其他扫描限制仍然适用。被跳过的 .min.js 文件可以计入 summary.files_scanned,而不会设置 summary.truncated。仅存在于这些文件中的机密不会触发严重性门槛,因此通过门槛并不能证明这些文件已接受检查。
| 模式 | 严重程度 |
|---|---|
AKIA[0-9A-Z]{16}(AWS 访问密钥 ID) | 高 |
gh[pousr]_[A-Za-z0-9]{36,} | 高 |
github_pat_[A-Za-z0-9_]{80,} | 高 |
glpat- + 20 个 URL 安全字符(GitLab 个人访问令牌) | 高 |
npm_ + 36 个字母数字字符(npm 访问令牌) | 高 |
sk- 或 sk-proj- + 至少 40 个 URL 安全字符(OpenAI;不包括 sk-ant-) | 高 |
sk-ant- + 至少 40 个 URL 安全字符(Anthropic) | 高 |
hf_ + 至少 34 个字母数字字符(Hugging Face) | 中 |
SG. + 22 个 URL 安全字符 + . + 43 个 URL 安全字符(SendGrid) | 高 |
数字 + : + 35 个 URL 安全字符(Telegram 机器人令牌) | 高 |
SK + 32 个十六进制字符(Twilio API 密钥 SID) | 中 |
xox[baprs]-[0-9A-Za-z-]{10,}(Slack) | 高 |
sk_live_[0-9a-zA-Z]{24,}(Stripe 正式环境密钥) | 严重 |
AIza[0-9A-Za-z_-]{35}(Google API 密钥) | 中 |
完整的多行 BEGIN … PRIVATE KEY / 匹配的 END 块 | 严重 |
| 已知私钥头后跟一行类似 base64 的内容(旧式回退规则) | 严重 |
通用 api_key / api-key / apikey / secret / password / passwd / token 赋值 | 中 |
Stripe 正式环境受限密钥(rk_live_ + 至少 24 个字母数字字符) | 高 |
上述通用赋值名称不区分大小写;此规则不识别单独的 key。赋值使用 = 或 :,后跟单引号或双引号括起来的值;值至少包含 16 个字符,且不含空白字符或内嵌引号。通用赋值键也可以用成对的单引号或双引号括起来,例如 JSON 中的 "password": "…16+ chars"。这是一种基于行的启发式规则,并非 JSON/YAML 解析器;现有的值排除规则和提示降级仍然适用。
上述 URL 安全字符包括 A-Z、a-z、0-9、_ 和 -。
完整私钥块接受任何由大写字母和空格组成的标签(包括未标注的 PRIVATE KEY),要求至少包含三行 base64 正文,并在头部所在行生成一条 Multiline private key block detected 发现结果。该发现结果会替代旧式头部匹配。正文提示会沿用现有的示例/掩码和数值形态检查,将严重程度降至低;诸如 ...、YOUR_KEY_HERE、REDACTED 或 xxxx 这样的短占位符正文也会保留为低严重程度的发现结果。证据绝不会包含正文。源代码中没有正文的单独头部仍会被忽略。
旧式头部回退规则还会针对未标注的 RSA、EC、OPENSSH、DSA 或 PGP 私钥头部生成一条严重的 Private key block detected 发现结果,前提是下一行包含至少 20 个来自 A-Z、a-z、0-9、+、/ 或 = 的字符(允许前后有空白)。此回退规则不要求存在尾部或完整密钥,也不适用正文占位符降级。其证据只包含经过脱敏的头部。这些是文本形态检查,不能证明正文是有效的 base64 或可用的私钥材料。
如果通用赋值规则中的值匹配
example|changeme|your[_-]|xxx|dummy|placeholder|<|${,则该规则会降至低,并在证据中标记 (placeholder?)。
值形态提示(v0.4.1)。 2026-10-08 数据集显示,路径规则过滤后留下的大多数密钥命中项其实并非凭据,因此现在证据会带有以下标签之一;当值符合形态启发式规则时,原始严重程度会降至低;--triage 会将带有相同标签的项归类为可能的误报:
| 标签 | 形态 |
|---|---|
(expression?) | 模板 / 环境引用:={{$credentials.x}}、${VAR}、$VAR、$__env{…}、{env:…}、#{…}、%{…}、<%…、var(--…) |
(non-ascii?) | 通用赋值的值包含非 ASCII 文本(通常是翻译后的 UI 文案或 •••• 掩码);实际密码也可能匹配 |
(dotted-name?) | 不含数字的点分标识符(entity.other.inherited-class、grafana.someFlagToken、主机名) |
(word-like?) | 通用赋值的值以 ASCII 字母开头,且仅包含 ASCII 字母、下划线和连字符,不含数字;包括大小写混合的值,不仅限于单词或字段名 |
(form-placeholder?) | 匹配项位于表单的 placeholder= 属性中 |
(example-key?) | 符合固定模式的命中项是文档中的示例(AKIAIOSFODNN7EXAMPLE、GitHub 文档中的 ghp_…),或是 xxxx…/0000…/**** 掩码值 |
提示不会抑制匹配项:保留的命中项仍会连同标签保留在 findings 中,并受上述发现数量限制约束。这些是用于排序优先级的启发式规则,并非凭据验证。实际密码可以包含非 ASCII 字符,也可以不含数字。即使是 "AbCdEfGhJkLmNpQrStUv" 这样的大小写混合值,也会匹配 word-like?;匹配到的通用赋值仍会以低严重性报告,并由 --triage 分类为可能的误报。这些命中项本身不会触发 --fail-on medium(或更高阈值),但会触发 --fail-on low。没有提示的通用赋值会保持中等严重性;路径启发式规则在分诊期间仍可能将其分类为可能的误报。Provider-token 命中项(私钥处理除外)使用示例/掩码和表单占位符提示,不使用通用的 word-like? 或 non-ascii? 检查:有效载荷全为字母的匹配 GitHub token 仍可能具有高严重性。任何提示都无法确定凭据是否有效。测试路径规则也在 v0.4.1 中扩大:__fixtures__、__mocks__、testutils/、test-utils/、snapshots/、testUtils.*。
3. config
Dockerfile 检查会按区分大小写的基名选择文件:在任意纳入检查的目录层级中,文件名必须恰好为 Dockerfile,或以 Dockerfile. 开头(例如 services/api/Dockerfile.dev)。这些检查不会选择 Containerfile、dockerfile 或 service.Dockerfile。如果符合其他条件,这些其他名称仍可能接受独立的密钥模式扫描;未发现 Dockerfile 配置问题,并不能证明这些文件的内容已通过 Dockerfile 规则检查。
.env/.env.*中至少包含一条赋值(KEY=value,可选地以export为前缀)——高危。文件名恰好以小写.example、.sample、.template或.dist结尾的文件不参与此配置检查。此豁免不会跳过机密模式扫描:符合条件的模板文件中,形似凭据的值仍可能产生机密发现,并触发--fail-on。模板文件名并不能证明其内容安全。此检查每个文件只报告一条发现,位置为第一条匹配的赋值;空值和PORT=8080等公开设置也计入。此检查确认是否存在赋值,而非判断值是否为机密。此类文件即使机密命中数为零,仍可能导致--fail-on high失败,包括启用--triage时。赋值名称必须是未加引号的 ASCII 标识符:以字母或下划线开头,后接字母、数字或下划线。允许行首有空格,也允许=前有空格;可选的export前缀必须为小写,且后接空格或制表符。带引号的名称、含点或连字符的名称、以数字开头的名称以及非 ASCII 名称均不符合此配置规则;后续符合条件的赋值仍可能被报告。这是基于行的存在性检查:不会解析多行带引号的值、执行 Shell 命令或展开变量。独立的机密模式扫描仍会进行。Dockerfile(或Dockerfile.*)中没有USER指令——低危,运行时用户未经验证(基础镜像可能设置了USER;扫描器不会检查镜像)。Dockerfile中存在ADD http(s)://…——低危。Dockerfile(或Dockerfile.*)的最后一个阶段中,最后一个显式指定的USER为root或0,可带组后缀——低危。更早的阶段和已被覆盖的用户不会触发此检查;不会解析继承的用户或变量。Dockerfile基础镜像没有标签或使用:latest——低危。带非 latest 标签的镜像、完整 SHA-256 摘要引用、scratch、较早阶段的别名以及包含$的镜像引用均不在检查范围内。标签并不能保证不可变。每条FROM都会检查,包括未使用的构建阶段;会跳过--platform=linux/amd64这类前置--name=value选项。注册表端口不算标签:registry.example.invalid:5000/team/app会被标记。latest标签比较区分大小写(:Latest不在检查范围内);scratch和较早阶段的别名比较时不区分大小写。摘要豁免条件只识别恰好包含 64 个十六进制字符的@sha256:后缀,不会验证镜像是否存在、摘要是否完整或是否可信。这并非摘要验证器:非 latest 标签本身就可能排除带格式错误摘要的引用。不会解析变量或阶段依赖。每条匹配的指令都会在其起始行产生一条发现,且省略镜像引用。Dockerfile中ENV/ARG赋值的变量名称看起来像机密——高危。API_TOKEN和DB_PASSWORD等名称会匹配;空值、单独的ARG声明以及整个值为$VAR/${VAR}的引用不在检查范围内。名称必须使用 ASCII 字母、数字和下划线,且以字母或下划线开头。不区分大小写:PASSWORD、PASSWD、SECRET、TOKEN、API_KEY、PRIVATE_KEY、SECRET_KEY或SECRET_ACCESS_KEY必须是完整名称,或是名称中下划线之后的后缀。MYTOKEN、TOKEN_FILE、PASSWORD_HASH、APIKEY和单独的KEY不符合此配置规则。ENV NAME=value和旧式ENV NAME value两种形式都会检查;同一条指令中的多个NAME=value赋值会分别检查。处理引号后,只有空值或完整的简单变量引用才不在检查范围内:${VAR:-demo}、${VAR}/suffix和prefix$VAR仍会产生发现。demo、changeme和xxxx等短值与占位符仍会被判为高危配置发现;机密模式扫描中的占位符降级规则不适用于此规则。启用--triage时,这些发现仍需review,并可能触发--fail-on high,即使机密命中数为零。证据中会省略赋值的值。这是基于名称和值的启发式判断,不会验证凭据,也不会执行 Docker 变量展开;未产生发现并不能证明值安全。更早阶段中的指令也会计入;报告位置为续行指令的首行,证据中会省略值。某项赋值不在检查范围内,并不保证扫描器的其他规则也会忽略其内容。- Shell 形式的
DockerfileRUN中,curl直接管道传递给sh或bash——中危。不会分析 JSON exec 形式的指令和间接 Shell 执行。此规则只标记命令形式,不会获取或运行任何内容。Dockerfile 配置检查使用范围有限的逻辑指令启发式规则,而非完整的 Dockerfile 或 Shell 解析器。反斜杠续行(或带有可识别的# escape=指令时使用反引号)会合并物理行;合并指令的发现位置指向其首个物理行。空行和整行注释会被跳过,包括续行中的空行和注释。RUN、COPY和ADD中可识别的命名 heredoc 会跳过其正文和定界符行:正文中看似指令的文本不会作为 Dockerfile 配置检查。支持简单的无引号或匹配引号的定界符名称,包括去除前导制表符的<<-和多个定界符;不会解析通用 Shell 语法。这些排除规则仅适用于配置检查。单独的机密扫描仍会检查符合条件的注释行和 heredoc 正文行,因此这些行仍可能产生机密发现并触发严重性门槛。 docker-compose*.yml/docker-compose*.yaml/compose.yml/compose.yaml中包含普通的privileged: true声明——中危。键可以不加引号,也可以用匹配的单引号或双引号括起;值必须仍是普通的true。此基于行的启发式规则允许尾随注释,但会忽略注释掉的声明、无关的键以及trueish这类更长的标量值;不会解析通用 YAML 结构。文件名匹配区分大小写,但键和普通值匹配时不区分大小写(例如,"PRIVILEGED": TRUE)。带引号的值(如privileged: "true")和内联映射(如{privileged: true})会被忽略。每条匹配的行都会被报告,包括嵌套文件中的行,且不会检查服务的层级关系:扩展项或无关映射下的匹配行也可能被报告。此发现是供审查的提示,并不代表已确认容器实际拥有特权;未发现问题也不能证明 Compose 文件未启用特权。.github/workflows/*.y(a)ml中的远程 Action 或可复用工作流uses:引用未使用完整的 40 字符十六进制提交 SHA——低危。包括actions/*;本地./Action 和docker://引用不在检查范围内。所有工作流配置检查只会选择相对于扫描目标的.github/workflows目录的直接子文件,目录名称区分大小写,扩展名必须为小写.yml或.yaml。不会选择workflows子目录中的文件、嵌套仓库中的工作流以及build.YML等文件。以嵌套仓库为目标进行扫描时,会选择该仓库自身.github/workflows目录的直接子文件;直接扫描.github/workflows本身则不会选择任何文件。其他符合条件但不在此选择范围内的文件仍会单独进行机密模式扫描。未发现工作流配置问题,并不能证明文件已由这些规则检查。- 由
pull_request触发且工作流或作业的permissions:声明了write-all或某个范围为write的工作流——中危。此规则标记请求的权限;作业级覆盖、分支来源限制和仓库策略都可能降低实际访问权限。检查会统计显式声明,不会解析继承关系或作业级覆盖:即使作业设置了permissions: {}或read-all,工作流级write-all仍会被报告。以write设置的各个独立块映射范围都会分别产生一条发现;简单的内联权限映射即使包含多个write范围,也只产生一条发现。缺少声明不会推断默认权限。此检查要求触发器包含pull_request;仅有pull_request_target、push或其他触发器都不会启用此检查。未发现此问题,并不能证明实际访问权限为只读。 pull_request_target工作流中,actions/checkout步骤的with.ref使用${{ github.event.pull_request.head.sha }}、${{ github.event.pull_request.head.ref }}或${{ github.head_ref }}——高危。checkout 和 ref 必须位于同一步骤中;将 checkout Action 固定到完整提交 SHA 并不会抑制此项单独检查。匹配为字面匹配且区分大小写;表达式花括号内允许空格,也会匹配嵌入较长 ref 字符串中的受支持表达式。扫描器不会计算表达式:${{ github['head_ref'] }}这类方括号表示法、${{ env.SELECTED_REF }}这类间接引用,以及${{ github.head_ref || 'main' }}这类包含运算符的表达式均不符合这些形式。因此未发现问题,并不能证明 checkout ref 不受 PR 输入影响。.npmrc中包含精确的无作用域或注册表作用域_authToken键且后接=,或包含password行——高危。Token 键必须在可选空白之后位于行首;=前允许空格或制表符。其他选项名称、值或尾随注释中的 Token 标记不计入。.pypirc保留对精确_authToken=子串和有锚定的空格键形式的检查,并保留password规则。 在这两个文件中,_authToken匹配区分大小写且必须包含=;_AUTHTOKEN=x和_authToken: x不会触发此配置检查。password键匹配时不区分大小写,必须在可选空白之后位于行首,并允许在可选空白后使用:或=(例如,PassWord : x)。db_password和password_extra等名称不匹配。 被忽略的行不会阻止后续符合条件的行被报告。 这些是扫描器的筛选规则,并非对软件包管理器配置的完整解释;符合条件的机密模式仍可能被单独报告。 空的或仅包含空白字符的password=/password:字段会被忽略;后续凭据行仍会检查。对于这种保守启发式规则,带引号的值和分隔符后的内联注释仍视为非空。 在.npmrc中,无作用域或注册表作用域的_authToken值如果仅由${NAME}构成(变量名由字母、数字和下划线组成,且以字母或下划线开头),则不在检查范围内。混合字面值和引用的值仍会被标记;不会读取或展开环境变量。 此配置检查会跳过整行#和;注释(包括缩进的注释)。机密模式扫描仍会检查注释文本。 对每个精确匹配的.npmrc或.pypirc文件(包括嵌套目录中的文件),此检查只会在该文件中产生第一条符合条件的配置发现,位置为原始行号,然后停止检查该文件。跳过的注释、空密码以及受支持的.npmrc环境变量引用均允许继续查找后续匹配项。 这并不是凭据数量统计:后续匹配行不会增加配置发现,但单独的机密扫描仍可能在同一文件中产生更多发现。扫描限制也可能导致部分发现被省略。 上文列出的所有配置检查均已实现。
例如,扫描这个合成 Dockerfile 会产生五条配置发现:
基础镜像和 root 用户各有一条低级别发现,ENV 和 ARG 各有一条高级别发现,
管道有一条中级别发现。证据中会省略赋值内容和 URL。扫描器将其作为文本读取;
不会构建镜像,也不会执行命令。
FROM alpine:latest
USER root
ENV API_TOKEN=synthetic-value
ARG DB_PASSWORD=synthetic-value
RUN curl https://example.invalid/install | sh
Dockerfile 检查采用有界的指令和 shell 启发式方法,而不是完整的 Docker 解析器或镜像检查器。注释和受支持的 heredoc 正文会被忽略; 不会解析基础镜像元数据、变量展开和运行时覆盖项。
限制
- 仅采用确定性检测。 仅使用纯正则表达式/解析器匹配以及 OSV 查询。不使用大模型,也不进行可达性分析或污点跟踪。
- 工作流检查采用 YAML 启发式规则,仅检查扫描根目录下
.github/workflows/中的文件。可识别块映射、步骤列表、标量/列表/映射形式的事件声明,以及简单的内联权限映射。注释和字面量/折叠式脚本正文会被忽略。不会解析锚点/别名、一般流映射(包括内联步骤/checkout 输入)、多行带引号标量和间接表达式;这些形式可能被漏检。这些检查用于提示审查,并不能证明存在可利用性,也不是完整的工作流审计。 不会评估作业和步骤的if:条件:即使是if: false或if: ${{ false }},也不会抑制匹配的声明。例如,已禁用步骤中的未固定 checkout 仍会产生低严重性发现;已禁用的 PR 作业若请求写入权限,仍会产生中严重性发现。这些发现仍会出现在 JSON 和分流结果中,并可能在达到相应阈值时触发--fail-on。它们描述的是配置文本,而不是作业或步骤是否会执行。 未固定引用检查会识别步骤和可复用工作流uses:声明中的远程owner/repo[/path]@ref值。任何恰好为 40 个字符的十六进制引用(大写或小写,包括全零)都通过此格式检查;标签、分支、短哈希和非十六进制引用会产生低严重性发现。检查不会获取引用,也不会验证提交是否存在、来源或可信度。本地./和../引用、docker://容器引用以及不含@ref的值不在检查范围内,因此没有发现固定引用并不能证明它们安全或有效。格式完整的 checkout SHA 不会抑制单独的pull_request_targetPR 头部 checkout 发现、其审查分流或其门限检查。 - 不审查误报。 测试夹具、文档示例以及已轮换/撤销的凭据都会被报告。唯一的启发式过滤是上述路径规则和值格式提示(v0.4.1);在非测试路径中,具有合理格式的已提交真实凭据绝不会被排除。
- 不扫描 Git 历史。 仅检查已检出的工作树(针对 URL 目标会使用
--depth 1克隆),因此,虽已在后续提交中移除但仍存在于历史记录中的密钥会被漏检。 - 本地扫描不会遵循
.gitignore,也不会将文件限制为 Git 索引中的文件。 也会扫描未跟踪和已忽略的常规文件,但受扫描器的目录/文件排除规则以及大小/数量限制约束。这些文件中的发现可能触发--fail-on;本地文件被忽略并不代表它已提交。 - 目录排除规则适用于扫描目标下的每一层目录。 排除的名称区分大小写,具体为
.git、node_modules、vendor、dist、build、.venv、venv、__pycache__、.mypy_cache、.tox、.next和target。这些目录及其子树不会纳入依赖清单、密钥检查和配置检查,即使它们位于src/之类的目录下也是如此。类似名称(Vendor、vendor-copy、myvenv)和其他点目录不会按此规则排除。不会根据显式选定的目标目录本身的名称进行过滤:直接扫描vendor/时会检查其内容,但仍会排除其中名称匹配的子目录。被排除的文件不会计入summary.files_scanned,这些排除也不会设置summary.truncated。因此,报告为空且严重性门限检查通过,并不能证明已覆盖被排除的子树。 - 传递依赖解析以锁文件中已有的记录为准——会完全忽略未固定的
requirements.txt行(>=、~=、未固定)。通配符==版本(demo==1.*)属于前缀范围而非精确版本,也会被跳过,因此不会出现在依赖清单或公告查询中。不会评估;后的 requirements 环境标记,而是直接丢弃:demo==1.0; python_version < "0"仍会将demo版本1.0纳入依赖清单,并在启用时纳入公告查询。会从名称中剥除 extras(demo[fast]==1.0会变为demo==1.0);不会因此触发额外的依赖解析。相同的包/版本固定项只计一次;即使标记互斥,不同的固定版本仍会保留。这是静态依赖清单,并非当前解释器或平台上的已解析安装结果。 - Go:扫描器会查询
go.modrequire条目(直接 + 间接)中记录的模块路径和版本,而不是已解析的构建列表。会忽略replace和exclude指令:被替换或排除的依赖仍会以原始路径和版本纳入依赖清单,并在启用时进行公告查询。不会跟进或替换为替代目标;指令本身不会添加任何包。不会运行 Go 工具链或模块图解析。 仅当go.sum旁边没有go.mod时才会读取go.sum,因为它也会记录已被取代的模块版本(v0.4 — 2026-10-08 的数据集显示,go.sum命中项中有 99% 不在go.mod中)。不进行调用图可达性分析:如需此功能,请使用govulncheck。 - 会解析 Yarn v1 和 Yarn Berry(v2+)锁文件(v0.4 起支持 Berry;会跳过 workspace/patch/portal 条目)。不支持
composer.lock、Maven/Gradle 和 NuGet。 summary.lockfiles_parsed会列出受支持且可读取、并且解析器正常返回的清单,即使未返回任何包也会列出。空文件、未固定的 requirements 和某些格式错误的输入(例如无效的 package-lock JSON)可能出现在此列表中,而errors中没有对应条目。列表非空并不能证明语法有效、已发现包或公告查询成功。不支持的文件以及超过 8 MiB 读取限制的清单会被省略。仅当此列表为空时才会显示“No supported dependency manifest was parsed”提示;还应检查summary.packages_found、summary.osv_enabled和errors。 对于位于测试/夹具/示例/基准测试路径下锁文件中的依赖命中项,--triage会将其标记为scaffold(低优先级);若某个包同时列在夹具和已发布的锁文件中,则归因于已发布的锁文件。- 没有 CVSS 的 GHSA 条目通常缺少 OSV 严重性,因此严重性会显示为
unknown。 - 密钥检测按行进行;例外情况是具有匹配标签的完整多行私钥块,以及上文所述的标头/下一行回退匹配。不完整的区块仍可能产生严重回退发现。不会解析 PEM 元数据行、转义换行符和其他多行编码数据块。会检查区块正文是否符合 base64 格式,但不会解码或进行密码学验证;合成示例也可能匹配。
Dockerfile 检查
对于 Dockerfile 和 Dockerfile.*,配置检查结果还包括:
ADD使用 HTTP(S) 源,包括 JSON 操作数和前置选项 — 低。 证据中会省略 URL,包括用户信息和查询字符串。 此检查会在源操作数中不区分大小写地匹配字面量http://或https://前缀(除最后一个操作数外的所有操作数都视为源,最后一个视为目标)。 每条指令最多产生一条检查结果,即使包含多个远程源也是如此。 前置的--name=value选项(包括--checksum)不会抑制此检查: 即使 HTTP(S) 源固定了校验和,仍会产生相同的低严重性检查结果, 并可能触发--fail-on low。不会验证校验和或下载的内容。本地路径、 Git/SSH/FTP 形式以及$SOURCE/${SOURCE}引用不符合此规则;不会展开变量。 仅作为目标出现的 URL 也不符合规则。这项检查针对的是源的形式, 并非验证 Docker 语法,也不代表下载内容未经验证。- Shell 形式的
RUN直接将curl的输出通过管道传给sh或bash— 中。 此检查会比较命令的基名,因此/usr/bin/curl ... | /bin/sh也会匹配,包括出现在;、&&或||之后的情况。每条RUN最多产生一条 管道检查结果。管道必须是单个|令牌;|&、中间有其他命令的情况、env/sudo等包装命令,以及curl前的赋值都无法识别。标记化时会移除引号, 因此值恰好为|的带引号参数也可能触发此启发式规则 (例如RUN curl '|' sh);检查结果不能证明 Shell 实际执行了管道。 不会获取或执行脚本和 URL。 ENV/ARG赋值的值非空,且变量名类似密钥(例如API_TOKEN、DB_PASSWORD、PRIVATE_KEY、AWS_SECRET_ACCESS_KEY)— 高。只会报告名称。 未设置的参数、空值以及普通的$NAME/${NAME}引用会被排除;合成值或默认值仍可能 被标记。这是配置警告,并非凭据验证。FROM使用显式的:latest或没有标签 — 低。固定了摘要的镜像、scratch、先前的构建阶段名称以及基于变量的引用会被排除。- 最终阶段中最后一个
USER显式设置为root或 UID0,也可以 带字面量组名(root:app、0:1000)— 低。较早阶段和被覆盖的 用户会被排除。不会解析基于变量的用户、继承的镜像用户以及用户名到 UID 的映射; 仅有 root 组不会触发此检查。此检查会匹配字面量且区分大小写的指令值:ROOT、00、 带引号的"root"和root # comment不会被识别为显式 root。 组后缀必须非空,且只能包含 ASCII 字母、数字、下划线、句点或连字符。 这些排除项并不能证明运行时身份不是 root。
仅当文件中任何位置都没有已解析的 USER 指令时,缺少 USER 规则才会产生一条
源行号为 null 的低严重性检查结果。例如,
FROM scratch AS builder、USER root,然后是 FROM builder,不会产生任何 USER 检查结果:
较早的指令会抑制针对整个文件的缺少用户检查,而最终阶段没有可匹配的显式用户。
扫描器不会解析从先前阶段或镜像继承的用户。对于跨行延续的显式最终 root 用户,
检查结果会指向第一条物理行。
这些规则是范围有限的语法启发式规则。续行会保留第一条物理行的行号,注释和简单的命名 heredoc
正文会被跳过,格式错误的带引号指令会被忽略。不会评估 heredoc 脚本、exec 形式的 Shell 命令、
间接管道、变量展开、自定义 Shell 以及完整的 Dockerfile/Shell 语法。现有的缺少 USER
检查针对整个文件,而非各个阶段。检查结果是供审查的提示,并非构建存在漏洞的证明。
数据集
此扫描器针对一组公开代码仓库运行并发布的结果,以及固定提交和可复现这些结果的脚本,位于 datasets/ 下:
2026-10-08-popular-oss— 涵盖 npm、PyPI、Go、crates.io 和 RubyGems 中的 25 个热门代码仓库;展示原始计数的误差(go.sum中的虚项、测试夹具锁文件、未知严重性、密钥正则表达式噪声),以及由此改进的扫描器。
扫描器之外
付费版会对单个 GitHub 或 GitLab 代码仓库(公开或私有)进行三轮 AI 审查,发现模式匹配无法识别的问题(身份验证与注入漏洞、逻辑错误、竞态条件):面向约 5 万行以内代码仓库的 49 美元 Codebase Audit Report,以及提供复现内容、起价 149 美元的深度审计:https://project-feldspar.com/?utm_source=github-readme&utm_medium=referral&utm_campaign=st1(价格另加结账时收取的任何税费)。
真实开源项目的示例报告位于 audits 代码仓库中。
条款
此扫描器、托管扫描服务 /scan/、其 API、MCP 端点和此 GitHub Action 均为免费工具,依据 L3Digital Standard Commercial Terms v1.1 第 2.5 节提供:https://project-feldspar.com/terms/v1.1#s2-5(按现状提供,不提供任何保证或支持承诺;仅可用于扫描你有合法权利扫描的代码)。
代码本身仍采用 MIT 许可证(见下文)。托管服务的隐私声明:https://project-feldspar.com/privacy。
许可证
MIT。版权所有 (c) 2026 Project Feldspar。付费版的付款由 L3Digital LLC d/b/a Project Feldspar 处理;本代码库中的任何内容均不代表 L3Digital LLC 发表声明。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。