GHSA-97jw-64cj-jc58 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 CodeValidated 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:Root CauseNormal 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: rawCollection rendering then amplifies the issue by calling `.html_safe` on the joined result.Proof of ConceptRun from the repository root:Observed output:The control case confirms that normal `#call` output is escaped. The `around_render` cases confirm that the same payload is emitted raw.Exploit ScenarioA 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:If `message` is user-controlled, scriptable HTML reaches the browser.ImpactSuccessful exploitation allows XSS in applications using affected components. Depending on application context, impact can include:session or token theft where cookies/tokens are accessibleauthenticated actions as the victimCSRF bypass through same-origin script executionexfiltration of page datacredential phishing or UI redress inside trusted application originThe collection path is particularly risky because it converts the joined raw output to `html_safe`, which can suppress later escaping.PreconditionsThe application defines or uses a component overriding `around_render`.`around_render` returns or wraps attacker-influenced HTML-unsafe content.The component is rendered in a browser-visible response.Higher impact when rendered through `ViewComponent::Collection` or controller/direct rendering.Chaining PotentialThis finding can chain with:preview routes or examples that accept URL parameters and render componentsunsafe markdown or CMS content rendered inside `around_render`CSP-disabled preview routesapplications that expose admin-only pages containing affected componentsRemediationApply the same HTML-safety enforcement to `around_render` return values that is applied to normal inline `#call` output.Possible approaches:Wrap the result of `around_render` with `__vc_maybe_escape_html` when the current template is HTML.Require `around_render` to return an `ActiveSupport::SafeBuffer` to opt into raw HTML.In collection rendering, avoid blindly calling `.html_safe` on joined component outputs unless each item has been normalized through the same safety boundary.
CVSS v3.1 score: 8.7