gem-audit — 用 Rust 打造的 Ruby 相依套件安全稽核工具
Ruby 專案要做相依套件安全稽核,大多數人第一個想到的就是 bundler-audit,1.27 億次下載算是 Ruby 生態系的標配。不過用久了會發現幾個痛點:要完整的 Ruby 執行環境、要系統 git、啟動慢,而且 exit code 只有兩種,沒辦法分「有漏洞」跟「指令掛了」。
gem-audit 是 Rust 重寫的版本,編譯成單一 binary,Ruby、Bundler、git 都不用,掃描速度快了 15 到 41 倍。
與 bundler-audit 的比較
兩者的差異:
| gem-audit | bundler-audit | |
|---|---|---|
| 語言 | Rust | Ruby |
| 執行環境 | 單一 binary,零依賴 | 需要 Ruby + Bundler + git |
| 授權 | MIT | GPL-3.0 |
| 輸出格式 | text、json | text、json、junit |
| Ruby 版本掃描 | 有(含 JRuby、mRuby) | 無 |
| 自動修復 | --fix 內建 | 需額外安裝 bundler-audit-fix |
| Exit codes | 4 種(正常/漏洞/錯誤/資料庫過期) | 2 種(正常/異常) |
| 設定檔 | .gem-audit.yml(相容 .bundler-audit.yml) | .bundler-audit.yml |
| Inline 忽略理由 | 支援 YAML 註解 | 不支援 |
| 資料庫管理 | 內建 git 操作(gix) | 依賴系統 git |
效能差異
在 Apple Silicon 上的實測數據:
| 操作 | gem-audit | bundler-audit | 倍數 |
|---|---|---|---|
| 掃描(有漏洞) | 7.0 ms | 216.5 ms | 31x |
| 掃描(無漏洞) | 16.6 ms | 250.2 ms | 15x |
| 啟動時間 | 4.6 ms | 188.4 ms | 41x |
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.0Ruby 版本掃描
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-ignoreCI 整合
GitHub Actions
- uses: 7a6163/gem-audit-action@v1Docker
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 好處理。