CI 绿灯,扫描器却没跑:ASH v3 的 incomplete scan 判定
项目信息
curl -sSfL https://astral.sh/uv/install.sh | sh
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.7.0"
有 v3 浮动 tag;需要可复现就固定到 v3.7.0。
现象:退出码 0 不等于没有漏洞
ASH 有三种执行模式:
local:Python 3.10+ 与 uv,部分扫描器需要自己安装外部工具container:任意 OCI 运行时(Finch/Docker/Podman),Windows 走 WSL2precommit:Python 3.10+ 与 uv,只运行扫描器子集,为速度做了裁剪
local 模式下,有些扫描器需要依赖外部环境:npm-audit 需要 Node,Grype 与 Syft 需要自行安装,cfn_nag 需要 gem install cfn-nag。缺少这些工具时,扫描器不会执行,但 ASH 不会把它当作“跳过”。
ASH 内置了 Scanner Validation System,跟踪扫描器从注册、启用、排队、执行到进入结果的全过程;其中任何一个环节没有完成,都会被记录下来。默认 --fail-on-incomplete-scanners 是开启的:被选中的扫描器只要状态是 ERROR 或 MISSING,退出码就为 1。文档中的原话是“An incomplete scan is not a clean scan”。
内容库过期属于另一类风险。当扫描器的内容库超过声明的年龄上限时,默认会让扫描失败;Grype 的上限是构建后 5 天。--allow-stale-content-db 打开后只记录 warning 并放行;这个参数优先于配置中的 content_db_staleness。
沙箱行为也一样。--sandbox 取值 auto|bwrap|firejail|landlock|sandbox-exec|off,默认是 off(或取 sandbox.mode)。如果要求使用沙箱,但某个扫描器无法按指定的方式沙箱化,该扫描器会被标记为 MISSING,不会被直接跳过。容器模式下这个参数会被忽略。
退出码分三档:
0:成功,没有 actionable findings,或未配置fail_on_findings1:执行期间出错,包括 incomplete scan2:检测到 actionable findings 且fail_on_findings: true(默认 true)
因此,CI 日志里只有一行 0 时,先确认它没有被 --no-fail-on-incomplete-scanners 或 --allow-stale-content-db 放行。
编排参数
--scanners/--exclude-scanners:精确选择要运行的扫描器--changed-files-only配合--base-ref:只扫描 base 分支到 HEAD 之间改动的文件--shard-index/--shard-count:必须成对使用,把一次扫描拆分到多个执行器--use-existing:复用已有结果文件--phases convert,scan,report,inspect--mode local|container|precommit;--oci-runner指定 finch/docker/nerdctl/podman--config/-c(默认.ash/.ash.yaml)、--config-overrides--workspace支持 VS Code.code-workspace多仓库扫描,--dry-run只看执行计划
每个扫描器占一行,列包括 Suppressed/Critical/High/Medium/Low/Info/Duration/Actionable/Result/Threshold。实际输出里可能是 bandit 7 条抑制、1 High、56 Low、19.9s、FAILED、MEDIUM(global);detect-secrets 48 High;grype 2 High 1 Medium。整轮 1m6s,122 条 actionable。
配置与 pre-commit
# yaml-language-server: $schema=...
project_name: my-project
global_settings:
severity_threshold: MEDIUM
ignore_paths:
- path: 'tests/test_data'
reason: 'Test data only'
scanners:
bandit:
enabled: true
options:
confidence_level: high
reporters:
markdown:
enabled: true
options:
include_detailed_findings: true
配置头部可加 JSON Schema 的 yaml-language-server 注释,编辑器里能做校验。
repos:
- repo: https://github.com/awslabs/automated-security-helper
rev: v3.7.0
hooks:
- id: ash-simple-scan
pre-commit run ash-simple-scan --all-files
工具隔离与输出
local、precommit 模式下,ASH 用 uv tool isolation 安装扫描器,并固定版本区间:bandit >=1.7.0,<2.0.0、semgrep >=1.125.0,<2.0.0、checkov >=3.2.0,<4.0.0;detect-secrets、cdk-nag 随 ASH 提供。这样做是为了避免扫描器依赖污染项目环境,也避免项目的锁文件反过来限制扫描器版本。
结果位于 .ash/ash_output:
ash_aggregated_results.json:完整机器可读结果reports/ash.summary.txt/ash.summary.md/ash.html/ash.csvreports/ash.sarif、ash.junit.xmlscanners/:各扫描器原始报告
ASH 遵循 .gitignore,也可以用 ignore_paths 排除路径。
已知取舍
cdk-nag 无法扫描 CDK 应用的结果。ASH 通过 CfnInclude 把裸 CloudFormation 模板交给 NagPack,而 CDK 合成的模板带有 BootstrapVersion 参数,会和 CfnInclude 冲突。官方建议在 CDK 代码里直接集成 CDK Nag;cfn-nag 和 checkov 仍然能扫描 CDK 合成的模板。
离线环境先用 ash --mode container --offline --offline-semgrep-rulesets p/ci --no-run 构建镜像,推到私有 registry;隔离环境中改用 --mode container --offline --no-build。
尚未确定的一点
severity_threshold: MEDIUM 和 --shard-index/--shard-count 一起使用时,门禁成本不好估算。每个 shard 只扫描自己那部分文件,threshold 过滤和 actionable 计数都在分片内完成;合并报告时是按 shard 汇总还是回到全量重算,直接决定分片数变化时门禁是否会显著波动。参数层面没有说明这一点,需要在 CI 里实测。