Skip to content

Upgrade Rails to 8.1.3.1 and refresh dependency security posture - #745

Merged
aaronlippold merged 18 commits into
masterfrom
rails-8.1-upgrade
Aug 9, 2026
Merged

Upgrade Rails to 8.1.3.1 and refresh dependency security posture#745
aaronlippold merged 18 commits into
masterfrom
rails-8.1-upgrade

Conversation

@aaronlippold

@aaronlippold aaronlippold commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

Rails 8.0 support ends 2026-10-07, and Brakeman's 60-day EOL warning opened on 2026-08-08 — failing CI on every branch. This PR moves master to the current stable line and takes the full dependency security posture with it: Rails, every held gem major, and both yarn trees.

Rails 8.1

  • Rails 8.0.5.1 → 8.1.3.1 with config.load_defaults 8.1 (all six new defaults active; the stale 7.0 defaults initializer deleted)
  • rack 2.2.23 → 3.2.6 — converges master with the feature branch's rack line
  • omniauth-rails_csrf_protection 2.0.1 — root-cause fix for the ActiveSupport::Configurable deprecation (removed in Rails 8.2)
  • Security/audit sweep: brakeman 8, rspec-rails 8, shoulda-matchers 8, simplecov 1.0, plus ~110 within-major updates; bundler-audit clean
  • params.expect adoption at 15 controller sites (query-string shape tampering → clean 400) plus the new-cop modernization sweep
  • schema.rb alphabetized under the 8.1 dumper (own commit; proven a pure reorder — 23 tables / 75 indexes identical both sides)
  • 8.1 stock error pages (adds dark-mode support)

The gem-major chain (each upgraded guides-first, live-proven, independently AC-reviewed)

  • puma 8.0.2 — IPv6 default bind with automatic fallback, proven live in the production container
  • rubyzip 3.4.1 via the revived roo 3 (replacing the dormant ruh-roo fork)
  • haml 7.3 + haml-rails 3.1 — the Rails 8.1 fragment-cache-tracker fix release; all 42 templates compile-swept
  • slack-ruby-client 3.2 + faraday 2 — the last faraday-1 holder; delivery proven against a real Slack workspace
  • OIDC stack: omniauth_openid_connect 0.8, openid_connect 2.5, rack-oauth2 2.3, swd 2.0, webfinger 2.1, json-jwt 1.17 (includes its padding-oracle security fix); httpclient leaves the lock; live Okta round-trip incl. RP-initiated logout
  • omniauth-ldap 3.0.2 — live-proven against a containerized directory
  • inspec-core 7.1.7 from the Cinc Project gem server — Vulcan consumes it purely as a library for control-code generation; live-proven in the rule editor. (Filed upstream: the rebuilt inspec gems' license metadata — cinc-project/distribution/auditor work_items/26)

Bugs found and fixed along the way (each with regression tests)

  • The settings YAML template rendered any #-leading env value (Slack channels, hex colors, passwords, secrets) as a YAML comment — silent nil, masked by initializer defaults
  • Streaming zip reads were unbounded regardless of rubyzip's size validation (extract-only) — every zip-read site now goes through a declared-size-capped reader
  • Remember-me via any OmniAuth provider (LDAP/OIDC/GitHub) has thrown NoMethodError since v2.3.1 — the Devise Rememberable controller module was never included; the covering spec only grepped source text and was replaced with real callback-driving specs
  • An unreachable LDAP server surfaced to users as the raw error type ("Ldap error") — now a readable reason that tells the user their credentials were never checked
  • Rake tasks double-loaded across task specs (order-dependent parallel failure)
  • Consent spec regex was quote-style-coupled to the old haml attribute quoting

JS dependency posture (both yarn trees)

  • App tree: 168 audit findings → 4 (both criticals eliminated). axios 1.19.0 (eleven highs; supply-chain ceiling resolution retained), dompurify 3.4.13 (fourteen sanitizer advisories), vitest 4.1.10, vite 7.3.6, @rails packages matched to Rails 8.1.301, in-range refreshes plus scoped resolutions for lagging toolchain transitives. The 4 accepted: image-size ×2 (no patch exists; dev-only build chain) and vue/vue-template-compiler (inherent to Vue 2; the migration is the planned fix)
  • Docs tree: 68 findings → 0 — vitepress 2.0.0-alpha.19, mermaid 10.9.8 with its transitives, one resolution; the site builds clean

Changelog

Notable version moves

Package From To
rails (full framework) 8.0.5.1 8.1.3.1
rack 2.2.23 3.2.6
puma 7.2.1 8.0.2
rubyzip 2.4.1 3.4.1
ruh-roo → roo 3.0.1 3.0.0 (revived upstream)
haml / haml-rails 6.3.0 / 2.1.0 7.3.0 / 3.1.0
slack-ruby-client 1.0.0 3.2.0
faraday 1.10.6 2.14.3
omniauth_openid_connect 0.6.1 0.8.0
openid_connect 1.4.2 2.5.0
rack-oauth2 1.21.3 2.3.0
swd / webfinger / json-jwt 1.3.0 / 1.2.0 / 1.15.3.1 2.0.3 / 2.1.3 / 1.17.2
omniauth-ldap 2.3.3 3.0.2
omniauth-rails_csrf_protection 1.0.2 2.0.1
inspec-core 5.22.95 7.1.7 (Cinc gem server)
brakeman 7.1.0 8.0.5
rspec-rails 6.1.5 8.0.4
shoulda-matchers 7.0.1 8.0.1
simplecov 0.22.0 1.0.3
axios (JS) 1.12.2 1.19.0
dompurify (JS) 3.3.1 3.4.13
vitest / vite (JS) 4.0.18 / 7.3.1 4.1.10 / 7.3.6
@rails/actioncable + activestorage (JS) 8.0.201 8.1.301
vitepress (docs) 2.0.0-alpha.11 2.0.0-alpha.19
mermaid (docs) 10.9.3 10.9.8
Full Ruby dependency delta — 118 changed, 11 added, 25 left the tree

Changed:
actioncable, actionmailbox, actionmailer, actionpack, actiontext, actionview, activejob, activemodel, activerecord, activestorage, activesupport, railties, rails 8.0.5.1→8.1.3.1 · activerecord-session_store 2.2.0→2.3.0 · anonymous_loader 0.1.1→0.1.3 · auth-sanitizer 0.2.2→0.2.3 · bindata 2.5.1→3.0.0 · blueprinter 1.2.1→1.3.0 · bootsnap 1.24.6→1.25.0 · brakeman 7.1.0→8.0.5 · byebug 12.0.0→13.0.0 · caxlsx 4.4.1→4.5.0 · cgi 0.5.1→0.5.2 · chef-config 18.8.11→19.3.15 · chef-utils 18.8.11→19.3.15 · commonmarker 2.7.0→2.9.0 · cookstyle 8.4.0→8.7.6 · crack 1.0.0→1.0.1 · csv 3.3.5→3.3.6 · database_cleaner-core 2.0.1→2.1.0 · devise-encryptable 0.2.0→0.3.0 · erb 6.0.4→6.0.7 · factory_bot 6.5.6→6.6.0 · faraday 1.10.6→2.14.3 · faraday-follow_redirects 0.3.0→0.5.0 · faraday-net_http 1.0.2→3.4.4 · ffaker 2.24.0→2.25.0 · ffi 1.17.2→1.17.4 · globalid 1.3.0→1.4.0 · haml 6.3.0→7.3.0 · haml-rails 2.1.0→3.1.0 · hashdiff 1.2.0→1.2.1 · hashie 5.0.0→5.1.0 · http-cookie 1.0.8→1.1.6 · i18n 1.14.8→1.15.2 · inspec-core 5.22.95→7.1.7 · io-console 0.8.2→0.9.0 · irb 1.17.0→1.18.0 · jbuilder 2.14.1→2.15.1 · json 2.21.1→2.21.2 · json-jwt 1.15.3.1→1.17.2 · language_server-protocol 3.17.0.5→3.17.0.6 · listen 3.9.0→3.10.0 · mail 2.9.0→2.9.1 · marcel 1.1.0→1.2.1 · mime-types-data 3.2025.0812→3.2026.0701 · minitest 6.0.3→6.0.6 · mixlib-log 3.0.9→3.2.3 · mixlib-shellout 3.3.9→3.4.10 · msgpack 1.8.3→1.8.4 · multi_xml 0.7.2→0.9.1 · net-imap 0.6.4.1→0.6.6 · net-ssh 7.3.0→7.3.3 · nokogiri-happymapper 0.10.0→0.10.1 · oauth2 2.0.24→2.0.25 · oj 3.17.4→3.17.5 · omniauth 2.1.3→2.1.4 · omniauth-ldap 2.3.3→3.0.2 · omniauth-rails_csrf_protection 1.0.2→2.0.1 · omniauth_openid_connect 0.6.1→0.8.0 · openid_connect 1.4.2→2.5.0 · ox 2.14.23→2.14.28 · parallel 1.27.0→1.28.0 · parallel_tests 5.6.0→5.7.0 · parser 3.3.9.0→3.3.12.0 · pp 0.6.3→0.6.4 · propshaft 1.2.1→1.3.2 · pry 0.15.2→0.16.0 · public_suffix 7.0.2→7.0.5 · puma 7.2.1→8.0.2 · rack 2.2.23→3.2.6 · rack-oauth2 1.21.3→2.3.0 · rack-protection 3.2.0→4.2.1 · rack-session 1.0.2→2.1.2 · rackup 1.0.1→2.3.1 · rake 13.3.1→13.4.2 · rdoc 7.2.0→8.0.0 · regexp_parser 2.11.2→2.12.0 · reline 0.6.3→0.7.0 · rspec 3.13.1→3.13.2 · rspec-core 3.13.5→3.13.6 · rspec-mocks 3.13.5→3.13.8 · rspec-rails 6.1.5→8.0.4 · rspec-support 3.13.4→3.13.7 · rubocop 1.79.2→1.86.1 · rubocop-ast 1.46.0→1.50.0 · rubocop-capybara 2.22.1→3.0.0 · rubocop-performance 1.25.0→1.26.1 · rubocop-rails 2.33.3→2.36.0 · rubocop-rspec 3.6.0→3.9.0 · rubyzip 2.4.1→3.4.1 · selenium-webdriver 4.35.0→4.46.0 · shoulda-matchers 7.0.1→8.0.1 · simplecov 0.22.0→1.0.3 · slack-ruby-client 1.0.0→3.2.0 · snaky_hash 2.0.6→2.0.7 · swd 1.3.0→2.0.3 · temple 0.10.4→0.10.6 · test-prof 1.5.2→1.6.3 · tilt 2.6.1→2.8.0 · tomlrb 1.3.0→2.0.4 · train-core 3.12.13→3.16.5 · tzinfo-data 1.2025.2→1.2026.3 · version_gem 1.1.12→1.1.15 · web-console 4.2.1→4.3.0 · webfinger 1.2.0→2.1.3 · webmock 3.25.1→3.26.2 · zeitwerk 2.7.5→2.8.3

Added: action_text-trix 2.1.19 · chef-licensing 1.4.1 · climate_control 1.2.0 · faraday-http-cache 2.7.0 · faraday-mashify 1.0.2 · net-http 0.9.1 · pstore 0.1.4 · rbs 4.1.2 · roo 3.0.0 · syslog 0.4.0 · tty-spinner 0.9.3

Left the tree — deliberate replacements: ruh-roo (→ roo) · httpclient (→ faraday 2 across the OIDC stack) · faraday_middleware + the faraday-1 adapter constellation (em_http, em_synchrony, excon, httpclient, net_http_persistent, patron, rack, retry — replaced by faraday 2's model) · validate_email (dropped by openid_connect 2.3)

Left the tree — dependency-graph shifts (nothing lost): psych, stringio, benchmark, webrick, mutex_m, ruby2_keywords (stdlib gems no longer declared by updated dependents) · mini_portile2, rake-compiler-dock, rb_sys (build tooling no longer pulled) · simplecov-html, simplecov_json_formatter, docile (absorbed by simplecov 1.0) · nokogiri's generic lock entry (platform-specific entries remain — nokogiri itself unchanged at 1.19.4)

JS dependency delta (app tree)

Direct: axios 1.12.2→1.19.0 (exact pin; supply-chain ceiling resolution raised to match) · dompurify 3.3.1→3.4.13 · lodash 4.17.23→4.18.1 · marked 17.0.3→17.0.6 · shiki 3.22.0→3.23.0 · easymde 2.20.0→2.21.0 · sass 1.97.3→1.102.0 · esbuild 0.25.9→0.25.12 · esbuild-sass-plugin 3.3.1→3.7.0 · vite 7.3.1→7.3.6 · vitest + @vitest/coverage-v8 4.0.18→4.1.10 · concurrently 9.2.1→9.2.4 · @eslint/js 9.33.0→9.39.5 · @rails/actioncable + @rails/activestorage 8.0.201→8.1.301

New scoped resolutions (patched transitives under toolchains whose own pins lag): minimatch (3.x and 9.x lines separately) · picomatch · js-yaml · ajv · flatted · ws · rollup · piscina · js-cookie · vite (single-copy hoist for yarn 1)

Docs tree: vitepress 2.0.0-alpha.11→alpha.19 · mermaid 10.9.3→10.9.8 (+ its dompurify/uuid/lodash-es transitives) · svgo 4.0.0→4.0.2 · vue 3.5.18→3.5.41 · vite-plugin-image-optimizer 2.0.2→2.0.3 · one resolution (mdast-util-to-hast)

Verification

  • Full backend suite: 2440 examples, 0 failures (parallel, 8 workers); zero deprecation warnings
  • JS suite: 2317 passed on the upgraded toolchain; docs site build renders every page
  • rubocop: zero offenses, zero disables added; Brakeman 8: zero warnings (EOL cleared, no ignore entries on this line); bundler-audit clean; ESLint zero-warnings mode clean
  • Every gem-major card closed through independent AC review with live evidence in a real browser (Okta sign-in/logout round-trip, LDAP container round-trip, Slack delivery receipts, editor-driven InSpec regeneration, archive export/restore)

Merge note

The commits are structured as a reviewable story (Rails upgrade → gem majors one per commit → found fixes → JS posture). Recommend a merge commit rather than squash to preserve it.

Rails 8.0 support ends 2026-10-07; Brakeman's 60-day EOL window opened
2026-08-08 and failed CI on every branch. Move to the current stable
line and take the dependency posture with it:

- rails 8.0.5.1 -> 8.1.3.1 (all framework gems in lockstep;
  action_text-trix extracted, benchmark dependency dropped)
- rack 2.2.23 -> 3.2.6 (+ rack-session 2, rackup 2, rack-protection 4)
  converging master with the feature branch's rack line
- omniauth-rails_csrf_protection 1.0.2 -> 2.0.1: root-cause fix for
  the ActiveSupport::Configurable deprecation (removed in Rails 8.2)
- brakeman 8, rspec-rails 8, shoulda-matchers 8, simplecov 1.0,
  byebug 13, bindata 3, rubocop-capybara 3, rdoc 8
- ~110 further within-major updates via bundle update --minor --strict

bundler-audit clean after all movements. Riskier majors (OIDC stack,
omniauth-ldap, rubyzip, haml, puma, slack/faraday, the InSpec/chef
line) deliberately held for dedicated passes with their own live
verification.

Authored by: Aaron Lippold<lippold@gmail.com>
- config.load_defaults 8.1: all six new defaults active (JSON
  escaping relaxed, finder-order raise with PK fallback, path-relative
  redirect raise, Ruby render tracker, hidden-field autocomplete
  removal)
- Delete the stale 7.0 framework-defaults initializer: every line was
  commented out and those defaults have been active since
  load_defaults reached 8.0
- Development gains verbose redirect logging (new in 8.1)
- Refresh the static error pages and icon from the 8.1 template:
  they gain dark-mode support and dvh sizing

app:update was reviewed hunk-by-hunk; the stock-template overwrites
that would have removed real configuration (force_ssl env contract,
the active Content-Security-Policy, the audited YAML allowlist,
bin/dev port detection, bin/setup flow, test.rb cache settings) were
rejected.

Authored by: Aaron Lippold<lippold@gmail.com>
Active Record 8.1 sorts table columns alphabetically in schema dumps
so they stop flip-flopping with migration order. One-time re-dump,
proven a pure reorder: 23 tables and 75 indexes identical on both
sides, with only the schema-version header changing.

Authored by: Aaron Lippold<lippold@gmail.com>
The rubocop-rails update ships cops for the Rails 8 parameter API and
the Rack 3 status names. The application-code half, each site
hand-reviewed:

- Model.find(params[:id]) -> Model.find(params.expect(:id)) at 15
  sites: array/hash query-string tampering now yields a clean 400
  instead of surprising find([]) behavior; well-formed requests are
  unchanged
- redirect_back fallback_location: -> redirect_back_or_to
- Rack 3 status names (:unprocessable_content) in application code,
  matching what the feature branch already uses
- Project comment-count builders move from each_with_object to to_h
- Two now-redundant cop disables removed

Authored by: Aaron Lippold<lippold@gmail.com>
Mechanical sweep from the rubocop family updates, dominated by the
new HttpStatusNameConsistency cops moving specs to the Rack 3 status
names the feature branch already uses. Also: benchmark output leaves
the performance spec (the measured time reports through the failure
message, keeping the suite silent on success), the pending-migration
notice goes to stderr, an intentional literal interpolation grep is
escaped instead of single-quoted, and a kind-check select becomes
grep(Symbol).

Authored by: Aaron Lippold<lippold@gmail.com>
Two task spec files each called Rails.application.load_tasks in
before(:all). Rake accumulates actions on re-load, so when both files
landed in the same parallel worker every task body ran twice per
invoke — the admin bootstrap specs failed with logger expectations
receiving two calls instead of one, an order-dependent failure that
never fired when the files ran in separate workers.

Port the load-once helper from the feature branch byte-identical and
convert both callers. Both files together in a single process: green.

Authored by: Aaron Lippold<lippold@gmail.com>
Copilot AI lite review requested due to automatic review settings August 8, 2026 18:29
@aaronlippold
aaronlippold temporarily deployed to vulcan-rails-8-1-upgrad-gv6g6d August 8, 2026 18:29 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Upgrades the application to Rails 8.1.3.1 (and aligned dependency set like rack 3.2.x) while modernizing request parameter handling (params.expect) and normalizing 422 handling (:unprocessable_content), plus refreshing several test/support utilities and stock error pages.

Changes:

  • Upgrade Rails to 8.1 defaults (config.load_defaults 8.1) and update Gem dependencies (notably rack 3.2.6, rspec-rails 8, shoulda-matchers 8, brakeman 8).
  • Adopt params.expect in multiple controllers and switch many JSON/API 422 responses from :unprocessable_entity to :unprocessable_content (with matching spec updates).
  • Improve test stability by loading Rake tasks once per spec process and refresh public error pages (dark-mode + CSS tweaks).

Reviewed changes

Copilot reviewed 55 out of 58 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
spec/support/helpers/rake_task_helpers.rb Adds helper to load Rails Rake tasks once for task specs.
spec/services/export/modes/backup_spec.rb Minor Ruby cleanup using grep(Symbol) for association symbol filtering.
spec/requests/users/unlink_identity_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/users/omniauth_callbacks_spec.rb Adjusts string matching for OAuth error flash interpolation check.
spec/requests/users_spec.rb Updates multiple 422 expectations to :unprocessable_content.
spec/requests/user_lock_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/upload_validation_spec.rb Updates expected 422 status symbol to :unprocessable_content across upload validations.
spec/requests/stigs_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/rules_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/rule_section_locks_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/rule_satisfactions_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/reviews_spec.rb Updates many expected 422 status symbols to :unprocessable_content.
spec/requests/reviews_lock_sections_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/reviews_lock_controls_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/registrations_spec.rb Updates expectation to allow :unprocessable_content for wrong-password update.
spec/requests/reactions_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/projects_import_backup_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/projects_create_from_backup_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/memberships_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/format_handling_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/components_update_spreadsheet_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/components_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/requests/components_detect_srg_spec.rb Updates expected 422 status symbol to :unprocessable_content.
spec/rails_helper.rb Uses warn for pending migration messaging (stderr) before exiting.
spec/performance/stig_viewer_performance_spec.rb Removes noisy puts; keeps perf threshold asserted via failure message.
spec/models/concerns/severity_counts_spec.rb Formatting-only indentation adjustment in expected columns computation.
spec/migrations/strip_satisfaction_text_spec.rb Converts heredocs to squiggly heredocs (<<~) in migration specs.
spec/lib/tasks/stig_and_srg_puller_spec.rb Switches to load_rake_tasks helper.
spec/lib/tasks/admin_bootstrap_spec.rb Switches to load_rake_tasks helper.
public/500.html Updates stock error page (dark mode, dvh, typography, straight apostrophes).
public/422.html Updates stock error page (dark mode, dvh).
public/406-unsupported-browser.html Updates stock error page (dark mode, dvh).
public/404.html Updates stock error page (dark mode, dvh, straight apostrophes).
public/400.html Updates stock error page (dark mode, dvh).
Gemfile.lock Bumps Rails and broad dependency set, including rack 3.x and test/security tooling.
Gemfile Updates Rails constraint (> 8.1.0), omniauth CSRF protection (> 2.0), rspec-rails (> 8.0), shoulda-matchers (> 8.0).
db/schema.rb Updates schema header to Rails 8.1 and reorders schema output under new dumper.
config/initializers/new_framework_defaults_7_0.rb Removes obsolete Rails 7.0 defaults initializer.
config/environments/development.rb Enables verbose redirect logs in development.
config/application.rb Updates config.load_defaults from 8.0 to 8.1.
app/models/security_requirements_guide.rb Switches SQL heredoc to squiggly form (<<~).
app/models/project.rb Uses rows.to_h transformations instead of manual hash building.
app/controllers/users/registrations_controller.rb Uses redirect_back_or_to and returns :unprocessable_content for JSON errors.
app/controllers/users_controller.rb Switches multiple JSON error responses to :unprocessable_content; adopts params.expect for set_user.
app/controllers/stigs_controller.rb Adopts params.expect(:id) for show; uses :unprocessable_content in destroy JSON error response.
app/controllers/security_requirements_guides_controller.rb Adopts params.expect(:id) for show/private finder; uses :unprocessable_content in destroy JSON error response.
app/controllers/rules_controller.rb Switches multiple JSON error responses to :unprocessable_content; adopts params.expect for set_rule.
app/controllers/rule_satisfactions_controller.rb Switches failure responses to :unprocessable_content; adopts params.expect for IDs.
app/controllers/reviews_controller.rb Switches invalid section handling to :unprocessable_content; adopts params.expect for IDs.
app/controllers/projects_controller.rb Switches many JSON error responses to :unprocessable_content; adopts params.expect(:id) for set_project.
app/controllers/project_access_requests_controller.rb Uses redirect_back_or_to, switches JSON failure to :unprocessable_content, adopts params.expect for IDs.
app/controllers/memberships_controller.rb Uses redirect_back_or_to, switches JSON failure to :unprocessable_content, adopts params.expect(:id).
app/controllers/concerns/upload_validatable.rb Switches upload validation failures to :unprocessable_content.
app/controllers/concerns/oidc_discovery_helper.rb Removes unnecessary RuboCop predicate-method disable/enable comments.
app/controllers/components_controller.rb Switches multiple JSON failures to :unprocessable_content; uses redirect_back_or_to in several places.
app/controllers/application_controller.rb Uses redirect_back_or_to for error handlers.
app/controllers/api/user_search_controller.rb Uses params.expect(:membership_id) when resolving membership target.
Suppressed comments (1)

app/controllers/components_controller.rb:370

  • Inside the .or(...) clause, also use params.expect(:id) so the exclusion is validated the same way as the outer scope.
                          .or(Component.where(based_on: SecurityRequirementsGuide.where(title: srg_title))
                 .where.not(id: params[:id])
                                       .where(released: true))

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app/controllers/components_controller.rb
Puma 8 changelog and the 8.0-Upgrade guide reviewed in full first:
none of the renamed lifecycle hooks, before_thread_start arity,
SIGPWR, supported_http_methods, http_content_length_limit,
fork_worker, or prune_bundler surfaces are in use — config/puma.rb
needs no changes.

The one behavioral change that touches us is the IPv6-preferring
default bind. Accepted as shipped: dual-stack hosts serve both
families, and on IPv4-only networks (the Docker bridge) puma falls
back to 0.0.0.0 with a boot warning — observed live in the
production container smoke. 8.0.2 also carries PROXY-protocol
parsing security fixes.

Verified: full suite green, Docker image built and serving,
real browser login round-trip through the new server.

Authored by: Aaron Lippold<lippold@gmail.com>
The Excel reader blocked the zip library: ruh-roo (dormant since 2021)
caps rubyzip below 3, while the revived roo 3.0 requires and was
co-designed with rubyzip 3. Swap ruh-roo for roo ~> 3.0 — the
Roo::Spreadsheet surface we use is unchanged — and take rubyzip 3.4.1
with its path-traversal and central-directory hardening.

Re-proving the zip-bomb defense found it half-imaginary: rubyzip
validates entry sizes only in Entry#extract, so streaming reads —
every read the import paths perform — inflated without limit on 2.4
and 3.x alike. An archive whose central directory under-declares
sizes passed the aggregate budget and expanded unbounded in memory.

- ZipEntryReader.read_capped: chunked streaming read hard-capped at
  the entry's declared size, raising Zip::EntrySizeError on the lie;
  applied at every zip-read site — the archive importer, the restore
  modal's pre-budget defaults probe, and the STIG puller
- The importer reports a clean error for a manifest without a
  components list instead of crashing
- Fixture tests binary-patch real archives' central directories to
  prove the lying-CD rejection at both the importer and the real
  upload endpoint, alongside the existing honest-big budget tests

Verified: full suite green, live UI export-and-restore round trip
through the real modals with the restored project's contents
DB-confirmed. The re-lock also normalized bundler platform entries
(mechanical Bundler 2.7.2 behavior; all deploy platforms retained).

Authored by: Aaron Lippold<lippold@gmail.com>
haml 7's breaking surface was reviewed against every template before
the bump: the attr_quote default flips from single to double quotes
(byte-level only — entity decoding keeps semantics identical), the
Ruby floor rises to 3.2, and the 7.3 Prism parser interpolates
literal instance/global variable references in strings. Zero of the
42 templates use any sensitive pattern and none needed changes — an
independent compile sweep of all 42 confirms.

haml-rails 3.1 is the Rails 8.1 support release and fixes a real
Rails 8.1 regression: the tightened ERB dependency tracker stopped
Haml partial changes from busting fragment caches; 3.1 switches Haml
templates to the :ruby render tracker.

The quote flip's one casualty was a consent spec helper whose regex
demanded single-quoted attributes — now quote-agnostic with a
backreference, and still failing honestly when the attribute is
absent.

Verified: full suite green including system specs, visual pass of
login, project, component view, and component edit pages in a real
browser.

Authored by: Aaron Lippold<lippold@gmail.com>
slack-ruby-client 1.0.0 was the bundle's last faraday-1 holder (via
faraday_middleware, which 3.2 replaces with faraday-mashify and
faraday-multipart). The UPGRADING guide was reviewed end to end: our
surface is Web API only, so the RTM removals don't touch us; the 3.2
error-type refinement keeps our ChannelNotFound-then-SlackError rescue
order correct; the 2.0 faraday SSL-default removal was the one live
risk and is proven by a real delivery over TLS to slack.com.

- faraday 1.10 -> 2.14 (direct pin loosened to ~> 2.0); the entire
  faraday-1 adapter constellation drops from the lock
- slack_block_kit stays: latest release, dependency-free, independent
  of the client
- climate_control joins the test group for parallel-safe ENV mutation
  in specs

Verified live end to end against a real workspace: direct
chat.postMessage delivery (ok=true, name resolved to channel id) and a
UI-triggered membership notification rendering its full Block Kit
payload in-channel.

Authored by: Aaron Lippold<lippold@gmail.com>
The settings template interpolated env vars into YAML unquoted, so any
value whose first character is '#' — Slack channels, hex colors,
passwords, client secrets — turned the rest of the line into a YAML
comment and the setting silently became nil. The banner colors only
appeared to work because an initializer re-defaults blank values,
which means a deployer's custom '#'-color silently reverted to the
default; a Slack channel like #vulcan-test could never be configured
at all, and SMTP/LDAP/OIDC credentials containing YAML-special
characters vanished without a trace.

Every free-form string interpolation now emits its value with to_json:
JSON strings are valid YAML scalars, every special character is
escaped, and unset variables still render as YAML null. Typed values
(booleans, numerics, symbols) are untouched.

New template-rendering specs load the file exactly as Settingslogic
does and pin that hash-leading channels, colors, secrets, and
punctuation-heavy passwords all survive — each proven failing against
the unquoted template first.

Authored by: Aaron Lippold<lippold@gmail.com>
omniauth_openid_connect 0.6.1 held the whole nov stack at its 1.x/
httpclient era. The 0.8 bump brings openid_connect 2.5, rack-oauth2 2.3,
swd 2.0, webfinger 2.1, and json-jwt 1.17 — the entire line now rides
the faraday 2 already in the bundle, and httpclient leaves the lock.
json-jwt 1.16.1's padding-oracle removal comes with it.

Every changelog was read end to end before the bump: the strategy's
option surface is unchanged for our config, rack-oauth2 2.x keeps the
jwt_bearer/private_key client auth the login.gov plan needs, and the
2.x error classes preserve their contracts. No application code needed
to change; the full suite runs green untouched.

Proven live against a real Okta org in the browser: discovery, code
exchange, id_token session storage, and RP-initiated provider logout
through the discovered end_session_endpoint.

Authored by: Aaron Lippold<lippold@gmail.com>
The 3.0 line's only removals — the deprecated top-level ca_file and
ssl_version options and their adaptor translation — are options this app
never passes; the whole configured surface (host, port, uid, encryption,
bind_dn, password, base) carries over unchanged, so no env var or
settings-template change is needed. 3.0.1's hardened inspection
redaction (bind passwords and TLS keys filtered from nested output)
comes along as a security win, and the trusted-header SSO tightening
landed already via 2.3.4.

Proven live in the browser against the same test-openldap fixture image
the system specs document: form sign-in, user provisioning from the
directory's mail attribute, and sign-out.

Authored by: Aaron Lippold<lippold@gmail.com>
Checking Remember me on an LDAP or OIDC login has thrown NoMethodError
since v2.3.1: the callback controller calls remember_me(user) but
Devise::Controllers::Rememberable is opt-in and was never included —
the module exists precisely for controllers that sign users in from a
callback. The catch-all rescue converted every occurrence into a
generic error toast, and the covering spec only grepped the controller
source for the word remember_me, so it never noticed. That spec is
replaced with request specs that drive the real callback route across
both delivery paths (form params at the callback; a query param stashed
through the provider round-trip — OmniAuth only carries request.GET into
omniauth.params, which the old comment got wrong) plus the negative
case.

An unreachable directory also surfaced to users as the raw humanized
error type — 'Ldap error'. The failure message now translates known
types through devise.omniauth_callbacks.reasons, telling the user the
server was unreachable and their credentials were never checked;
unknown types keep Devise's humanized fallback. Unused failure-mock
helpers that overrode OmniAuth's global on_failure without restoring it
are deleted, and the rescue handlers are no longer public methods.

Authored by: Aaron Lippold<lippold@gmail.com>
Vulcan uses inspec-core purely as a library — one code-generation site
building InSpec control source through mitre-inspec-objects, which needs
inspec-core for a single class. The pin moves that dependency to the
Cinc Project's community gem server, which builds current InSpec
releases from source, bringing the 5.23 line up to 7.1.7.

The resolution is narrow: only inspec-core comes from the cinc remote;
chef-config and chef-utils step to the 19.x line from rubygems.org
(kept alive by the chef-licensing and chef-telemetry dependencies), and
the handful of new arrivals are small. Control generation is proven
in-process and live through the rule editor: an edited test script
round-trips through save and comes back embedded in the regenerated
control.

Authored by: Aaron Lippold<lippold@gmail.com>
Yarn audit stood at 168 findings (2 critical, 96 high). This sweep
takes it to 4, all accepted with reasons: image-size has no patch in
existence (dev-only, esbuild-vue's build chain processing our own
assets), and vue/vue-template-compiler advisories are inherent to the
Vue 2 line whose migration is already the project's plan.

- axios 1.12.2 -> 1.19.0, clearing eleven highs; the supply-chain
  ceiling resolution stays, raised to the new version
- dompurify -> 3.4.13, clearing fourteen sanitizer advisories
- vitest/coverage -> 4.1.10 (critical) and vite -> 7.3.6, worked around
  yarn 1's nested-linking bug with a vite resolution; concurrently and
  its shell-quote (critical) refreshed
- @rails/actioncable and @rails/activestorage -> 8.1.301, matching the
  app's Rails 8.1.3.1
- lodash, marked, sass, esbuild, esbuild-sass-plugin, easymde, shiki,
  @eslint/js refreshed within their ranges
- scoped resolutions pin patched transitives under toolchains whose own
  pins lag: minimatch (3.x and 9.x lines separately), picomatch,
  js-yaml, ajv, flatted, ws, rollup, piscina, js-cookie

Verified: yarn test:unit 2317/2317, eslint --max-warnings 0 clean,
esbuild build clean.

Authored by: Aaron Lippold<lippold@gmail.com>
The documentation site's own yarn tree stood at 68 audit findings.
This sweep takes it to zero: vitepress 2.0.0-alpha.11 -> alpha.19
(carrying the patched vite line), mermaid -> 10.9.8 with its dompurify,
uuid, and lodash-es transitives refreshed, svgo/vue/image-optimizer
in-range refreshes, and one resolution pinning the patched
mdast-util-to-hast under vitepress's shiki chain.

Verified: vitepress build renders every page clean.

Authored by: Aaron Lippold<lippold@gmail.com>
@aaronlippold
aaronlippold temporarily deployed to vulcan-rails-8-1-upgrad-gv6g6d August 9, 2026 01:30 Inactive
The same-SRG lookup already has the component loaded by its
before_action; excluding by the record's own id drops the redundant
params read and states the intent. Behavior identical — the routed
path id and the loaded record are the same value by construction.

Authored by: Aaron Lippold<lippold@gmail.com>
@aaronlippold
aaronlippold temporarily deployed to vulcan-rails-8-1-upgrad-gv6g6d August 9, 2026 01:40 Inactive
@sonarqubecloud

sonarqubecloud Bot commented Aug 9, 2026

Copy link
Copy Markdown

@aaronlippold
aaronlippold merged commit 5d1578e into master Aug 9, 2026
18 checks passed
@aaronlippold
aaronlippold deleted the rails-8.1-upgrade branch August 9, 2026 02:29
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.

2 participants