Skip to content

Dependency hardening: roo, httparty, searchkick, image_processing, puma, dev toolchain - #17

Merged
jayvarner merged 5 commits into
developfrom
dependency-hardening-2
Aug 12, 2026
Merged

Dependency hardening: roo, httparty, searchkick, image_processing, puma, dev toolchain#17
jayvarner merged 5 commits into
developfrom
dependency-hardening-2

Conversation

@jayvarner

Copy link
Copy Markdown
Member

Summary

Bumps the direct-dependency majors deferred from the earlier Rails 8 dependency-hardening pass, per user decision. elasticsearch/rack-cors explicitly excluded (ES9 needs a server upgrade first; rack-cors needs Rack 3, deferred as its own dedicated ecosystem-wide upgrade alongside sidekiq - see below).

roo 2.10.1 -> 3.0.0, httparty 0.20.0 -> 0.24.2 (befe33d)

No API breaking changes to either. httparty fixes a High-severity SSRF CVE; every call site hits a hardcoded host with no credentials, so no behavior change expected.

searchkick 5.3.1 -> 6.1.2 (41caef7)

No breaking changes to .search. Surfaced two real, pre-existing bugs neither caught by tests before:

  • LettersController/EntitiesController's before_action :reindex relied on Searchkick's default reindex-mode resolution to pick synchronous - that resolution now falls back to the model's callbacks: :async option, so the reindex sometimes ran async and the search immediately after raced against it, intermittently missing the ES mapping entirely. Fixed with explicit mode: :inline.
  • letter.orgins (typo for origins) - 5.x's HashWrapper silently returned nil for the typo, 6.x raises NoMethodError. Fixed + added regression coverage.

image_processing 1.14.0 -> 2.0.3 (49a1d67)

Fixes several real RCE vulnerabilities. mini_magick/ruby-vips became soft dependencies in 2.0. Initially assumed mini_magick was the active processor (Rails' bare default) - wrong. This app's config.load_defaults 7.0 unconditionally sets variant_processor = :vips, which has been the actually-active processor the whole time (mini_magick was present only as an unused transitive dependency of image_processing 1.x). Added ruby-vips instead, confirmed via an actual end-to-end variant generation, not just a clean boot.

puma 5.6.9 -> 8.0.2 (a4de628)

Reviewed all four majors' upgrade guides. The one real change: Puma 8 defaults to binding IPv6 instead of IPv4 when available. Pinned config/puma.rb's bind explicitly to 0.0.0.0 to preserve current behavior regardless of Fargate's IPv6 support (unverifiable from here). Confirmed via a real server boot that it listens on 0.0.0.0 as pinned.

Dev/test toolchain (252111f)

rspec-rails, shoulda-matchers, rubocop + plugins to latest. Dev-only, no production impact. Fixed a handful of newly-surfaced, individually-verified-safe rubocop offenses; deliberately left Rails/StrongParametersExpect alone (18 call sites - params.expect raises where bare params[:key] silently returns nil, a real behavior change deserving its own audit, not a mechanical sweep).

Explicitly deferred, not in this PR

  • sidekiq 7.3.10 -> 8.x: hard-requires rack >= 3.2.0, the same Rack 3 bump just deferred via rack-cors. Per your decision, staying on 7.x until Rack 3 is tackled as its own dedicated upgrade (both gems can ride together then).
  • elasticsearch client 8->9: needs an ES9 server first, per your note.
  • rack-cors 2.0.2 -> 3.0.0: same Rack 3 dependency as sidekiq.

Test plan

  • Full suite after every single bump: 309 examples, 0 failures throughout
  • System specs (real headless-Chrome + Puma-backed server) passing after the Puma bump
  • rubocop clean except deliberately-deferred items
  • Manual end-to-end verification: real image variant generation (image_processing/vips), real server boot confirming the Puma bind pin

🤖 Generated with Claude Code

jayvarner and others added 5 commits August 11, 2026 12:50
Both deferred from the earlier dependency-hardening PR since they're major
version bumps needing their own verification.

roo 3.0 has no API-level breaking changes to spreadsheet reading (open/cell
access/sheet iteration) per its changelog - just needs Ruby 3.1+ (have
3.4.10) and pulls rubyzip to 3.x, unconstrained by anything else in the
Gemfile.

httparty 0.24.2 fixes a High-severity SSRF/API-key-leakage CVE
(GHSA-hm5p-x4rq-38w4) by restricting credential-forwarding on redirects to
same-host. Every call site in this app (lib/import.rb,
app/jobs/import_data_job.rb) hits a single hardcoded host directly, with no
base_uri set and no credentials passed, so no behavior change expected -
verified via full suite + clean boot (no dedicated spec coverage exists for
either file either way).

Verified: full suite (308 examples, 0 failures), rubocop clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No breaking changes to the .search API (where/load/order) per the
changelog. Removed its hard dependency on the elasticsearch gem (now
optional, bring-your-own client) - decoupled from any future ES9 bump,
not blocked by it.

Surfaced two real, pre-existing bugs neither caught by tests before:

- LettersController/EntitiesController's `before_action :reindex` calls
  `Letter.reindex`/`Entity.reindex` (class-level, full reindex) with no
  explicit mode, relying on Searchkick's default mode resolution to pick a
  synchronous reindex before the search that immediately follows. That
  resolution (Searchkick::Index#reindex_records) falls back to the model's
  own `callbacks:` searchkick option when no mode is given - and Searchable
  sets `callbacks: :async`. Whether that fallback applied to explicit
  `.reindex` calls the same way in 5.x is unclear, but it does now: the
  reindex enqueued an async job instead of running inline, and the search
  that followed sometimes ran before it completed, intermittently missing
  the ES mapping entirely ("No mapping found for [date]"). Fixed by passing
  `mode: :inline` explicitly in both controllers' reindex methods.

- LettersController#letters_from_results called `letter.orgins` (typo for
  `origins`) on a Searchkick::HashWrapper. 5.x's HashWrapper silently
  returned nil for the typo'd key; 6.x raises NoMethodError instead.
  Fixed the typo and added regression coverage - nothing had ever asserted
  on the `origins` key in a letters index response before.

Verified: full suite (309 examples, 0 failures), rubocop clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes several real RCE vulnerabilities (remote code execution via
user-influenced operation names through #operation/#method_missing/#send,
and unsafe minimagick loader/saver option names).

mini_magick/ruby-vips became soft dependencies in 2.0 and must be added
explicitly now - image_processing 1.x's gemspec hard-required both
unconditionally. Initially added mini_magick, assuming it was the active
processor since Rails defaults ActiveStorage.variant_processor to
:mini_magick. That was wrong: this app calls config.load_defaults 7.0
(config/application.rb), and Rails' 7.0 defaults block
(config/application/configuration.rb) unconditionally sets
config.active_storage.variant_processor = :vips - vips has been the
actually-active processor the whole time, with mini_magick present only as
an unused transitive dependency. Confirmed by generating a real image
variant, not just booting cleanly. Added ruby-vips instead.

Verified: full suite (309 examples, 0 failures), rubocop clean, and an
actual end-to-end variant generation via `rails runner` (attach a real
image, call .variant(...).processed, confirm it produces a real blob) -
not just a clean boot, since this exact class of bug (config silently
depending on a gem that happened to be present transitively) only shows up
when the feature is actually exercised.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reviewed all four majors' upgrade guides (6.0/7.0/8.0-Upgrade.md) for
anything that could affect this app: hook renames, removed Server#min/
max_threads, HTTP method allowlisting, response header lowercasing - none
of it touches config/puma.rb (no hooks, no workers/cluster mode configured)
or anything this app depends on.

The one real behavioral change: Puma 8 defaults to binding IPv6 (::)
instead of IPv4 (0.0.0.0) when a non-loopback IPv6 interface is available.
This app deploys to AWS ECS/Fargate, and whether that networking setup
handles the new default cleanly isn't verifiable from here. Puma's own
upgrade guide recommends pinning the bind explicitly to preserve exact
current behavior - added `port ENV.fetch('PORT', 3000), '0.0.0.0'` to
config/puma.rb rather than gambling on it. Confirmed via a real (non-test-
suite) server boot that it actually listens on 0.0.0.0 as pinned.

No dependency conflicts - puma itself has no rack version constraint at
all, unlike sidekiq 8 (which hard-requires rack >= 3.2.0 and was deferred
alongside rack-cors for that reason).

Verified: full suite (309 examples, 0 failures) including system specs,
which boot a real Puma server via Capybara/Selenium - the exact code path
Puma's own 6.0 changelog warned could break for gems depending on removed
Server internals. rubocop clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rspec-rails 6.1.5 -> 8.0.4, shoulda-matchers 6.4.0 -> 8.0.1, rubocop
1.65.1 -> 1.89.0, rubocop-{rails,rspec,factory_bot,rspec_rails} to latest.
All dev/test-only, no production runtime impact - verified by running the
actual test suite and linter rather than reading changelogs, since a
failure here surfaces immediately as a spec or lint failure.

Applied the handful of newly-surfaced, genuinely safe rubocop fixes
(redundant safe-navigation chains, a no-op Array#flatten after a
string-returning #map, order('position') -> order(:position)) - all
verified equivalent by reading the surrounding code, not blindly
autocorrected.

Deliberately left Rails/StrongParametersExpect alone (18 call sites across
6 controllers) even though most are auto-correctable: params.expect(...)
raises ActionController::ParameterMissing/ParameterInvalid where bare
params[:key] silently returns nil, a real behavior change across every
public-facing endpoint in this app. That's its own deliberate audit, not a
mechanical sweep. Also left Naming/PredicateMethod (flags a pre-existing
dead `# TODO: Add authentication logic here` stub) and RSpec/Output
(a false positive - it's rails_helper.rb boot-time boilerplate that prints
and hard-exits on pending migrations, not a leftover debug print in a
spec) - both pre-existing, out of scope here.

Verified: full suite (309 examples, 0 failures) both before and after the
manual fixes, rubocop clean except the deliberately-deferred items above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jayvarner
jayvarner merged commit ae62bfa into develop Aug 12, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant