CVE-2026-54497 Overview
CVE-2026-54497 affects view_component, a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. Versions from 4.0.0 until 4.12.0 retain render-scoped objects on ViewComponent::Base instances across calls to render_in. When the same component, collection, or spacer instance is reused across requests, users, tenants, or threads, later renders can consume stale helpers, controller, request, view_flow, format/variant details, and slot child context from an earlier render. The flaw is tracked as a race condition weakness [CWE-362].
Critical Impact
Authorization-aware components can render privileged UI to lower-privileged users, generate links with stale Host headers, leak slot and helper state, and mix request context under concurrent rendering.
Affected Products
- view_component gem versions 4.0.0 through 4.11.x
- Ruby on Rails applications reusing ViewComponent::Base instances across requests
- Multi-tenant Rails deployments using shared component, collection, or spacer instances
Discovery Timeline
- 2026-07-17 - CVE-2026-54497 published to NVD
- 2026-07-23 - Last updated in NVD database
Technical Details for CVE-2026-54497
Vulnerability Analysis
The vulnerability stems from ViewComponent::Base#render_in conditionally assigning render-scoped instance variables using the ||= operator. When a component instance is reused, previously assigned values persist and short-circuit the reassignment. Subsequent renders operate on the prior request's @view_context, @lookup_context, @view_flow, @__vc_requested_details, and __vc_original_view_context.
In concurrent environments, threads sharing a component instance can interleave state from different requests. The impact includes privileged UI exposure to lower-privileged users, URL generation using a stale Host header, and cross-tenant slot or helper leakage.
Root Cause
The root cause is memoization using ||= on request-scoped state that must be reset per render. Because ViewComponent::Base instances are Ruby objects that callers may cache, memoized values from the first render_in call are reused instead of being refreshed from the current view_context.
Additionally, ViewComponent::Collection#render_in produced HTML via Array#join on rendered fragments. This did not reset per-render state on children, compounding the reuse issue for collections and spacer components.
Attack Vector
Exploitation requires an application pattern that reuses a ViewComponent::Base instance across requests, users, tenants, or threads. An attacker triggers an initial render that populates instance state, then causes a subsequent render on the same instance under a different security context. The second render inherits the first render's controller, request, helpers, and format details, producing output derived from the wrong principal.
# Security patch in lib/view_component/base.rb
def render_in(view_context, **_, &block)
self.class.__vc_compile(raise_errors: true)
__vc_reset_render_state!
@view_context = view_context
@old_virtual_path = view_context.instance_variable_get(:@virtual_path)
self.__vc_original_view_context = view_context
@output_buffer = view_context.output_buffer
@lookup_context = view_context.lookup_context
# For content_for
@view_flow = view_context.view_flow
# For i18n
@virtual_path ||= virtual_path
# Describes the inferred request constraints (locales, formats, variants)
@__vc_requested_details = @lookup_context.vc_requested_details
# For caching, such as #cache_if
@current_template = nil unless defined?(@current_template)
Source: GitHub Commit 6796b2e
The patch introduces __vc_reset_render_state! and replaces ||= with direct assignment for render-scoped fields, forcing state refresh on every render_in call.
# Security patch in lib/view_component/collection.rb
require "action_view/renderer/collection_renderer"
require "action_view/helpers/output_safety_helper"
module ViewComponent
class Collection
include Enumerable
include ActionView::Helpers::OutputSafetyHelper
def render_in(view_context, **_, &block)
rendered = components.map do |component|
component.render_in(view_context, &block)
end
safe_join(rendered, rendered_spacer(view_context))
end
Source: GitHub Commit 6796b2e
Detection Methods for CVE-2026-54497
Indicators of Compromise
- Rendered HTML containing UI elements or links belonging to a different user, tenant, or session than the authenticated principal.
- Generated URLs referencing a Host value inconsistent with the incoming request.
- Application logs showing shared ViewComponent::Base instance object IDs across distinct request contexts.
Detection Strategies
- Audit Rails codebases for patterns that cache ViewComponent::Base, ViewComponent::Collection, or spacer instances in constants, class variables, memoized helpers, or long-lived singletons.
- Perform dependency scanning against Gemfile.lock to identify view_component versions between 4.0.0 and 4.11.x.
- Add integration tests that render the same component instance twice with different controllers and assert that helpers and request context are isolated.
Monitoring Recommendations
- Alert on cross-tenant data appearing in HTTP response bodies when tenant isolation is expected.
- Monitor for anomalous Host header values in generated URLs written to outbound emails or notifications.
- Track upgrade status of the view_component gem across all Rails services in the fleet.
How to Mitigate CVE-2026-54497
Immediate Actions Required
- Upgrade view_component to version 4.12.0 or later, which resets render-scoped state on every render_in call.
- Inventory Rails applications for reuse of component instances across requests, threads, or tenants and refactor to instantiate components per render.
- Review authorization-aware components to confirm access checks do not depend on cached render state.
Patch Information
The issue is fixed in view_component version 4.12.0. The fix, published in GitHub Release v4.12.0 and detailed in GitHub Security Advisory GHSA-9h85-g7w3-rh49, calls __vc_reset_render_state! at the start of render_in and replaces conditional ||= memoization with direct assignment for @view_context, @lookup_context, @view_flow, @__vc_requested_details, and __vc_original_view_context. Collection rendering now uses safe_join instead of Array#join with html_safe.
Workarounds
- Instantiate a new component object for each render call rather than caching instances at the class or module level.
- Avoid storing component, collection, or spacer instances in thread-shared containers such as class variables or Rails initializers.
- Where upgrade is delayed, wrap render calls in helpers that build fresh instances per request using dup or explicit new.
# Configuration example: upgrade the vulnerable gem
bundle update view_component --conservative
bundle list | grep view_component
# Ensure version is >= 4.12.0
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

