Dawarich 安全扫描实践:从 CI 流水线到本地全量 Git 历史的完整指南
【免费下载链接】dawarichYour favorite self-hostable alternative to Google Timeline (Google Location History)项目地址: https://gitcode.com/GitHub_Trending/da/dawarich
导读:Dawarich 是一个自托管的 Google Timeline(Google 位置历史)替代方案,代码库以 Rails(Ruby)为核心。为了保证"自托管 ≠ 自担全部风险",项目在 SECURITY-SCANNING.md 中沉淀了一套分层安全扫描体系:CI 侧用 Brakeman、bundler-audit、Semgrep、gitleaks 与 Trivy 覆盖 SAST、依赖漏洞、密钥泄漏与容器镜像漏洞,本地侧则提供全量 Git 历史密钥扫描与逐条可复现的扫描命令。读完本文,你将掌握这套扫描矩阵的触发时机、门禁策略(Report-only vs Blocking)、CI 工作流实现细节,以及如何在任意一台机器上复跑全部扫描器,为自己的 Rails/Docker 项目搭建同样的防线。
一、整体设计:三层防线与三种触发时机
Dawarich 的安全扫描由 GitHub Actions 自动驱动,核心目标是在合入代码之前尽可能发现四类问题:
- 应用代码自身的安全缺陷(SQL 注入、XSS、批量赋值、不安全跳转等 Rails 常见弱点)——由 SAST 类工具负责;
- 依赖链中的已知漏洞(
Gemfile.lock中的易受攻击 gem)——由 bundler-audit 负责; - 密钥/凭据意外入库(API Key、Token 等敏感信息出现在 diff 或历史提交中)——由 gitleaks 负责;
- 容器产物与基础设施配置缺陷(Docker 镜像内操作系统/库的 CVE、Dockerfile/IaC 配置不当)——由 Trivy 负责。
根据 SECURITY-SCANNING.md 的说明,扫描在三种场景下自动触发:
- 每次 Pull Request:变更进入主干前的第一道闸门;
- 每次 push 到
master与dev分支:主干/开发分支的持续守护; - 每周定时任务(周一):即使本周没有代码变更,也会用最新的漏洞库重新审视代码与镜像。
这一设计意味着漏洞情报(advisory 数据库、规则集)每周都会刷新,且不需要任何开发动作即可保持"最新基线"。
最小权限原则:工作流声明了
permissions: contents: read,除上传 SARIF 结果所需的security-events: write外,不申请任何多余权限。除 GitHub 内置的GITHUB_TOKEN外,不需要任何额外机密,第三方 Action 全部以 commit SHA 固定版本,避免供应链投毒(详见下文 CI 实现)。
二、CI 扫描矩阵:六项任务的角色与门禁
文档用一张表明确了六个扫描任务的定位,这里逐项展开说明其职责边界:
| 工作流 | 任务 | 工具 | 捕获范围 | 门禁级别 |
|---|---|---|---|---|
security.yml | brakeman | Brakeman | Rails SAST(SQL 注入、XSS、批量赋值、不安全跳转等) | 仅报告 |
security.yml | bundler-audit | bundler-audit | Gemfile.lock中已知漏洞 gem(advisory 数据库在任务内刷新) | 阻断 |
security.yml | semgrep | Semgrep(p/ruby、p/rails、p/secrets、p/owasp-top-ten四套规则) | SAST + 密钥模式 | 仅报告 |
security.yml | gitleaks | gitleaks | PR/push 的 diff 中的密钥 | 阻断 |
trivy.yml | image | Trivy(镜像扫描) | 构建出的 Docker 镜像中的 OS/库 CVE(有修复的 HIGH/CRITICAL) | 仅报告 |
trivy.yml | config | Trivy(配置扫描) | Dockerfile / IaC 配置错误 | 仅报告 |
2.1 为什么用两套 SAST?Brakeman + Semgrep 互补
Brakeman是 Rails 生态事实标准的静态分析工具。Dawarich 将它与 bundler-audit 一起声明在 Gemfile 的开发/测试/staging 组中(gem 'brakeman', require: false、gem 'bundler-audit', require: false),专门针对 Rails 框架的惯用 API 做深度检查——例如params直接拼进 SQL、redirect_to使用外部输入、update_all/create_with类批量赋值路径等。
Semgrep则作为"广谱补充":它不绑定 Rails 框架,用p/ruby、p/rails、p/secrets、p/owasp-top-ten四套公开规则集同时覆盖通用 Ruby 代码质量、Rails 最佳实践、密钥模式(如硬编码的 AWS Key、GitHub Token 等)以及 OWASP Top Ten 常见漏洞。两套工具规则来源不同、视角互补,同一类问题可以用不同模式交叉验证。
2.2 唯一默认"硬门禁":bundler-audit 与 gitleaks
bundler-audit直接读取Gemfile.lock,与 RubyGems Advisory Database 比对。由于 Dawarich 是一个长期演进、依赖较多的 Rails 项目,其 security.yml 中的实现是:
bundle exec bundle-audit update bundle exec bundle-audit check先更新本地 advisory 数据库再检查——这正是文档强调的"advisory DB refreshed in-job":每次运行都拉取最新漏洞情报,而不是依赖某个缓存的旧库。一旦发现已知漏洞 gem,任务直接失败,阻断合并。
gitleaks则守护"密钥不落库"这条红线。CI 中仅扫描本次变更的 diff(详见下文第四节),发现任何疑似密钥即以失败收场。
2.3 镜像与基础设施:Trivy 双任务
trivy.yml的image任务针对构建产物(本地docker build出来的镜像)执行漏洞扫描:
trivy image \ --scanners vuln \ --severity HIGH,CRITICAL \ --ignore-unfixed \ --format sarif --output /work/trivy-image.sarif \ --exit-code 0 \ dawarich:scan三个关键参数的含义:
--severity HIGH,CRITICAL:只关注高危与严重级别,过滤掉大量低优先级噪声;--ignore-unfixed:只看有修复版本可用的 CVE——没有修复方案的漏洞无法通过升级解决,先不阻塞交付;--exit-code 0:本轮不阻断(Report-only),但结果照常上传到 Security 标签页。
config任务则用trivy config /work扫描仓库本身(主要是 docker/Dockerfile 等 Docker/IaC 文件),检查诸如基础镜像版本过旧、特权指令、危险环境变量等配置类问题。
值得一提的细节:Trivy 固定使用
ghcr.io/aquasecurity/trivy:0.65.0容器镜像(trivy.yml 的env.TRIVY_IMAGE),并通过TRIVY_USERNAME/TRIVY_PASSWORD环境变量传递凭据拉取私有层;Docker 守护进程通过挂载/var/run/docker.sock提供给容器内的 Trivy,从而可以直接扫描宿主机构建出的本地镜像dawarich:scan。
三、门禁分级策略:先"采集信号",再"收紧闸门"
文档明确指出,当前阶段四分之三的任务都是 Report-only(Brakeman、Semgrep、Trivy 的 image 与 config),只有 bundler-audit 和 gitleaks 是阻断性的。这是有意为之的设计决策:
"This is intentional for the first pass — gather signal before gating merges."
原因很务实:一个成熟代码库在第一次接入 SAST 时,历史存量问题可能非常多,如果直接全部设为阻断,所有 PR 都会红掉,团队会被迫在"修历史债"和"合入新功能"之间二选一,反而阻碍扫描体系的落地。先以 Report-only 运行一段时间,把真实告警量、误报率摸清楚,再逐步升级为门禁。
文档同时给出了从 Report-only 升级为 Blocking 的具体操作路径,这对应到源码中:
- Brakeman / Semgrep / Trivy config:在 security.yml 与 trivy.yml 中删除对应 step 上的
continue-on-error: true; - Trivy image:将
--exit-code 0改为--exit-code 1,让"存在可修复的 HIGH/CRITICAL CVE"直接导致任务失败。
升级节奏可以按工具逐个推进:先放开风险最高的(如 Trivy image 的可修复高危 CVE),再根据误报情况逐步覆盖其余任务。
四、CI 实现细节:从源码看安全实践如何落地
4.1 第三方 Action 全部固定到 commit SHA
打开 security.yml 可以看到,每一个uses:都带上了完整 SHA 与版本注释,例如:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0SHA 固定意味着:即便上游仓库被恶意篡改或 Action 作者账号被盗,CI 也只会执行当初审核过的那个 commit的代码。这是供应链安全的基础实践,文档中"All third-party actions are pinned to a commit SHA"的承诺在源码层面得到了完整落实。
4.2 SARIF 统一汇入 Security → Code scanning
Brakeman、Semgrep、Trivy(image 与 config)四个任务都会产出 SARIF 格式报告,并通过github/codeql-action/upload-sarif上传(if: always()保证即使扫描失败也会上传报告),并且各自带独立category(如brakeman、semgrep、trivy-image、trivy-config),在仓库的Security → Code scanning标签页按类别分组展示。这样所有工具的告警集中在一个入口,方便统一分类、指派与跟踪。
4.3 gitleaks 在 CI 中只扫 diff 的取舍
这是一个容易被忽视但很关键的设计。CI 中的 gitleaks 任务(security.yml)有两个值得注意的点:
- 触发范围:
if: github.event_name == 'pull_request' || github.event_name == 'push'——只有 PR 和 push 时运行,定时任务不跑gitleaks; - 扫描对象:通过
fetch-depth: 0拉取完整历史后,gitleaks-action 默认仍以本次变更的 diff为扫描范围。
原因写在任务注释里:CI 只扫 diff,是为了避免反复标记已知的历史提交。这个仓库是公开的,如果每周定时全量扫历史,每次都会命中多年前的历史密钥,产生大量无法通过"删掉提交"解决的噪声。全量历史扫描被有意放到本地手动执行(详见第六节),因为那才是有意为之的"排雷"动作,而不是 CI 的例行公事。
4.4 并发与失败取消
security.yml与trivy.yml都声明了:
concurrency: group: security-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true同一分支上连续多次 push 时,旧的扫描会被新的一次取代,避免排队积压和资源浪费。
五、依赖与基础镜像的持续更新:Dependabot
安全扫描只负责"发现",修复依赖要靠 .github/dependabot.yml 的自动化更新。它覆盖三个维度:
| 生态 | 目录 | 频率 | 说明 |
|---|---|---|---|
bundler(RubyGems) | / | 每周一 | minor/patch 分组批量更新,限制 10 个并发 PR |
docker | /docker | 每周一 | 基础镜像更新,限制 5 个 PR |
github-actions | / | 每周一 | 保持 SHA 固定版本不过期,限制 5 个 PR |
三个更新目标都打在dev分支,便于先在开发分支验证再合入主干。
其中 docker 生态的ignore规则非常值得注意——它主动禁止Ubuntu 基础镜像的 major/minor 升级:
ignore: - dependency-name: 'ubuntu' update-types: - 'version-update:semver-major' - 'version-update:semver-minor'原因注释写得很清楚:Dockerfile 中的mbgl_libs阶段依赖 Ubuntu 24.04 预编译的@maplibre/maplibre-gl-native二进制,它链接的是libicuuc/libicudata/libicui18n的.so.74版本,而更新的 Ubuntu 不再提供 ICU 74。也就是说:基础镜像不能盲目升级,Dependabot 的自动化必须在兼容性约束下运行——这本身也是依赖管理安全实践的一部分("已知的破坏性升级比缓慢的 CVE 更危险")。
六、全量 Git 历史密钥扫描(本地执行)
这是 SECURITY-SCANNING.md 中最重要的实操章节,也是公开仓库必做的"体检"。
6.1 为什么 CI 扫不到历史密钥
CI 的 gitleaks 只扫 diff。而公开仓库的历史是不可磨灭的:一个多年前提交的密钥,即使后来从文件中删除,只要曾出现在任一历史 commit 中,就仍然暴露在互联网上。文档强调:
"a secret committed years ago remains exposed even if later removed"
所以必须在本地对完整 git 历史执行一次扫描,找出所有历史提交中残留的密钥。
6.2 全量扫描命令
docker run --rm -v "$(pwd):/repo" -w /repo \ ghcr.io/gitleaks/gitleaks:latest \ detect --source=. --redact -v参数拆解:
-v "$(pwd):/repo" -w /repo:把当前目录挂载进容器并设为工作目录;detect --source=.:以当前目录为 git 仓库根,扫描全部提交历史;--redact:输出中把密钥值打码,避免终端日志二次泄露;-v:verbose,显示每个命中的详情。
6.3 输出 SARIF 报告便于分诊
如果需要把结果沉淀成报告文件、导入缺陷跟踪系统或交给团队逐一处理,使用:
docker run --rm -v "$(pwd):/repo" -w /repo \ ghcr.io/gitleaks/gitleaks:latest \ detect --source=. --redact --report-format sarif --report-path gitleaks-history.sarif与 CI 上传到 Security 标签页的格式一致,可以直接用同一套流程管理。
6.4 命中后的处理流程
文档给出了明确的处置链路:
- 轮换(Rotate):凡是历史中出现的密钥,一律视为已泄露——即使看起来从未被使用。修改对应的 API Key、Token、密码,使其立即失效;
- 抑制(Suppress):确认已轮换、且不需要再被报出的历史命中,通过
.gitleaks.toml的 allowlist 按finding fingerprint精确豁免,避免每次全量扫描都重复报警。注意:当前仓库根目录下并没有现成的.gitleaks.toml文件,这是文档建议的"可选动作",需要时自行创建。
七、在本地复跑全部扫描器
SECURITY-SCANNING.md 提供了完整的本地复现命令,适合在 CI 之外做"提交前自查"或"CI 之外环境的巡检"。逐条说明如下:
# 1) Rails SAST —— 与 CI 的 brakeman 任务一致 bundle exec brakeman --no-progress # 2) 依赖漏洞 —— 先更新 advisory 库再检查 Gemfile.lock bundle exec bundle-audit update && bundle exec bundle-audit check # 3) Semgrep —— 四套规则集与 CI 完全一致 pip install semgrep && semgrep scan \ --config p/ruby --config p/rails --config p/secrets --config p/owasp-top-ten # 4) 镜像漏洞 —— 先本地构建再扫 docker build -f docker/Dockerfile -t dawarich:scan . trivy image --ignore-unfixed --severity HIGH,CRITICAL dawarich:scan # 5) 基础设施配置 trivy config .几点补充说明:
brakeman与bundler-audit来自 Gemfile 的development, :test, :staging组,本地开发环境bundle install后即可直接使用;- Semgrep 通过
pip install安装,参数与 CI 中 security.yml 的--config p/ruby --config p/rails --config p/secrets --config p/owasp-top-ten完全对齐,保证本地结果与 CI 可比; - Trivy 命令需要本机已安装
docker与trivy,且镜像构建耗时较长(Dawarich 的 Dockerfile 包含多阶段构建与资产预编译),适合在发布前或 CI 环境外做增量巡检时使用; trivy config .不依赖构建,直接对仓库中的 Dockerfile 等 IaC 文件做静态检查,速度最快。
八、落地经验小结
Dawarich 的安全扫描体系提供了一套可迁移的模板,核心经验可以总结为四点:
- 分层而非单点:SAST(Brakeman + Semgrep)、依赖审计(bundler-audit)、密钥扫描(gitleaks)、容器扫描(Trivy)各司其职,覆盖从源码到产物的完整链路;
- 渐进式门禁:先用 Report-only 采集信号、摸清误报率,再按工具逐个收紧为 Blocking——既保安全底线,又不阻塞开发;
- 自动化与人工分工:CI 例行扫 diff,全量历史"排雷"放到本地按需执行,避免定时任务制造噪声;
- 供应链安全贯穿始终:Action 固定 SHA、最小权限、Dependabot 每周更新,同时在兼容性约束下理性控制基础镜像升级。
对于任何一个自托管、公开源码的 Rails 项目,这套"CI 例行扫描 + 本地全量排雷 + 自动化依赖更新"的组合,都是一份可以直接照搬的安全基线。
【免费下载链接】dawarichYour favorite self-hostable alternative to Google Timeline (Google Location History)项目地址: https://gitcode.com/GitHub_Trending/da/dawarich
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考