Ruby 專案要做相依套件安全稽核,大多數人第一個想到的就是 bundler-audit,1.27 億次下載算是 Ruby 生態系的標配。不過用久了會發現幾個痛點:要完整的 Ruby 執行環境、要系統 git、啟動慢,而且 exit code 只有兩種,沒辦法分「有漏洞」跟「指令掛了」。

gem-audit 是 Rust 重寫的版本,編譯成單一 binary,Ruby、Bundler、git 都不用,掃描速度快了 15 到 41 倍

與 bundler-audit 的比較

兩者的差異:

gem-auditbundler-audit
語言RustRuby
執行環境單一 binary,零依賴需要 Ruby + Bundler + git
授權MITGPL-3.0
輸出格式text、jsontext、json、junit
Ruby 版本掃描有(含 JRuby、mRuby)
自動修復--fix 內建需額外安裝 bundler-audit-fix
Exit codes4 種(正常/漏洞/錯誤/資料庫過期)2 種(正常/異常)
設定檔.gem-audit.yml(相容 .bundler-audit.yml.bundler-audit.yml
Inline 忽略理由支援 YAML 註解不支援
資料庫管理內建 git 操作(gix)依賴系統 git

效能差異

在 Apple Silicon 上的實測數據:

操作gem-auditbundler-audit倍數
掃描(有漏洞)7.0 ms216.5 ms31x
掃描(無漏洞)16.6 ms250.2 ms15x
啟動時間4.6 ms188.4 ms41x

bundler-audit 的啟動成本來自 Ruby 直譯器、gem 載入和 Bundler 的 LockfileParser。gem-audit 編譯成原生碼,啟動幾乎沒額外開銷。

為什麼 bundler-audit 需要 git

bundler-audit 的漏洞資料庫 ruby-advisory-db 是一個 git repository。下載和更新都呼叫系統的 git 指令。所以在精簡的 Docker image 或沒裝 git 的 CI 環境中,得另外把 git 補上才能用。

gem-audit 改用 Rust 的 gix 直接處理 git 操作,系統沒 git 也照跑。

功能介紹

漏洞掃描

掃描當前目錄的 Gemfile.lock

gem-audit

輸出會列出每個有問題的套件、CVE 編號、嚴重等級和修復建議:

Name: rails
Version: 5.2.0
Advisory: CVE-2023-1234
Criticality: High
URL: https://...
Title: Remote Code Execution
Solution: Update to >= 6.0.0

Ruby 版本掃描

bundler-audit 沒有這個功能。gem-audit 會額外檢查 Gemfile.lock 裡記錄的 Ruby 版本本身是否有已知漏洞,CRuby、JRuby、mRuby 都涵蓋。

不安全來源偵測

Gemfile.lock 裡如果有 http://git:// 這種未加密來源,gem-audit 會發警告。

嚴重等級篩選

只想看高風險以上的:

gem-audit check --severity high

支援的等級:none、low、medium、high、critical。

自動修復

# 預覽修復內容
gem-audit check --fix --dry-run

# 直接修復
gem-audit check --fix

gem-audit 會產生對應的 bundle update 指令,並直接修補 Gemfile.lock。bundler-audit 本身沒有修復功能,得另外裝 bundler-audit-fix gem。

語意化 Exit Codes

Exit Code意義
0安全,無漏洞
1發現漏洞
2執行錯誤
3資料庫過期

bundler-audit 只有 0 和 1,分不出「有漏洞」和「指令掛了」,CI 想做精準錯誤處理就不太順。

設定檔

.gem-audit.yml、bundler-audit 的 .bundler-audit.yml 都吃:

ignore:
  - CVE-2023-1234  # 已評估,不影響我們的使用情境
  - GHSA-xxxx-yyyy-zzzz

max_db_age_days: 7

忽略清單可以加行內註解,順手把為什麼忽略某個 CVE 寫下來。

懶得手動維護忽略清單的話,--write-ignore 會把目前偵測到的漏洞直接寫入設定檔:

gem-audit check --write-ignore

CI 整合

GitHub Actions

- uses: 7a6163/gem-audit-action@v1

Docker

docker run --rm -v $(pwd):/workspace ghcr.io/7a6163/gem-audit:latest check --update

嚴格模式

CI 上比較推薦的組合是 --strict + --fail-on-stale

gem-audit check --update --max-db-age 1 --fail-on-stale --strict

資料庫超過一天沒更新、或有任何解析警告,pipeline 就會失敗。再加 --format json --output results.json 可以把結果存檔做後續分析。

從 bundler-audit 遷移

gem-audit 刻意維持向下相容:照吃 .bundler-audit.yml、用同一份 ruby-advisory-db、CLI 介面風格也一致。

基本上就是裝好 binary、把 CI 裡的 bundle-audit 換成 gem-audit。原本有 .bundler-audit.yml 的話,連設定檔都不用改。

收尾

CI 還在跑 bundler-audit 的 Ruby 專案,gem-audit 算是一個方便的替代品。最實際的差別其實不是速度,是不用 Ruby — multi-stage Docker build 或非 Ruby 的 CI runner 上會差很多。授權是 MIT,要嵌進商業專案也比 GPL-3.0 好處理。