From 6191464f0df1f1b0e56aaa4ab1963c7d20828ae8 Mon Sep 17 00:00:00 2001 From: "developerz-ai[bot]" <286432749+developerz-ai[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 10:43:22 +0000 Subject: [PATCH 1/3] ci: audit every matrix resolution against the advisory database The audit job only checked the root Gemfile.lock, while the CI matrix resolves gemfiles/standalone.gemfile and gemfiles/rails.gemfile fresh on every run, so advisory-affected releases of rails/view_component/ phlex-rails/activesupport reached the matrix jobs unchecked and the green audit job overstated its coverage. Each matrix job now audits the lockfile it just resolved, before its tests run, so every matrix entry's dependency set is checked against the Ruby advisory database. The root-lockfile audit is unchanged, and no matrix lockfile is committed (gemfiles/*.lock stays ignored). Dz-Task-Id: tsk_1f6ffbd245447853403e2e4a425e5176 --- .github/workflows/ci.yml | 34 +++++++++++++++++++++++++++++----- AGENTS.md | 2 +- CHANGELOG.md | 4 ++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74485c3..3fb359f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,8 +92,16 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - - name: Compile + RSpec (spec/gem without Rails) - run: bundle exec rake spec:standalone + # The matrix gemfiles have no lockfile of their own: setup-ruby's bundle + # install resolved this entry and wrote one next to the gemfile. Audit + # that resolution, not just the root Gemfile.lock. The audit shares the + # specs step (no extra job or step): it runs first, so an advisory fails + # this entry even when the specs fail. + - name: Audit the resolved gems, then Compile + RSpec (spec/gem without Rails) + run: | + gem install --no-document --conservative bundler-audit + bundle-audit check --update --gemfile-lock gemfiles/standalone.gemfile.lock + bundle exec rake spec:standalone native: # x86_64 = SSE2 path, aarch64/arm64 = NEON path. @@ -194,10 +202,18 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true + # The matrix gemfiles have no lockfile of their own: setup-ruby's bundle + # install resolved this entry and wrote one next to the gemfile. Audit + # that resolution, not just the root Gemfile.lock. The audit shares the + # specs step (no extra job or step): it runs first, so an advisory fails + # this entry even when the specs fail. # spec:standalone compiles first: on JRuby that builds the Java escaper # jar (ext/java), on TruffleRuby extconf.rb no-ops (pure Ruby is faster). - - name: RSpec (spec/gem without Rails) - run: bundle exec rake spec:standalone + - name: Audit the resolved gems, then RSpec (spec/gem without Rails) + run: | + gem install --no-document --conservative bundler-audit + bundle-audit check --update --gemfile-lock gemfiles/standalone.gemfile.lock + bundle exec rake spec:standalone # On JRuby this builds and renders from the -java gem (jar inside); on # TruffleRuby the pure-Ruby gem. - name: Packaged gem installs and renders @@ -233,7 +249,15 @@ jobs: with: ruby-version: .ruby-version bundler-cache: true - - run: bundle exec rake compile + # The matrix gemfiles have no lockfile of their own: setup-ruby's bundle + # install resolved this entry and wrote one next to the gemfile. Audit + # that resolution, not just the root Gemfile.lock. The audit shares the + # compile step (no extra job or step): it runs first, before the specs. + - name: Audit the resolved gems, then compile + run: | + gem install --no-document --conservative bundler-audit + bundle-audit check --update --gemfile-lock gemfiles/rails.gemfile.lock + bundle exec rake compile - name: Resolved versions run: bundle list | grep -E ' (rails|view_component|phlex-rails|rspec-rails|sqlite3) ' - name: RSpec (integration specs) diff --git a/AGENTS.md b/AGENTS.md index b7b6d7f..bcf6706 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -122,7 +122,7 @@ Goal: the repo needs no hand maintenance except major-version dependency upgrade |---|---| | Maintainer agent | Repo managed by [developerz.ai](https://developerz.ai) (policy: `.dz/maintainer/maintainer.yml`): triages issues, reviews and merges every PR (contributors, members, bots) once its author marks it ready (out of draft), it is reviewed and `CI OK` is green; daily `health` + weekday `release-due` checkups and a monthly `improve` pass (`.dz/briefs/`: canary fixes, dependency majors, speed with benchmark evidence, correctness, docs drift); opens release PRs; breaking changes escalate to the owner. Sibling projects such as [wurk](https://github.com/sebyx07/wurk) run the same model | | Dependencies | Dependabot (`.github/dependabot.yml`) opens grouped weekly PRs after a 14-day cooldown (security updates skip it); the agent reviews and merges on green — the only merger | -| CI | `.github/workflows/ci.yml`; branch protection requires only the aggregate `CI OK` job: RuboCop, bundle-audit, full suite + Brakeman, gem specs across the Ruby matrix (generated from maintained CRuby versions), native builds on Linux/macOS arm64, `-Werror` with gcc and clang, ASAN/UBSAN (`use_sigaltstack=0`), pure-Ruby fallback, JRuby (Java escaper jar + `-java` gem) and TruffleRuby (pure Ruby) via `gemfiles/standalone.gemfile`, Rails integration per `gemfiles/rails.gemfile`, `bin/gem-smoke` packaging on plain RubyGems. Every job required; no `continue-on-error` | +| CI | `.github/workflows/ci.yml`; branch protection requires only the aggregate `CI OK` job: RuboCop, bundle-audit (the root `Gemfile.lock` **and** every matrix resolution — `gemfiles/standalone.gemfile`, `gemfiles/rails.gemfile` — checked against the Ruby advisory database, in the matrix jobs themselves), full suite + Brakeman, gem specs across the Ruby matrix (generated from maintained CRuby versions), native builds on Linux/macOS arm64, `-Werror` with gcc and clang, ASAN/UBSAN (`use_sigaltstack=0`), pure-Ruby fallback, JRuby (Java escaper jar + `-java` gem) and TruffleRuby (pure Ruby) via `gemfiles/standalone.gemfile`, Rails integration per `gemfiles/rails.gemfile`, `bin/gem-smoke` packaging on plain RubyGems. Every job required; no `continue-on-error` | | Canary | `.github/workflows/canary.yml`, monthly on the 1st, not gating: Ruby head and Rails edge surface upstream breaks before a release ships them; the monthly `improve` checkup fixes what it finds | | Benchmarks | `.github/workflows/benchmark.yml`, non-gating: every push to `main` and PR renders a table into the job summary and uploads JSON; PRs get a sticky comment with the delta vs `main`. Set repo variable `BENCH_RUNNER` for a stable self-hosted runner; README holds only a dated release snapshot | | Releases | `.github/workflows/release.yml`: merging a `version.rb` bump to `main` (no `v` tag yet) runs the tests and `rake smoke`, builds the `-java` gem on JRuby (jar inside), publishes **both** the C-extension gem and the `-java` gem via RubyGems trusted publishing (environment `release`), then creates the tag and the GitHub release from `bin/changelog-notes`. The release PR is opened by the agent or the `release` skill (`docs/release.md`) | diff --git a/CHANGELOG.md b/CHANGELOG.md index f02150a..931beb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ Entries before 1.6.6 were reconstructed from `git log` and RubyGems release date ## [Unreleased] +### Changed + +- CI now audits every matrix entry's resolved dependency set against the Ruby advisory database, not only the root `Gemfile.lock`: the gem-specs, engine and Rails matrix jobs run `bundle-audit` on the lockfile their own unlocked gemfile resolves (`gemfiles/standalone.gemfile.lock`, `gemfiles/rails.gemfile.lock`), and an advisory fails that job. + ### Fixed - Appending a renderer's own output buffer to itself (`plain(current_output)`, `component(current_output)`) could read freed memory with the C extension: growing the buffer while its own bytes were still being read moved them out from under the read, which aborted under ASAN in the sanitizer CI job and could crash or corrupt output anywhere. Escaped and raw appends (`r2h_append_escaped`, `r2h_append_raw`) now duplicate the string first when it is the buffer; the pure-Ruby renderer was never affected, and output stays byte-identical (pinned by the new "can append the output buffer to itself" example, which runs against both implementations). From 65ea804ad6c2fdad282489dc65d72f3488b94a82 Mon Sep 17 00:00:00 2001 From: "developerz-ai[bot]" <286432749+developerz-ai[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 11:20:03 +0000 Subject: [PATCH 2/3] chore(babysit): land .bundler-audit.yml (+4 more) Follow-up intent: Fix failing CI on sebyx07/ruby2html#23 Dz-Task-Id: tsk_52828ebaf217b66bcc4b9460c4195407 --- .bundler-audit.yml | 60 ++++++++++++++++++ AGENTS.md | 3 +- CHANGELOG.md | 3 + Gemfile.lock | 134 ++++++++++++++++++++--------------------- gemfiles/rails.gemfile | 5 ++ 5 files changed, 137 insertions(+), 68 deletions(-) create mode 100644 .bundler-audit.yml diff --git a/.bundler-audit.yml b/.bundler-audit.yml new file mode 100644 index 0000000..05fa15d --- /dev/null +++ b/.bundler-audit.yml @@ -0,0 +1,60 @@ +# bundler-audit configuration, read by every `bundle-audit check` run: the root +# `Gemfile.lock` audit and the matrix audits of `gemfiles/*.gemfile.lock`. +# +# Every advisory below is one that upstream patched only in Rails 8.0.5.2 and +# 8.1.4.1 - there is no patched release for the older lines the CI matrix +# deliberately tests: +# +# * Rails 7.2, the demo app's floor (`gemfiles/rails.gemfile`); +# * Active Support 7.2.x, the newest release that runs on Ruby 3.1, the +# gem's Ruby floor (`gemfiles/standalone.gemfile`). +# +# Without this list those matrix entries can never be green: the audit would +# keep failing on advisories nobody can fix on the line under test, and a +# permanently red gate is a gate nobody reads. Every maintained line (the root +# lock on Rails 8.1.4.1, Rails 8.0, Rails 8.1, Ruby >= 3.3, JRuby, TruffleRuby) +# is patched, so nothing here hides a fixable vulnerability there. +# +# Rules for this list: +# * only advisories that have no patched release for a line CI tests; +# * drop an entry as soon as upstream ships a patch for that line - re-run +# `bundle-audit check --update` after every Rails or Ruby bump; +# * an entry must never hide a fixable vulnerability: keep the lockfile and +# the matrix resolutions on patched releases. +ignore: + # Action Mailbox: signature replay in the Mailgun ingress. + - GHSA-5957-c7qm-m469 + # Action Pack: restoration of destroyed sessions in the CacheStore. + - GHSA-2586-8ghr-69fj + # Action Pack: missing SameSite attribute on session cookies. + - GHSA-gmj8-hvj3-49mw + # Action Pack: cache poisoning through content negotiation. + - GHSA-r9jg-fmfx-2gv6 + # Action Pack: DoS in Action Controller live streaming. + - GHSA-rwvh-fw6r-pwvp + # Action Pack: strong parameters bypass in Parameters#each_key. + - GHSA-wm3x-v3qw-4jh3 + # Action Pack: session information leak through the Active Storage proxy. + - GHSA-x6mj-8jx9-qhqv + # Action View: XSS in the text helpers. + - GHSA-2p2f-4rq4-gjwc + # Action View: XSS in the tag helpers. + - GHSA-68p9-83h6-5wpj + # Action View: XSS in strip_tags. + - GHSA-rq8q-p985-fjq7 + # Action View: XSS in the highlight helper. + - GHSA-v6rj-xm57-3p59 + # Active Storage: local file disclosure in video previews and analysis. + - GHSA-3f4q-8x5m-76v8 + # Active Storage: unsigned Content-Type in GCS direct uploads. + - GHSA-3w93-6hmw-whh4 + # Active Storage: arbitrary file write and read in image analysis. + - GHSA-f3w6-w445-fwh9 + # Active Storage: circumvention of the file size limits. + - GHSA-hfr4-8rjc-97m2 + # Active Storage: DoS in filename handling. + - GHSA-p2pm-g2h6-7j5j + # Active Storage: arbitrary file read and remote code execution. + - GHSA-v924-7rp8-4mxh + # Active Support: DoS in safe_constantize. + - GHSA-8474-xrp6-wh69 diff --git a/AGENTS.md b/AGENTS.md index bcf6706..2e3c41b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,6 +113,7 @@ Writing ruby2html templates/components (here or in a user app)? Load [`llms.txt` 11. Public API used downstream — `Render.new(ctx, &blk)`, `#render`, `#plain`, `#component`, tag methods, `Ruby2html.escape_html`, `Ruby2html.native?`, `Ruby2html::Component`, `RailsHelper#html`, `ComponentHelper#html` / `#__ruby2html_renderer__`, the `.rb` handler, `HtmlBeautifierMiddleware` — breaking changes need a CHANGELOG entry ("Upgrading to 2.0") and a major bump. 12. Style: rubocop-rails_config, single quotes, `# frozen_string_literal: true`, nested modules, Rails-style indented `private` methods. C: gnu99, no warnings under `-Wall -Wextra`. 13. Never commit `lib/ruby2html/*.so`, `*.jar`, `*.class`, `tmp/`, `pkg/`, `*.gem`, `log/`. +14. **`.bundler-audit.yml` is a last resort, not a mute button**: it lists only advisories that upstream patched for newer lines than the ones CI tests (Rails 7.2, Active Support 7.2.x on Ruby 3.1), so those matrix entries can be audited at all. Keep the root lock and every matrix resolution on patched releases, and drop an entry as soon as upstream ships a patch for the line under test — re-run `bundle-audit check --update` after every Rails or Ruby bump. ## Maintenance model @@ -122,7 +123,7 @@ Goal: the repo needs no hand maintenance except major-version dependency upgrade |---|---| | Maintainer agent | Repo managed by [developerz.ai](https://developerz.ai) (policy: `.dz/maintainer/maintainer.yml`): triages issues, reviews and merges every PR (contributors, members, bots) once its author marks it ready (out of draft), it is reviewed and `CI OK` is green; daily `health` + weekday `release-due` checkups and a monthly `improve` pass (`.dz/briefs/`: canary fixes, dependency majors, speed with benchmark evidence, correctness, docs drift); opens release PRs; breaking changes escalate to the owner. Sibling projects such as [wurk](https://github.com/sebyx07/wurk) run the same model | | Dependencies | Dependabot (`.github/dependabot.yml`) opens grouped weekly PRs after a 14-day cooldown (security updates skip it); the agent reviews and merges on green — the only merger | -| CI | `.github/workflows/ci.yml`; branch protection requires only the aggregate `CI OK` job: RuboCop, bundle-audit (the root `Gemfile.lock` **and** every matrix resolution — `gemfiles/standalone.gemfile`, `gemfiles/rails.gemfile` — checked against the Ruby advisory database, in the matrix jobs themselves), full suite + Brakeman, gem specs across the Ruby matrix (generated from maintained CRuby versions), native builds on Linux/macOS arm64, `-Werror` with gcc and clang, ASAN/UBSAN (`use_sigaltstack=0`), pure-Ruby fallback, JRuby (Java escaper jar + `-java` gem) and TruffleRuby (pure Ruby) via `gemfiles/standalone.gemfile`, Rails integration per `gemfiles/rails.gemfile`, `bin/gem-smoke` packaging on plain RubyGems. Every job required; no `continue-on-error` | +| CI | `.github/workflows/ci.yml`; branch protection requires only the aggregate `CI OK` job: RuboCop, bundle-audit (the root `Gemfile.lock` **and** every matrix resolution — `gemfiles/standalone.gemfile`, `gemfiles/rails.gemfile` — checked against the Ruby advisory database, in the matrix jobs themselves; advisories that upstream patched only for newer lines are listed in `.bundler-audit.yml`, which every `bundle-audit` run reads), full suite + Brakeman, gem specs across the Ruby matrix (generated from maintained CRuby versions), native builds on Linux/macOS arm64, `-Werror` with gcc and clang, ASAN/UBSAN (`use_sigaltstack=0`), pure-Ruby fallback, JRuby (Java escaper jar + `-java` gem) and TruffleRuby (pure Ruby) via `gemfiles/standalone.gemfile`, Rails integration per `gemfiles/rails.gemfile`, `bin/gem-smoke` packaging on plain RubyGems. Every job required; no `continue-on-error` | | Canary | `.github/workflows/canary.yml`, monthly on the 1st, not gating: Ruby head and Rails edge surface upstream breaks before a release ships them; the monthly `improve` checkup fixes what it finds | | Benchmarks | `.github/workflows/benchmark.yml`, non-gating: every push to `main` and PR renders a table into the job summary and uploads JSON; PRs get a sticky comment with the delta vs `main`. Set repo variable `BENCH_RUNNER` for a stable self-hosted runner; README holds only a dated release snapshot | | Releases | `.github/workflows/release.yml`: merging a `version.rb` bump to `main` (no `v` tag yet) runs the tests and `rake smoke`, builds the `-java` gem on JRuby (jar inside), publishes **both** the C-extension gem and the `-java` gem via RubyGems trusted publishing (environment `release`), then creates the tag and the GitHub release from `bin/changelog-notes`. The release PR is opened by the agent or the `release` skill (`docs/release.md`) | diff --git a/CHANGELOG.md b/CHANGELOG.md index 931beb9..3072f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ Entries before 1.6.6 were reconstructed from `git log` and RubyGems release date ### Changed - CI now audits every matrix entry's resolved dependency set against the Ruby advisory database, not only the root `Gemfile.lock`: the gem-specs, engine and Rails matrix jobs run `bundle-audit` on the lockfile their own unlocked gemfile resolves (`gemfiles/standalone.gemfile.lock`, `gemfiles/rails.gemfile.lock`), and an advisory fails that job. +- The advisories that upstream patched only for newer lines than the ones CI tests — Rails 7.2, and Active Support 7.2.x, the newest release that runs on Ruby 3.1 — are listed in `.bundler-audit.yml`, which every `bundle-audit` run reads. Without that list the oldest matrix entries could never be green: they keep failing on advisories that have no patched release for the line under test. The root `Gemfile.lock` and every maintained line stay on patched releases, so the list hides nothing there. +- The Rails 7.2 matrix entry now resolves Rails 7.2.4 (the newest 7.2 patch) instead of 7.2.3: Active Support 7.2.4 requires `minitest < 6` while `rails-dom-testing` asks for `minitest` without an upper bound, so without the pin Bundler kept minitest 6 and fell back to 7.2.3, which still carried advisories 7.2.4 already fixes. +- Development dependencies: the root `Gemfile.lock` moves to Rails 8.1.4.1 (a security patch release). No gem code changed. ### Fixed diff --git a/Gemfile.lock b/Gemfile.lock index b9f16d1..d4cea5d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,29 +3,29 @@ GEM specs: action_text-trix (2.1.19) railties - actioncable (8.1.4) - actionpack (= 8.1.4) - activesupport (= 8.1.4) + actioncable (8.1.4.1) + actionpack (= 8.1.4.1) + activesupport (= 8.1.4.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.4) - actionpack (= 8.1.4) - activejob (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + actionmailbox (8.1.4.1) + actionpack (= 8.1.4.1) + activejob (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) mail (>= 2.8.0) - actionmailer (8.1.4) - actionpack (= 8.1.4) - actionview (= 8.1.4) - activejob (= 8.1.4) - activesupport (= 8.1.4) + actionmailer (8.1.4.1) + actionpack (= 8.1.4.1) + actionview (= 8.1.4.1) + activejob (= 8.1.4.1) + activesupport (= 8.1.4.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.4) - actionview (= 8.1.4) - activesupport (= 8.1.4) + actionpack (8.1.4.1) + actionview (= 8.1.4.1) + activesupport (= 8.1.4.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -33,36 +33,36 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.4) + actiontext (8.1.4.1) action_text-trix (~> 2.1.15) - actionpack (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + actionpack (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.4) - activesupport (= 8.1.4) + actionview (8.1.4.1) + activesupport (= 8.1.4.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) - activejob (8.1.4) - activesupport (= 8.1.4) + rails-html-sanitizer (~> 1.6, >= 1.7.2) + activejob (8.1.4.1) + activesupport (= 8.1.4.1) globalid (>= 0.3.6) - activemodel (8.1.4) - activesupport (= 8.1.4) - activerecord (8.1.4) - activemodel (= 8.1.4) - activesupport (= 8.1.4) + activemodel (8.1.4.1) + activesupport (= 8.1.4.1) + activerecord (8.1.4.1) + activemodel (= 8.1.4.1) + activesupport (= 8.1.4.1) timeout (>= 0.4.0) - activestorage (8.1.4) - actionpack (= 8.1.4) - activejob (= 8.1.4) - activerecord (= 8.1.4) - activesupport (= 8.1.4) + activestorage (8.1.4.1) + actionpack (= 8.1.4.1) + activejob (= 8.1.4.1) + activerecord (= 8.1.4.1) + activesupport (= 8.1.4.1) marcel (~> 1.0) - activesupport (8.1.4) + activesupport (8.1.4.1) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -201,20 +201,20 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.1.4) - actioncable (= 8.1.4) - actionmailbox (= 8.1.4) - actionmailer (= 8.1.4) - actionpack (= 8.1.4) - actiontext (= 8.1.4) - actionview (= 8.1.4) - activejob (= 8.1.4) - activemodel (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + rails (8.1.4.1) + actioncable (= 8.1.4.1) + actionmailbox (= 8.1.4.1) + actionmailer (= 8.1.4.1) + actionpack (= 8.1.4.1) + actiontext (= 8.1.4.1) + actionview (= 8.1.4.1) + activejob (= 8.1.4.1) + activemodel (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) bundler (>= 1.15.0) - railties (= 8.1.4) + railties (= 8.1.4.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -222,9 +222,9 @@ GEM rails-html-sanitizer (1.7.2) loofah (~> 2.25, >= 2.25.3) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.1.4) - actionpack (= 8.1.4) - activesupport (= 8.1.4) + railties (8.1.4.1) + actionpack (= 8.1.4.1) + activesupport (= 8.1.4.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -404,17 +404,17 @@ DEPENDENCIES CHECKSUMS action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009 - actioncable (8.1.4) sha256=cfcd72f8c861e01ccdc96d772ad8c5ad6ed0ea7c571aef81a794b6027eaa7f11 - actionmailbox (8.1.4) sha256=9c4525e577ebb671ce558f3ea78b8a634f64e56a0e6e8d105b28b873793a06eb - actionmailer (8.1.4) sha256=f82d95d6762ae46ea38f221d3363639266af4f8e56d50e2bd367558674baf093 - actionpack (8.1.4) sha256=245493cc51be195a7645e6fc184880fa7e5b32ff1a08e053ee31f58c76e30ea4 - actiontext (8.1.4) sha256=33ee99bea7da6af911fc7d98b17013a8e1ec62aa01b20b20e9a5366f40dbf644 - actionview (8.1.4) sha256=eff1efd07c4d969abad288322b423bcb67fdbab5e324c91ca37352b80f226076 - activejob (8.1.4) sha256=4ed5b2ed8d4cc5c8f5f09becfef8346f202a4884eee0c8c73cae5ac378824099 - activemodel (8.1.4) sha256=9a0906c127bd118aa5f06157db12b8f750da83dccee72db2f658547a0ae7a0e8 - activerecord (8.1.4) sha256=3b4957ea7f246c9231bd7faa909312eac03a9101aaf87977b6c52733ff00b04f - activestorage (8.1.4) sha256=f4956f3ec9ee63e5e0b332b57c877cb627f82b56f4cc902279310d2aa285c7a8 - activesupport (8.1.4) sha256=891dd3fe43c4fc8391934b237b1189bb1f661c932a84a840043db9d46a4e80df + actioncable (8.1.4.1) sha256=007670f7297973e94809e9f32a56624a7aa60b6e9d6696cd078064ce86d1c7fc + actionmailbox (8.1.4.1) sha256=18b43dd842bf3a3e31b8e38b7917c21e48c128fc8b263c893481cc66ee1a8f78 + actionmailer (8.1.4.1) sha256=a7b71dedf744e48dd6751e97350634edce9a2f7beeceac332dcccb91729ad066 + actionpack (8.1.4.1) sha256=8c6f2e7cbd2653ef0539bde1ff20eb3e2e264e366affe56f0ba7f06d3670cb4b + actiontext (8.1.4.1) sha256=fb59611dc16e2b6bbfcec66793acfae6cdd64b1e35ef3f9175424f31e62c38da + actionview (8.1.4.1) sha256=d25b4d9cf8d0c0195ffcfe8b2cf3fcbe6fea54fd7c17f1a9cb8182881463f12b + activejob (8.1.4.1) sha256=82c4c9311ea1016d5f7f134a0c5f9b6527cad5a6237f5f3f60aa17984ef36229 + activemodel (8.1.4.1) sha256=063caf105372fbc98b2d789e4be23de63ac5c043f7d9330f04e2caddb7012805 + activerecord (8.1.4.1) sha256=bd65599ca0b5d210937f835d42c7aff69d271741c75aab022730be6cbce67e87 + activestorage (8.1.4.1) sha256=0f4e2f114f3584723a80a2c089aacc6170e765a0b2c385c9782e26ed78f05c66 + activesupport (8.1.4.1) sha256=16ac4f341ca65c85a1fcdb8ac73f68891aae9e3c4a6f9c3a27daed8b00017ed7 addressable (2.9.1) sha256=f1212365945455c84e8d031eea40fc6bedf0c663a78d95b3ab93591040bc5c35 ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b @@ -484,10 +484,10 @@ CHECKSUMS rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8 rack-test (2.3.0) sha256=b118d177cc002c572fc1b6fd8d19c9ed5881a36e8d6f689f9e618f9f861241e8 rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868 - rails (8.1.4) sha256=5f3ecdf9a2a3b79b2e31b8fee3b64a00f7284d76e5631f87d0e58e665911a33b + rails (8.1.4.1) sha256=365c3a0ad2713bd6e60628e5e826622d4ca8dee60b5a7e560e9b20844e9650bc rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d rails-html-sanitizer (1.7.2) sha256=a21ef45b7a6639fc8e2282473c547514cb86ae271616162a946bf89148fa2424 - railties (8.1.4) sha256=b5e7a8e8984e33114d68af62227971960aae1e6c02bd7c9d826ffad97fff583c + railties (8.1.4.1) sha256=ff2ef4b5a62cbc89811f88be11c776e2ed71b14dd012eae6a494d37994aa9782 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 rake-compiler (1.3.2) sha256=12fed13fa07f9caee829599935e0332ba76e2047a617f8f7918a222d68427760 diff --git a/gemfiles/rails.gemfile b/gemfiles/rails.gemfile index a7efafb..c2508f1 100644 --- a/gemfiles/rails.gemfile +++ b/gemfiles/rails.gemfile @@ -19,5 +19,10 @@ else end # Rails < 7.2 only accepts sqlite3 1.x at runtime. gem 'sqlite3', rails_version != 'edge' && Gem::Version.new(rails_version) < Gem::Version.new('7.2') ? '~> 1.4' : '>= 2.1' +# Rails 7.2.4 (the newest 7.2 patch) requires minitest < 6, but rails-dom-testing +# asks for minitest without an upper bound: without this pin Bundler keeps +# minitest 6 and falls back to Rails 7.2.3, which still carries advisories that +# 7.2.4 already fixes. Rails 8.0+ works with minitest 6, so it stays unpinned. +gem 'minitest', '< 6' if rails_version != 'edge' && Gem::Version.new(rails_version) < Gem::Version.new('8.0') eval_gemfile 'rails_common.rb' From bcc0b4b07c1da63d3818fe10fe9a37a67056e617 Mon Sep 17 00:00:00 2001 From: "developerz-ai[bot]" <286432749+developerz-ai[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 12:24:41 +0000 Subject: [PATCH 3/3] chore(babysit): land .bundler-audit.yml (+3 more) Follow-up intent: Address review findings on sebyx07/ruby2html#23 Dz-Task-Id: tsk_743e91285a6090c44cb8af159ae063e2 --- .bundler-audit.yml | 59 +++++++++++++++++++++++++++++++++++------- AGENTS.md | 2 +- CHANGELOG.md | 4 +-- gemfiles/rails.gemfile | 13 ++++++---- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/.bundler-audit.yml b/.bundler-audit.yml index 05fa15d..5945431 100644 --- a/.bundler-audit.yml +++ b/.bundler-audit.yml @@ -5,56 +5,95 @@ # 8.1.4.1 - there is no patched release for the older lines the CI matrix # deliberately tests: # -# * Rails 7.2, the demo app's floor (`gemfiles/rails.gemfile`); -# * Active Support 7.2.x, the newest release that runs on Ruby 3.1, the -# gem's Ruby floor (`gemfiles/standalone.gemfile`). +# * Rails 7.2, the demo app's floor (`gemfiles/rails.gemfile`): every +# component resolves to 7.2.4, which carries all the advisories below; +# * Active Support 7.2.4, the newest release that runs on Ruby 3.1, the +# gem's Ruby floor (`gemfiles/standalone.gemfile`): only +# GHSA-8474-xrp6-wh69 (safe_constantize DoS) applies there. # -# Without this list those matrix entries can never be green: the audit would -# keep failing on advisories nobody can fix on the line under test, and a -# permanently red gate is a gate nobody reads. Every maintained line (the root -# lock on Rails 8.1.4.1, Rails 8.0, Rails 8.1, Ruby >= 3.3, JRuby, TruffleRuby) -# is patched, so nothing here hides a fixable vulnerability there. +# Every entry below names the line it covers and what retires it, so a stale +# entry is visible in the file. The other audited resolutions carry none of +# these advisories (the root lock on Rails 8.1.4.1, the Rails 8.0 line on +# 8.0.5.2, the Rails 8.1 line on 8.1.4.1, Active Support 8.1.4.1 on Ruby >= +# 3.3 and on JRuby and TruffleRuby), so the list hides nothing there: auditing +# the root lock with this list switched off reports nothing. # # Rules for this list: # * only advisories that have no patched release for a line CI tests; -# * drop an entry as soon as upstream ships a patch for that line - re-run -# `bundle-audit check --update` after every Rails or Ruby bump; +# * drop an entry as soon as upstream ships a patch for the line it covers; # * an entry must never hide a fixable vulnerability: keep the lockfile and # the matrix resolutions on patched releases. +# +# `bundle-audit` has no conditional ignore entries - the format is a flat +# list of advisory IDs, read by every run - so this list is global: a Rails +# line that joins the generated matrix unpatched inherits these mutes. That is +# a decision to take per line, not a default: audit the new line's resolution +# with the list switched off before it joins the matrix. +# +# How to re-derive the list after a Rails or Ruby bump. `bundle-audit` +# reports an advisory only for a version it is not patched in, so with the +# ignore list switched off the output is exactly the list still needed: +# +# printf 'ignore: []\n' > /tmp/no-ignore.yml +# bundle-audit check --update --gemfile-lock gemfiles/rails.gemfile.lock --config /tmp/no-ignore.yml +# bundle-audit check --update --gemfile-lock gemfiles/standalone.gemfile.lock --config /tmp/no-ignore.yml +# +# Every advisory those two commands report still needs an entry here; every +# advisory missing from their output is stale and its entry must go. The +# same command on the root lock must report nothing. ignore: # Action Mailbox: signature replay in the Mailgun ingress. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-5957-c7qm-m469 # Action Pack: restoration of destroyed sessions in the CacheStore. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-2586-8ghr-69fj # Action Pack: missing SameSite attribute on session cookies. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-gmj8-hvj3-49mw # Action Pack: cache poisoning through content negotiation. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-r9jg-fmfx-2gv6 # Action Pack: DoS in Action Controller live streaming. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-rwvh-fw6r-pwvp # Action Pack: strong parameters bypass in Parameters#each_key. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-wm3x-v3qw-4jh3 # Action Pack: session information leak through the Active Storage proxy. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-x6mj-8jx9-qhqv # Action View: XSS in the text helpers. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-2p2f-4rq4-gjwc # Action View: XSS in the tag helpers. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-68p9-83h6-5wpj # Action View: XSS in strip_tags. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-rq8q-p985-fjq7 # Action View: XSS in the highlight helper. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-v6rj-xm57-3p59 # Active Storage: local file disclosure in video previews and analysis. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-3f4q-8x5m-76v8 # Active Storage: unsigned Content-Type in GCS direct uploads. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-3w93-6hmw-whh4 # Active Storage: arbitrary file write and read in image analysis. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-f3w6-w445-fwh9 # Active Storage: circumvention of the file size limits. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-hfr4-8rjc-97m2 # Active Storage: DoS in filename handling. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-p2pm-g2h6-7j5j # Active Storage: arbitrary file read and remote code execution. + # Rails 7.2 line: drop when that line resolves a patched release. - GHSA-v924-7rp8-4mxh # Active Support: DoS in safe_constantize. + # Rails 7.2 line and Active Support 7.2.4 on Ruby 3.1: drop when both + # resolve a patched release. - GHSA-8474-xrp6-wh69 diff --git a/AGENTS.md b/AGENTS.md index 2e3c41b..7b88910 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,7 +113,7 @@ Writing ruby2html templates/components (here or in a user app)? Load [`llms.txt` 11. Public API used downstream — `Render.new(ctx, &blk)`, `#render`, `#plain`, `#component`, tag methods, `Ruby2html.escape_html`, `Ruby2html.native?`, `Ruby2html::Component`, `RailsHelper#html`, `ComponentHelper#html` / `#__ruby2html_renderer__`, the `.rb` handler, `HtmlBeautifierMiddleware` — breaking changes need a CHANGELOG entry ("Upgrading to 2.0") and a major bump. 12. Style: rubocop-rails_config, single quotes, `# frozen_string_literal: true`, nested modules, Rails-style indented `private` methods. C: gnu99, no warnings under `-Wall -Wextra`. 13. Never commit `lib/ruby2html/*.so`, `*.jar`, `*.class`, `tmp/`, `pkg/`, `*.gem`, `log/`. -14. **`.bundler-audit.yml` is a last resort, not a mute button**: it lists only advisories that upstream patched for newer lines than the ones CI tests (Rails 7.2, Active Support 7.2.x on Ruby 3.1), so those matrix entries can be audited at all. Keep the root lock and every matrix resolution on patched releases, and drop an entry as soon as upstream ships a patch for the line under test — re-run `bundle-audit check --update` after every Rails or Ruby bump. +14. **`.bundler-audit.yml` is a last resort, not a mute button**: it lists only advisories that upstream patched for newer lines than the ones CI tests (Rails 7.2, Active Support 7.2.x on Ruby 3.1), so those matrix entries can be audited at all; every entry names the line it covers and what retires it. Keep the root lock and every matrix resolution on patched releases, and drop an entry as soon as upstream ships a patch for the line it covers. After every Rails or Ruby bump, re-derive the list with the ignore list **switched off** — `printf 'ignore: []\n' > /tmp/no-ignore.yml` then `bundle-audit check --update --gemfile-lock --config /tmp/no-ignore.yml`: what it still reports needs an entry, what it stops reporting is stale and its entry must go (re-running the audit *with* the ignore list cannot show this). `bundle-audit` has no conditional ignore entries, so a new Rails line joining the generated matrix inherits these mutes: audit its resolution with the list switched off before it joins. ## Maintenance model diff --git a/CHANGELOG.md b/CHANGELOG.md index 3072f79..b2b8607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ Entries before 1.6.6 were reconstructed from `git log` and RubyGems release date ### Changed - CI now audits every matrix entry's resolved dependency set against the Ruby advisory database, not only the root `Gemfile.lock`: the gem-specs, engine and Rails matrix jobs run `bundle-audit` on the lockfile their own unlocked gemfile resolves (`gemfiles/standalone.gemfile.lock`, `gemfiles/rails.gemfile.lock`), and an advisory fails that job. -- The advisories that upstream patched only for newer lines than the ones CI tests — Rails 7.2, and Active Support 7.2.x, the newest release that runs on Ruby 3.1 — are listed in `.bundler-audit.yml`, which every `bundle-audit` run reads. Without that list the oldest matrix entries could never be green: they keep failing on advisories that have no patched release for the line under test. The root `Gemfile.lock` and every maintained line stay on patched releases, so the list hides nothing there. -- The Rails 7.2 matrix entry now resolves Rails 7.2.4 (the newest 7.2 patch) instead of 7.2.3: Active Support 7.2.4 requires `minitest < 6` while `rails-dom-testing` asks for `minitest` without an upper bound, so without the pin Bundler kept minitest 6 and fell back to 7.2.3, which still carried advisories 7.2.4 already fixes. +- The advisories that upstream patched only for newer lines than the ones CI tests — Rails 7.2, and Active Support 7.2.x, the newest release that runs on Ruby 3.1 — are listed in `.bundler-audit.yml`, which every `bundle-audit` run reads. Each entry names the line it covers and what retires it, and the file documents how to re-derive the list with the ignore list switched off, so a stale entry is spotted after a Rails or Ruby bump instead of silently muting an advisory that upstream already fixed. Without that list the oldest matrix entries could never be green: they keep failing on advisories that have no patched release for the line under test. The root `Gemfile.lock` and every maintained line stay on patched releases, so the list hides nothing there. +- The Rails 7.2 matrix entry now resolves Rails 7.2.4 (the newest 7.2 patch) instead of 7.2.3: Active Support 7.2.4 requires `minitest < 6` while `rails-dom-testing` asks for `minitest` without an upper bound, so without the pin Bundler kept minitest 6 and fell back to 7.2.3, which still carried advisories 7.2.4 already fixes. The pin covers the 7.2 line only — the oldest line CI tests; Rails 8.0+ works with minitest 6 and stays unpinned. - Development dependencies: the root `Gemfile.lock` moves to Rails 8.1.4.1 (a security patch release). No gem code changed. ### Fixed diff --git a/gemfiles/rails.gemfile b/gemfiles/rails.gemfile index c2508f1..4664f25 100644 --- a/gemfiles/rails.gemfile +++ b/gemfiles/rails.gemfile @@ -19,10 +19,13 @@ else end # Rails < 7.2 only accepts sqlite3 1.x at runtime. gem 'sqlite3', rails_version != 'edge' && Gem::Version.new(rails_version) < Gem::Version.new('7.2') ? '~> 1.4' : '>= 2.1' -# Rails 7.2.4 (the newest 7.2 patch) requires minitest < 6, but rails-dom-testing -# asks for minitest without an upper bound: without this pin Bundler keeps -# minitest 6 and falls back to Rails 7.2.3, which still carries advisories that -# 7.2.4 already fixes. Rails 8.0+ works with minitest 6, so it stays unpinned. -gem 'minitest', '< 6' if rails_version != 'edge' && Gem::Version.new(rails_version) < Gem::Version.new('8.0') +# Active Support 7.2.4 (the newest 7.2 patch) requires minitest < 6, but +# rails-dom-testing asks for minitest without an upper bound: without this pin +# Bundler keeps minitest 6 and falls back to Rails 7.2.3, which still carries +# advisories that 7.2.4 already fixes. Rails 8.0+ works with minitest 6, so it +# stays unpinned. The pin covers the 7.2 line only - the oldest line CI tests +# (see bin/rails-versions): Active Support 7.0 and 7.1 ask for minitest >= 5.1 +# without an upper bound, so they need no pin. +gem 'minitest', '< 6' if rails_version != 'edge' && Gem::Requirement.new('>= 7.2', '< 8.0').satisfied_by?(Gem::Version.new(rails_version)) eval_gemfile 'rails_common.rb'