Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-54497

CVE-2026-54497: ViewComponent Privilege Escalation Flaw

CVE-2026-54497 is a privilege escalation vulnerability in ViewComponent for Ruby on Rails that causes components to retain stale render context, potentially exposing privileged UI to unauthorized users. This article covers technical details, affected versions 4.0.0-4.11.x, impact, and mitigation steps.

Updated:

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.

ruby
# 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.

ruby
# 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.
bash
# 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.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.