gem-audit
Fast, standalone security auditor for Bundler dependencies, rewritten in Rust.
A security auditor for Gemfile.lock inspired by bundler-audit, compiled to a
single static binary with zero runtime dependencies -- no Ruby, no Bundler, no
gem install required.
Features
- Checks for vulnerable versions of gems in
Gemfile.lock. - Checks the Ruby interpreter version against known CVEs.
- Checks for insecure gem sources (
http://andgit://). - Allows ignoring specific advisories via CLI flags or a configuration file.
- Filters by severity threshold (
--severity). - Warns when the advisory database is stale (
--max-db-age). - Strict mode treats parse/load warnings as errors (
--strict). - Semantic exit codes for CI/CD integration (0/1/2/3).
- Prints advisory information (CVE, GHSA, CVSS criticality, solution).
- Supports text and JSON output formats.
- Downloads and updates the ruby-advisory-db automatically.
- Does not require Ruby or Bundler to be installed.
Install
From source
$ cargo install --path .
Build from source
$ git clone https://github.com/user/gem-audit.git
$ cd gem-audit
$ cargo build --release
$ ./target/release/gem-audit --version
Usage
Audit a project's Gemfile.lock:
$ gem-audit
Name: activerecord
Version: 3.2.10
CVE: CVE-2015-7577
GHSA: GHSA-xrr6-3pc4-m447
Criticality: Medium
URL: https://groups.google.com/forum/#!topic/rubyonrails-security/cawsWcQ6c8g
Title: Nested attributes rejection proc bypass in Active Record
Solution: upgrade to '>= 5.0.0.beta1.1', '~> 4.2.5, >= 4.2.5.1', '~> 4.1.14, >= 4.1.14.1', '~> 3.2.22.1'
Vulnerabilities found! (1 unpatched gem)
Update the ruby-advisory-db before checking:
$ gem-audit check --update
Ignore specific advisories:
$ gem-audit check --ignore CVE-2020-1234 GHSA-xxxx-yyyy-zzzz
Audit a specific directory:
$ gem-audit check /path/to/project
Check a custom Gemfile.lock file:
$ gem-audit check --gemfile-lock Gemfile.custom.lock
Output in JSON format:
$ gem-audit check --format json
Output to a file:
$ gem-audit check --format json --output audit-results.json
Only report high and critical vulnerabilities:
$ gem-audit check --severity high
Warn if the advisory database is older than 7 days:
$ gem-audit check --max-db-age 7
Fail in CI if the database is stale:
$ gem-audit check --max-db-age 7 --fail-on-stale
Treat parse/load warnings as errors:
$ gem-audit check --strict
Show remediation suggestions (dry-run, no files modified):
$ gem-audit check --fix
Use in CI (always update the advisory database before checking):
$ gem-audit check --update
A minimal GitHub Actions example:
- name: Audit gems
run: gem-audit check --update
For stricter CI enforcement — fail if the database couldn't be updated or is stale:
- name: Audit gems
run: gem-audit check --update --max-db-age 1 --fail-on-stale
To use the Docker image in GitLab CI, use the CI cache to store the advisory database in a writable directory. This avoids git locking issues that can occur on container overlay filesystems:
audit:
image:
name: ghcr.io/7a6163/gem-audit
entrypoint:
variables:
GEM_AUDIT_DB: "$CI_PROJECT_DIR/.ruby-advisory-db"
cache:
key: ruby-advisory-db
paths:
- .ruby-advisory-db/
script:
- gem-audit check --update
The first run clones the advisory database into the cache. Subsequent runs restore it from cache and fetch only the latest changes.
The Ruby interpreter version from the RUBY VERSION section is also checked:
$ gem-audit
Engine: ruby
Version: 2.6.0
CVE: CVE-2021-31810
Criticality: Medium
URL: https://www.ruby-lang.org/en/news/2021/07/07/...
Title: Trusting FTP PASV responses vulnerability in Net::FTP
Solution: upgrade Ruby to '>= 3.0.2', '~> 2.7.4', '~> 2.6.8'
Vulnerabilities found! (1 vulnerable Ruby version)
Commands
| Command | Description |
|---|---|
check |
Check Gemfile.lock for insecure dependencies (default) |
update |
Update the ruby-advisory-db |
download |
Download the ruby-advisory-db |
stats |
Print ruby-advisory-db statistics |
version |
Print the gem-audit version |
Running gem-audit with no subcommand is equivalent to gem-audit check.
Check Options
| Flag | Description |
|---|---|
-q, --quiet |
Suppress output |
-v, --verbose |
Show detailed advisory descriptions |
-i, --ignore <IDS>... |
Advisory IDs to ignore |
-u, --update |
Update the advisory database before check |
-D, --database <PATH> |
Path to the advisory database |
-F, --format <FORMAT> |
Output format: text (default) or json |
-G, --gemfile-lock <FILE> |
Path to the Gemfile.lock file |
-c, --config <FILE> |
Configuration file (default: .gem-audit.yml) |
-o, --output <FILE> |
Write output to a file instead of stdout |
-S, --severity <LEVEL> |
Minimum severity: none, low, medium, high, critical |
--max-db-age <DAYS> |
Warn if the advisory database is older than DAYS days |
--fail-on-stale |
Exit with code 3 if the database is stale |
--strict |
Treat parse/load warnings as errors (exit code 2) |
--fix |
Show remediation suggestions for vulnerable gems |
Exit Codes
| Code | Meaning |
|---|---|
0 |
No vulnerabilities found |
1 |
Vulnerabilities found |
2 |
Tool error (missing files, parse failures, --strict violations) |
3 |
Advisory database is stale (--fail-on-stale) |
Configuration File
gem-audit supports a per-project configuration file (.gem-audit.yml):
---
ignore:
- CVE-2020-1234
- GHSA-xxxx-yyyy-zzzz
max_db_age_days: 7
ignore:[Array<String>] - Advisory IDs to ignore (CVE, GHSA, or OSVDB).max_db_age_days:[Integer] - Warn if the database is older than this many days. CLI--max-db-ageoverrides this value.
The legacy .bundler-audit.yml file name is also supported for backward
compatibility.
You can specify a custom config file path:
$ gem-audit check --config custom-audit.yml
CLI --ignore flags take precedence over the configuration file.
GitHub Actions
A dedicated gem-audit-action is available on the GitHub Marketplace:
- uses: 7a6163/gem-audit-action@v1
Or use the binary directly in your workflow:
Basic check
name: Security Audit
on:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: 7a6163/gem-audit-action@v1
Severity threshold
Only fail on high and critical vulnerabilities:
- uses: 7a6163/gem-audit-action@v1
with:
severity: high
Strict mode with fresh database
- uses: 7a6163/gem-audit-action@v1
with:
strict: true
max-db-age: 7
fail-on-stale: true
JSON report as artifact
- uses: 7a6163/gem-audit-action@v1
with:
format: json
output: audit-report.json
continue-on-error: true
- uses: actions/upload-artifact@v4
with:
name: audit-report
path: audit-report.json
Docker
A Docker image is available for running gem-audit in any CI environment:
$ docker build -t gem-audit .
$ docker run --rm -v $(pwd):/workspace gem-audit check
The image uses gcr.io/distroless/cc-debian13:debug as the runtime base.
The advisory database is downloaded on first run and stored in /db inside
the container. Mount a volume or use a CI cache to persist it across runs.
CI Examples
GitLab CI:
gem-audit:
image: ghcr.io/7a6163/gem-audit:latest
script:
- gem-audit check
CircleCI:
jobs:
gem-audit:
docker:
- image: ghcr.io/7a6163/gem-audit:latest
steps:
- checkout
- run: gem-audit check
Bitbucket Pipelines:
pipelines:
default:
- step:
name: gem-audit
image: ghcr.io/7a6163/gem-audit:latest
script:
- gem-audit check
Drone CI:
steps:
- name: gem-audit
image: ghcr.io/7a6163/gem-audit:latest
commands:
- gem-audit check
Azure Pipelines:
jobs:
- job: gem_audit
container: ghcr.io/7a6163/gem-audit:latest
steps:
- checkout: self
- script: gem-audit check
Performance
Benchmarked with hyperfine on Apple M-series, comparing against Ruby bundler-audit 0.9.2:
| Benchmark | gem-audit (Rust) | bundler-audit (Ruby) | Speedup |
|---|---|---|---|
| check (unpatched gems) | 7.0 ms | 216.5 ms | ~31x |
| check (secure, no vulns) | 16.6 ms | 250.2 ms | ~15x |
startup (version) |
4.6 ms | 188.4 ms | ~41x |
Run the benchmark yourself:
$ ./benchmarks/bench.sh
Comparison with bundler-audit
gem-audit is a Rust reimplementation inspired by bundler-audit v0.9.x. Both use the same ruby-advisory-db and produce equivalent output.
Feature comparison
| Feature | gem-audit | bundler-audit |
|---|---|---|
| Language | Rust | Ruby |
| Runtime dependencies | None (single static binary) | Ruby, Bundler, Git |
| Advisory database | ruby-advisory-db | ruby-advisory-db |
| Git implementation | gix (pure Rust) | System Git CLI |
| Vulnerability check | Yes | Yes |
| Ruby version check | Yes | No (see ruby_audit) |
| Insecure source check | Yes | Yes |
| Ignore advisories | --ignore + config file |
--ignore + config file |
| Output formats | Text, JSON | Text, JSON |
| Output to file | --output |
--output |
| Severity filtering | --severity |
No |
| Stale DB warning | --max-db-age |
No |
| Fail on stale DB | --fail-on-stale |
No |
| Strict mode | --strict |
No |
| Exit codes | 0 / 1 / 2 / 3 (semantic) | 0 / 1 |
| DB statistics | gem-audit stats |
No |
| Configuration file | .gem-audit.yml |
.bundler-audit.yml |
| Backward-compatible config | .bundler-audit.yml supported |
— |
| Rake integration | No | Yes |
| GitHub Action | gem-audit-action | Community actions |
| Performance | ~7–17 ms | ~190–250 ms |
Why choose gem-audit?
- Zero dependencies -- no Ruby, Bundler, or Git required. Drop a single binary into any CI image.
- 15-41x faster -- finishes in milliseconds, ideal for pre-commit hooks and fast CI pipelines.
- Ruby version scanning -- checks the interpreter version against CVEs, built-in (no extra gem like ruby_audit needed).
- Severity filtering -- only fail on
highorcriticalvulnerabilities with--severity. - Database freshness --
--max-db-ageand--fail-on-staleensure your advisory data is never outdated. - Strict mode -- treat parse/load warnings as errors for stricter CI policies.
- Richer exit codes -- distinguish between vulnerabilities (1), tool errors (2), and stale database (3).
Why choose bundler-audit?
- Rake integration -- useful if your build is driven by Rake tasks.
- Ruby ecosystem -- installs via
gem install bundler-audit, no extra toolchain needed if Ruby is already present.
Architecture
src/
version/ # RubyGems version parsing and comparison
gem_version.rs # Gem::Version semantics (segments, ordering, bump)
requirement.rs # Gem::Requirement with all 7 operators (=, !=, >, <, >=, <=, ~>)
lockfile/ # Gemfile.lock parser
parser.rs # State-machine parser with indentation tracking
ruby_version.rs # Ruby interpreter version parser (engine + patchlevel stripping)
advisory/ # Advisory database
model.rs # Advisory YAML deserialization, vulnerability checking, CVSS
database.rs # Database clone/update via gix, advisory enumeration
scanner.rs # Ties lockfile + database; source, spec & Ruby scanning
configuration.rs # .gem-audit.yml loading and validation
format/ # Output formatters
text.rs # Human-readable text with ANSI colors
json.rs # JSON output with serde_json
main.rs # CLI (clap)
License
MIT. See LICENSE.md for details.