ci: check Gemfile.lock against ruby-advisory-db - #18
Merged
Conversation
MAINTAINING.md's release checklist has prescribed a bundler-audit run since before this commit, but bundler-audit appears nowhere in the Gemfile, the lockfile, or any workflow. The step only ever worked for a maintainer who happened to have the gem installed globally, which is how 14 advisories reached codenamev#13 without anything reporting them. Adds a separate Advisories workflow: PRs and pushes that move Gemfile, Gemfile.lock or the gemspec, plus a Monday sweep for advisories published against a lockfile nobody touched. The scanner installs outside the bundle so its resolution stays independent of the gem's, and .bundler-audit.yml gives a deliberate exception somewhere reviewable.
codenamev
approved these changes
Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's broken
MAINTAINING.md:121has told maintainers to run a security audit before every release. bundler-audit appears nowhere in theGemfile, nowhere inGemfile.lock, and nowhere in.github/. That step has only ever worked for someone who happened to have the gem installed globally, which means it has been a coin flip dressed as a checklist item.You can see what the coin flip cost in #13: fourteen advisories across faraday, uri, concurrent-ruby, addressable and rexml, sitting in the lockfile, surfaced only because somebody typed
bundler-auditby hand. The faraday ones were the ones that mattered, since faraday is ruby-openai's transport and therefore on the path of every LLM call this gem makes.What this does
Adds
.github/workflows/audit.yml, a separate workflow that runsbundler-auditagainstGemfile.lock:Gemfile,Gemfile.lock, the gemspec, or the workflow itselfworkflow_dispatchPlus
.bundler-audit.yml, empty, so that an advisory which genuinely can't be fixed by an upgrade has one reviewable place to be recorded rather than living in someone's shell history. And two doc lines pointing at the same command.Decisions worth arguing with
The scanner installs outside the bundle.
gem install bundler-audit -v '~> 0.9', nobundle exec. A lockfile scanner has no business inside the lockfile it scans: put it in theGemfileand the day bundler-audit capsthor(a runtime dependency of this gem, at~> 1.2) your test job goes red for a reason that has nothing to do with the gem.Its own workflow file, not a job in
main.yml. A workflow-levelschedule:fires every job in the file, so folding this intomain.ymlwould have meant running 610 specs every Monday and getting a red X that could mean either "an advisory landed" or "a spec broke."Fetching the database and checking the lockfile are separate steps.
bundler-audit updatehard-exits on a failed clone, so a github.com blip in an unattended Monday run would otherwise be indistinguishable from a real finding. Split, the step name tells you which. The fetch gets one retry; the check gets none, because a check failure is the point.The
paths:filter is load-bearing. Without it, an advisory published on a Tuesday morning reddens every open PR, including ones that never touched a dependency, and the fix a contributor is told to make is an unrelated upgrade they didn't sign up for. With it, only dependency PRs can be blocked.Limitations, stated up front
paths:filter is there. A path-filtered workflow that doesn't trigger reports no status, not a passing one, so a requiredAdvisories / bundler-auditwould leave every README PR stuck on "Expected, waiting for status" forever. There's a comment in the workflow saying so. (Not live today:branches/main/protectionreturns 404 andrulesetsreturns[], so main has no protection at all. Which also means this is a signal, not an enforced gate.)Gemfile.lockis excluded from the packaged gem, so what's audited here is your dev and CI resolution, not what anyone installing agentic resolves.addressableandrexmlfrom Patch 14 security advisories with conservative lockfile bumps #13 reach the lock only through webmock and rubocop and never reach a consumer at all. The runtime-transitive subtree (faraday, uri, concurrent-ruby via ruby-openai) does overlap, so it's an early warning there, and nothing stronger.Limitations 1 through 3 all argue the same thing, which is that this is the belt and not the braces. #17 is the actual fix: Dependabot alerts are disabled on this repo, and two checkboxes in Settings buy you a signal with none of the failure modes above. If you only do one, do that one.
Verification
Run on Ruby 4.0.6 against
upstream/mainat 99a9167:.bundler-audit.ymlparses and is honored ({"ignore" => []}). The workflow YAML parses; triggers resolve topull_request/push/schedule/workflow_dispatchand the five steps are as written. No Ruby code is touched, sorspecandstandardrbare unaffected; the CI run on this PR will exercise the new workflow against itself, since the workflow file is in its ownpaths:list.Related
Panel
Four adversarial reviewers were run against the diff with instructions to refute it, twice for the three who objected. The first round killed the original design outright: it had been a job bolted into
main.ymlwithbundler-cache: true, bundler-audit added to theGemfile, a workflow-level cron that also ran the specs, and a second hardcoded3.2.4. Almost none of that survived.buildtoo, the duplicated Ruby pin,bundler-cacheinstalling 75 gems to read one text file, CONTRIBUTING contradicting the offline-rakerationale. All fixed. Round 2: he read bundler-audit'scli.rband showed that the--ignore CVE-...flag I'd documented is ephemeral, so a contributor would go green locally and stay red in CI, recreating the exact rot this PR fixes. Fixed by committing.bundler-audit.ymland rewriting the doc line.MAINTAINING.mdwas the real justification and I'd buried it. He was right and it now leads. Round 2 caught that my ownpaths:fix had armed the required-check deadlock, which is limitation 1 above and would not otherwise be in this PR. He also traced thataddressableandrexmlnever reach a consumer, which is why limitation 4 is worded the way it is.timeout-minutesandpermissions: contents: read; he withdrew his advisory-DB caching objection on the argument that a cached advisory database is a stale one. Round 2: the version bound~> 0.9existed in CI but not in either doc,gem installalone doesn't put the binary on a human'sPATH, and the header comment promised a one-cause signal the file couldn't keep. All three fixed, the last by splitting fetch from check and rewriting the comment to be true.git ls-filesto confirmGemfile.lockisn't packaged, and greppedlib/andexe/for any runtime code reading the lockfile before concluding no saved agent or plan can be affected.One honesty note about the protocol: round-two objections were fixed rather than sent back for a third adjudication, so no expert has signed off on the final diff verbatim. Each round-two fix was the one that expert specified, which is why I think that's defensible, but you're reviewing the argument, so you should know where it stops.
Two corrections the panel made to claims I'd have otherwise shipped:
bundle auditwas never a wrong spelling (bundler-audit ships bothbundle-auditandbundler-auditexecutables, so bundler resolves it), and--version '~> 0.9'versus a baregem installis a real divergence, not a cosmetic one.