关于 scanner-pipeline
scanner-pipeline 是一项技能,可按仓库中的不同层面选择确定性安全扫描器、运行扫描、规范化发现结果,并为修复准备交接信息。它还会在目标仓库内创建扫描计划、工作流和扫描器配置;扫描在线应用则需要授权。
主要功能
- 01按仓库层面选择扫描器
- 02以非交互命令运行扫描器
- 03规范化并去重发现结果
- 04将规范化结果导出为 SARIF
- 05编写扫描计划和 CI 工作流文件
适用场景
- 01对包含多种生态的仓库执行安全检查
- 02汇总依赖、代码、机密和基础设施问题
- 03整理扫描结果以便分流和修复
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ briiirussell/ cybersecurity-skills --skill scanner-pipeline安装后在 Claude Code 中输入/scanner-pipeline确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/betterleaks-to-findings.mjsfindings-format.mdfindings.schema.jsonsecurity-scan.ymlto-sarif.mjsvalidate-findings.mjsreferences/semgrep/supabase-rls.yml详细介绍
扫描器流水线 — 运行实际工具、规范化并移交
别再让 Agent 改你的应用了。让它为每一层选用合适的确定性扫描器,以非交互方式运行,将输出合并为统一格式,然后把每项发现分派给修复 Agent。扫描器每次都会发现同样的问题;Agent 审查则只有在恰好检查到时才会发现。
来自 2026 年 10 月对 45 个应用进行排查的实地证据(patch-sweep)。Agent 主导的审查最终发现了所有这些问题;而扫描器在首次运行时就会标出每一项:
- 10 个 Supabase 表未启用 RLS,可使用公开的 anon 密钥读取和写入,其中包括 472k 封入站电子邮件正文。Supabase 自带的安全顾问会将此报告为
rls_disabled_in_public。 - 始终为真的 RLS 策略:
csr.collection_id = csr.collection_id。针对迁移文件运行的自定义 semgrep 规则可以捕获此问题(见下文)。 - 每个仓库分别运行
npm audit、pnpm audit和bundle-audit,而一次osv-scanner扫描就能覆盖所有锁定文件。
此技能负责协调。 深入解读工具的发现结果仍由对应层级的技能负责:secrets-audit、dependency-audit、container-audit、cloud-audit、owasp-audit。主动利用由 web-pentest / red-team-engagement 负责。
为何预先批准 Write: 此技能的主要输出是文件——scanner-pipeline.md 计划、.github/workflows/security-scan.yml 以及扫描器配置。它只会写入这些文件,而且仅限目标仓库。实时应用扫描器(nuclei、zap)不预先批准;它们始终会提示确认。
交叉引用:finding-triage(排序和处置)、secrets-audit、dependency-audit、container-audit、cloud-audit(Supabase 顾问、PaaS)、web-pentest(实时扫描授权)、cicd-security(固定 SHA 的 Actions、工作流强化;zizmor / actionlint 结果)、patch-sweep(跨项目群 CVE 响应)、log-watch。
授权
运行任何操作前,请确认:
- 仓库: 用户拥有每个仓库,或已获准扫描。若采用项目群模式,请确认要枚举的所有者 / 组织。
- 实时目标: 仅扫描用户拥有且在本次对话中明确指定名称的域名。不能自行推断“仓库部署到的任何地址”——用户必须明确给出主机名。
- 结果去向: 仅存放于本地文件、GitHub 代码扫描,还是 issue。密钥发现结果绝不能写入公开 issue。
拒绝以下操作:使用实时扫描器(nuclei、ZAP、Burp)扫描用户不拥有或未指定的主机;扫描第三方或共享主机(共享的 Supabase/Vercel/Heroku 主机名、CDN、用户调用的 API);未经过 web-pentest 授权步骤就运行 ZAP 主动扫描或 nuclei 入侵性模板;在任何地方打印密钥值。
1. 检测技术栈,并为每一层选择工具
先检查代码仓库:锁文件、Dockerfile、*.tf、supabase/、Gemfile、go.mod、.github/workflows/。然后为每一层选择一个工具,优先选择支持多种生态系统的工具,而非针对单个包管理器的工具。
| 层 | 首选 | 特定技术栈的附加工具 | 备选 |
|---|---|---|---|
| 密钥 | betterleaks git 或 gitleaks git(完整历史记录) | 活跃性检查,询问后再进行:betterleaks … -v / -a,或 trufflehog git --only-verified | — |
| 代码(SAST) | semgrep scan | Brakeman(Rails)、gosec(Go)、CodeQL(如果有 GHAS)、自定义 semgrep 规则(见下文) | — |
| 依赖项 | osv-scanner scan source -r .(一次性扫描 npm、pnpm、yarn、Bundler、pip、Go、Cargo、Composer、Maven) | bundle-audit(Rails)、govulncheck(Go,可达性调用图)、pip-audit | npm audit / pnpm audit --prod / yarn audit |
| 基础设施 / IaC / 容器 | trivy fs --scanners vuln,misconfig,secret . | checkov -d .(Terraform、CloudFormation、k8s、Actions)、trivy image <img> | — |
| 数据库(Supabase) | Supabase 安全顾问(supabase db advisors,或 Management API GET /v1/projects/{ref}/advisors/security) | supabase db lint(plpgsql_check:函数错误,不检查 RLS)、针对 supabase/migrations/ 的 semgrep 规则 | pg_policies / pg_tables.rowsecurity 查询 |
| 线上应用(受保护) | ZAP 基线(被动扫描) | 使用 -rl 的 nuclei | — |
命令均为非交互式,每种工具在 .security/out/ 下生成一个输出文件:
mkdir -p .security/out
# Secrets — full history, values redacted
gitleaks git . --redact --report-format sarif --report-path .security/out/gitleaks.sarif
# or Betterleaks: detection only and silent, so a stray BETTERLEAKS_VALIDATE=true can't use found keys and nothing prints.
# v2 has no SARIF reporter: convert, then export (needs_validation records go out at level "note")
betterleaks git . --validate=false --analyze=false --redact --silent -o .security/out/betterleaks.json
node references/betterleaks-to-findings.mjs .security/out/betterleaks.json -o .security/out/betterleaks.findings.json
node references/to-sarif.mjs .security/out/betterleaks.findings.json --include-needs-validation -o .security/out/betterleaks.sarif
trufflehog git file://. --only-verified --json --no-update | jq -c 'del(.Raw, .RawV2)' > .security/out/trufflehog.jsonl
# Code
semgrep scan --config p/default --config .semgrep/ --metrics=off --sarif --output .security/out/semgrep.sarif
# Dependencies (all lockfiles at once)
osv-scanner scan source -r . --format sarif --output .security/out/osv.sarif
# Infra / IaC / container files
trivy fs --scanners vuln,misconfig,secret --format sarif --output .security/out/trivy.sarif .
checkov -d . -o sarif --output-file-path .security/out/
# Rails
bundle-audit check --update
brakeman --no-pager -f sarif -o .security/out/brakeman.sarif
# Go
govulncheck -format sarif ./... > .security/out/govulncheck.sarif
gosec -fmt sarif -out .security/out/gosec.sarif ./...
# Supabase (check flags with `supabase db advisors --help` for your CLI version)
supabase db advisors --linked
curl -s -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
"https://api.supabase.com/v1/projects/<ref>/advisors/security" > .security/out/supabase-advisors.json
将 .security/out/ 添加到 .gitignore。注意,不同主版本的工具命令可能不同(gitleaks 8.19+ 使用 git / dir 子命令;osv-scanner v2 使用 scan source;Betterleaks v2 将 dir 重命名为 fs、将 --report-path 重命名为 -o,并将 --validation 重命名为 -v,而 v1 中 --verbose 表示详细输出);运行一次 <tool> --help,然后按需调整,不要猜测。
自定义规则:始终为真的 RLS 策略
引发问题的漏洞:using (csr.collection_id = csr.collection_id) 将列与其自身比较,因此该策略对每一行都会通过。作者原本想用外层表的列。Semgrep 没有一流的 SQL 解析器,因此使用通用模式:pattern-inside: create policy ... ;,再加上一个能反向引用左侧的正则表达式。完整规则文件(检查自比较、字面值 true,以及用于交叉检查 RLS 的公开表):references/semgrep/supabase-rls.yml。将其复制到代码仓库的 .semgrep/,以便 CI 工作流发现并使用它。若要根据已确认的问题编写此类规则并添加测试,请参阅 variant-analysis。
semgrep scan --metrics=off --config .semgrep/supabase-rls.yml supabase/migrations/
# quick check without semgrep (PCRE backreference)
rg -nP '(?i)\b([a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*)\s*=\s*\1\b' supabase/migrations/
# portable: BSD and GNU grep -E accept \1 in practice (ugrep aliased as grep does not; use /usr/bin/grep)
grep -rEni '([a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*)[[:space:]]*=[[:space:]]*\1([^a-z0-9_]|$)' supabase/migrations/
将其与 RLS 未启用检查配合使用;该检查需要数据库,而非源代码:
select schemaname, tablename from pg_tables
where schemaname = 'public' and rowsecurity = false;
2. 谨慎处理密钥
- Betterleaks 或 gitleaks。 规则 ID 相同,
gitleaks:allow注释也相同。Betterleaks 产生的通用密钥误报更少,会为每条发现生成一个值哈希,并检查密钥是否有效。v2 仍是候选发布版(betterleaks version;包管理器可能会安装 v1.9.0),已移除 SARIF,且不会读取.betterleaks.toml,除非你传入--config。标志、安全规则和报告字段:secrets-audit→references/betterleaks.md。 - 扫描完整历史记录,而不只是 HEAD。
gitleaks git会遍历每次提交;gitleaks dir只检查工作树。去年删除的密钥仍存在于每个克隆中。 - 验证通过意味着密钥仍有效。
trufflehog --only-verified会针对每个候选密钥向提供方的 API 发起检查。这会将找到的密钥发送给其提供方并使用该密钥,因此 trufflehog 不会预先获批:运行时会提示用户,用户需同意检查。经验证的命中属于 P0:将其记录为confirmed,并在清理历史记录之前先轮换密钥。先重写历史记录,会导致你处理期间每个分支和克隆中的密钥仍然有效。将轮换工作交给secrets-audit。 - 绝不打印密钥值。 gitleaks 使用
--redact;trufflehog JSON 中删除Raw/RawV2。报告、问题、PR 评论和 Agent 交接记录只写检测器、文件、提交和密钥值的哈希——绝不包含密钥值。将密钥指纹设为secret:<detector>:<sha256(value)[:12]>,这样同一个密钥出现在五个文件中时会合并为一条发现。Betterleaks 会在match.fingerprint中记录该哈希,并在脱敏前计算;references/betterleaks-to-findings.mjs 会据此构建记录。gitleaks 的--redact和移除Raw后的 trufflehog 不会保留密钥值,因此它们的命中会改为按文件和提交去重。对于会离开本机的哈希(CI 产物、问题),请使用--hmac-key:短令牌的普通 SHA-256 哈希可能会被暴力破解。 - 未验证的命中应标记为
needs_validation,而不是当作噪声:提供方可能不支持验证,密钥也可能用于内部系统。
3. 实时应用扫描护栏
实时扫描会向运行中的系统发送流量。请先遵循 web-pentest 的授权步骤;本节仅规定扫描速率和顺序。
- 仅扫描用户拥有且明确指定的主机名。 绝不扫描平台共用的域名根、CDN、身份验证提供方或应用调用的第三方 API。使用
-u https://app.example.com,不要使用通配符或从 DNS 抓取的列表。 - 先被动扫描,再主动扫描。 ZAP baseline 会以被动方式爬取并生成报告:
仅在用户确认后才运行主动扫描(docker run --rm -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py -t https://app.example.com -J zap.jsonzap-full-scan.py),最好在预发布环境的副本上运行。 - 低速率。
nuclei -u https://app.example.com -rl 10 -c 5 -severity medium,high,critical -exclude-tags intrusive,dos,fuzz -jsonl -o nuclei.jsonl。仅在用户同意后提高速率。 - 发现损害迹象时立即停止: 5xx 错误激增、收到速率限制响应、账户被锁定。报告情况并暂停。
4. 规范化输出
每个工具使用不同的格式。将所有结果转换为仓库的发现项格式,然后统一导出 SARIF。格式、字段和验证器:references/findings-format.md;使用 references/validate-findings.mjs 验证;使用 references/to-sarif.mjs 导出。
导入规则:
- 扫描器命中项导入时设为
verdict: "needs_validation",并设置blockers: ["scanner result, not yet triaged"]和validation_plan,其中写明准确的重新运行命令。工具自身的严重性写入evidence[].description;只有在finding-triage确认后,记录才设置实际的severity。例外:已验证的有效密钥导入时设为confirmed。 - 设置
skill: "scanner-pipeline",并在evidence中注明工具名称和规则 ID。 cwe使用规则元数据中的值(semgrep、Brakeman、gosec、checkov 均包含该值)。对于依赖项安全公告,若公告中有 CWE,则使用该值;否则留空,不要猜测。confidence:仅基于静态分析的结果最高设为medium。已验证的密钥和经可达性确认的govulncheck结果可以设为high。
按指纹跨工具去重——使用稳定的根因 ID,不包含行号、严重性或判定结果:
| 类型 | 指纹 | 合并范围 |
|---|---|---|
| 依赖项 | dep:<ecosystem>:<package>:<canonical-id>(存在 CVE 时用 CVE,否则用 GHSA,再否则用 OSV ID) | 同一安全公告的 osv-scanner + trivy + npm audit + Dependabot |
| 密钥 | secret:<detector>:<sha256(value)[:12]> | 跨文件和提交的同一密钥;Betterleaks 和 gitleaks 使用相同的规则 ID |
| 代码 | sast:<cwe>:<file>:<enclosing function or route> | 同一漏洞点的 semgrep + CodeQL + Brakeman |
| IaC | iac:<resource address>:<check family> | 同一资源上的 trivy misconfig + checkov |
| 数据库 | db:<project>:<schema.table>:<lint name> | 同一表上的 advisor + 自定义 semgrep 规则 |
在合并后的记录中保留每个工具的所有证据;修复 Agent 需要这些信息。
抑制文件——.security/suppressions.yml,每个指纹对应一条记录:
- fingerprint: dep:npm:next:CVE-2026-XXXXX
tool: osv-scanner
reason: "Static export; no server runtime. Advisory requires Server Actions."
owner: "@jdoe"
added: 2026-10-26
expires: 2027-01-24 # max 90 days; expired entries resurface the finding
规则:每条记录都必须包含原因、负责人和过期时间(最长 90 天);过期记录会被忽略,发现项也会重新出现;Agent 提议抑制项,但每一项都必须由用户批准。仅在工具支持填写原因和过期时间时,才将记录同步到原生忽略文件——osv-scanner.toml 中带有 ignoreUntil 和 reason 的 [[IgnoredVulns]],以及含 exp:YYYY-MM-DD 的 .trivyignore——并将 suppressions.yml 作为事实来源。被抑制的发现项写入 findings.json 时,设为 rejected,并将 ruled_out_by 指向对应的抑制记录,以免重新运行时再次报告。
5. 集群模式
gh repo list <owner> --limit 1000 --source --no-archived --json nameWithOwner,defaultBranchRef,pushedAt
--source 会排除 fork;--no-archived 会排除已归档的仓库。对每个所有者/组织重复执行。仅扫描密钥时,Betterleaks 可以在一个进程中扫描组织内的所有仓库及其完整历史记录,而不必逐个克隆:betterleaks github --validate=false --analyze=false --redact --silent -o .security/out/fleet-betterleaks.json https://github.com/<org>(需要在环境中设置 GitHub 令牌;--exclude-repo '<org>/*-archive';仅使用 --include=forks 才会扫描 fork)。此命令会提示操作。对每个仓库:浅克隆生产分支(如果有 patch-sweep 第 2 步的结果,则使用其中的分支;否则使用默认分支)——但密钥扫描除外,因为它需要完整历史记录(对 gitleaks 来说,git clone --filter=blob:none 已足够)。并行运行每个仓库的流水线(一次处理少量仓库),将输出写入 .security/out/<repo>/。然后汇总:
| repo | layer | tool | sev | finding | status |
|---|---|---|---|---|---|
| acme/api | db | supabase advisor | (tool: ERROR) | rls_disabled_in_public: public.inbound_emails | needs_validation |
| acme/api | secrets | trufflehog | P0 | verified Stripe live key in commit a1b2c3d (value hash 3f9e…) | confirmed — rotate |
| acme/web | deps | osv-scanner | (tool: CRITICAL) | next 15.5.10 — CVE-2026-XXXXX | needs_validation |
在 finding-triage 设置实际严重性之前,sev 会在括号中显示工具标注的严重性。优先列出 P0 密钥,然后按工具标注的严重性排序。
6. CI 和计划任务
将 references/security-scan.yml 搭建到仓库中,路径为 .github/workflows/security-scan.yml。它会:
- 在每个 PR 上运行 gitleaks、osv-scanner 和 semgrep(gitleaks 扫描 PR 的提交;semgrep 针对基线执行感知差异的扫描);
- 每周执行一次完整扫描(完整 Git 历史、所有规则),并支持通过
workflow_dispatch手动触发; - 可通过
workflow_call由其他仓库调用,因此一份配置即可服务于整个集群; - 在可用时将 SARIF 上传到 GitHub 代码扫描(公共仓库,或启用了 GitHub Advanced Security 的私有仓库);否则始终将 SARIF 保留为工作流产物;
- 将每个 Action 固定到完整提交 SHA,并将每个容器镜像固定到摘要(
cicd-security)。模板中的 SHA 根据上游发布标签于 2026-10-26 查找;请使用 Dependabot 或 Renovate 更新,不要改用标签。
在 CI 中使用 Betterleaks(可选,v2 稳定后启用)。在 gitleaks 旁边添加一个作业:
- 像其他镜像一样,按摘要固定镜像。不要使用
:latest或会变动的:v2标签。 - 在步骤的
env中设置BETTERLEAKS_VALIDATE: "false"和BETTERLEAKS_ANALYZE: "false",并传入--validate=false --analyze=false。绝不要在 CI 中执行验证。 对 fork 的拉取请求执行验证时,任何由 PR 作者植入的密钥都可能被发送到规则调用的任意端点。 - 从基线分支读取
.betterleaks.toml(git show "origin/$GITHUB_BASE_REF:.betterleaks.toml"),并通过--config传入。PR 可以在自己的 head 中修改该文件,从而关闭本应检测问题的规则(cicd-security)。 - 使用密钥中的
--hmac-key(BETTERLEAKS_FINGERPRINT_HMAC_KEY),因为报告产物会离开本机。 - 使用
betterleaks-to-findings.mjs转换,再使用to-sarif.mjs --include-needs-validation,然后上传。v2 发现问题时会以 1 退出:像 gitleaks 步骤一样保留continue-on-error: true。
无法使用代码扫描时,针对每个指纹创建或更新一个 issue(标题 = 指纹,正文 = 不包含密钥值的证据),不要把 SARIF 直接倾倒到评论中。
7. 移交给修复流程
- 先分诊。 每条
needs_validation记录都交给finding-triage,由其判定结论、严重性和处置方式。已验证的 P0 密钥跳过队列,直接进入轮换流程。 - 每个修复 Agent 处理一个发现项。 提供合并后的记录:指纹、每个工具的证据、确切的重新运行命令,以及需要查阅的所属层技能。不要提供整个看板。
- 在修复分支上重新运行指定工具进行确认——使用相同命令和相同配置,并记录修复前后的结果。只有当该工具不再报告此指纹时,发现项才算关闭。
- 绝不要通过修改扫描器配置来让发现项消失。 不得删除规则、缩小路径范围,也不得由修复 Agent 添加抑制规则。若修复 Agent 认为某项发现是误报,应反馈给用户,由用户批准是否添加抑制规则。
输出格式
在仓库根目录编写 scanner-pipeline.md(或为集群中的每个仓库各编写一份):
# Scanner Pipeline — [repo or fleet] — [date]
## Stack detected
[languages, package managers, frameworks, IaC, Supabase y/n, live targets named by user]
## Tools per layer
| layer | tool | version | command | output |
|---|---|---|---|---|
## Schedule
- PR: gitleaks (PR commits), osv-scanner, semgrep (diff-aware)
- Weekly: full history + all layers, [day/time UTC]
- Results: code scanning / artifact + issues
## Suppression policy
`.security/suppressions.yml`; reason + owner + expiry ≤ 90 days; user approves each entry.
## First run
- findings.json: [N] records ([c] confirmed, [n] needs_validation, [r] rejected)
- Roll-up board: [table above]
## Hand-off
[fingerprints sent to finding-triage; P0 secrets sent to rotation]
可选的搭建文件:.github/workflows/security-scan.yml、.gitleaks.toml(在默认配置基础上扩展组织专用规则)、.semgrep/(例如 RLS 规则等自定义规则)、osv-scanner.toml。
边界
- 仅负责编排。深入解读由各层技能负责。
- 不进行主动利用。需要验证的扫描器命中项应交给
web-pentest,并执行其授权步骤。 - 实时扫描:仅扫描自有且已命名的主机,先进行被动扫描,并限制速率。
- 绝不打印、提交或发布密钥值。
- 绝不削弱扫描器配置来通过 CI。
参考资料
- Betterleaks、gitleaks、trufflehog、semgrep、osv-scanner、trivy、checkov、Brakeman、govulncheck、ZAP baseline、nuclei
- Supabase 数据库顾问和 Splinter;
supabase db lint - GitHub — 上传 SARIF
- 灵感来源:@neilsuperduper,2026-10-25(转述)——按层运行真实工具,并将其发现结果交给 Agent 修复;问题 #84