diff --git a/.bundler-audit.yml b/.bundler-audit.yml new file mode 100644 index 0000000..4aaff70 --- /dev/null +++ b/.bundler-audit.yml @@ -0,0 +1,13 @@ +# Advisories deliberately accepted, with the reason and the exit condition. +# CI reads this file by default (`bundler-audit check --update`), so an entry +# here is applied by the same command a contributor runs locally, and removing +# one shows up in a diff. +# +# Add entries only for an advisory that cannot be fixed by an upgrade, e.g.: +# +# ignore: +# # CVE-2026-00000 — rexml, reached only via rubocop (dev-only, never in a +# # consumer's resolution). Upstream fix pending; revisit after standard 2.x. +# - CVE-2026-00000 +# +ignore: [] diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..74a613c --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,73 @@ +name: Advisories + +# Separate from main.yml on purpose. Folded into the Ruby workflow, a red +# Monday would be ambiguous between "an advisory landed" and "a spec broke". +# It is still not a one-cause signal: fetching the advisory database and +# evaluating the lockfile are split into two named steps below precisely so +# the step name says which of the two failed. +# +# Do NOT add this job to branch protection as a required status check while +# the `paths:` filter below is in place. A path-filtered workflow that does +# not trigger reports no status at all, so every PR that leaves the +# dependency files alone would sit on "Expected - Waiting for status" and +# never merge. Drop the filter or add a skip-shim job first. +on: + # Only PRs that actually move the resolution. An advisory published on a + # Tuesday should not turn a README PR red. + pull_request: + paths: + - 'Gemfile' + - 'Gemfile.lock' + - '*.gemspec' + - '.github/workflows/audit.yml' + + push: + branches: + - main + paths: + - 'Gemfile' + - 'Gemfile.lock' + - '*.gemspec' + - '.github/workflows/audit.yml' + + # Catches advisories published against a lockfile nobody touched, which is + # the case no push- or PR-triggered run can see. + schedule: + - cron: '0 6 * * 1' + + # For running the sweep on demand. Note that GitHub disables scheduled + # workflows after 60 days of repository inactivity, and only the "Enable + # workflow" control in the Actions tab brings one back. + workflow_dispatch: + +permissions: + contents: read + +jobs: + audit: + runs-on: ubuntu-latest + name: bundler-audit + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + # Latest stable, not the matrix pin: bundler-audit reads Gemfile.lock + # as text and never loads the gem, so the interpreter is irrelevant + # here and a second hardcoded version would only drift. + ruby-version: 'ruby' + - name: Install bundler-audit + # Deliberately outside the bundle. The scanner has no business inside + # the lockfile it scans, and this keeps the dev bundle's resolution + # independent of the scanner's. Keep the version bound in step with + # CONTRIBUTING.md and MAINTAINING.md. + run: gem install bundler-audit --no-document -v '~> 0.9' + - name: Fetch the advisory database + # Split from the check so a transient clone failure is legible as + # itself rather than as "a gem is vulnerable". One retry, because the + # scheduled run is unattended 52 weeks a year. + run: bundler-audit update || (sleep 15 && bundler-audit update) + - name: Check Gemfile.lock against ruby-advisory-db + run: bundler-audit check diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d994c2..714f5c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,16 @@ Thank you for your interest in contributing to Agentic! This guide will help you - **Testing**: Use RSpec for tests, aim for >90% coverage on new code - **Documentation**: Use YARD comments for all public APIs - **Commit Messages**: Follow [Conventional Commits](https://www.conventionalcommits.org/) +- **Dependencies**: If a change moves `Gemfile.lock` or the gemspec, check it for known advisories first: + + ```bash + gem install bundler-audit -v '~> 0.9' + bundler-audit check --update + ``` + + If your shell can't find `bundler-audit` after installing it, your gem bindir isn't on `PATH`; `gem exec bundler-audit check --update` works regardless on RubyGems 3.5+. + + CI runs the same command on those changes and again every Monday. If it flags an advisory in a gem your change did not touch, say so in the PR rather than bundling an unrelated upgrade into it. An advisory that genuinely cannot be fixed by an upgrade goes in `.bundler-audit.yml` with a reason and an exit condition, where CI honors it and a reviewer can see it. ### Before You Code diff --git a/MAINTAINING.md b/MAINTAINING.md index de4fd1e..777a475 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -117,8 +117,8 @@ Before any release: # Run full test suite bundle exec rake - # Check for security issues - bundle audit + # Check for security issues (gem install bundler-audit -v '~> 0.9') + bundler-audit check --update ``` 2. **Create Release PR**