VYPR
Moderate severityNVD Advisory· Published Nov 14, 2019· Updated Aug 5, 2024

CVE-2019-18978

CVE-2019-18978

Description

An issue was discovered in the rack-cors (aka Rack CORS Middleware) gem before 1.0.4 for Ruby. It allows ../ directory traversal to access private resources because resource matching does not ensure that pathnames are in a canonical format.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CVE-2019-18978 is a directory traversal vulnerability in rack-cors before 1.0.4 allowing access to private resources via ../ sequences.

Vulnerability

Overview CVE-2019-18978 is a directory traversal vulnerability in the rack-cors gem for Ruby, versions before 1.0.4. The issue arises because the middleware's resource matching does not canonicalize pathnames before checking them against CORS rules [1]. This allows an attacker to use '../' sequences in the URL path to bypass intended resource restrictions.

Exploitation

Details The vulnerability can be exploited by sending a specially crafted HTTP request with path traversal sequences (e.g., ../) in the PATH_INFO. The rack-cors middleware would match the request against its resource configuration without first resolving the path to a canonical form [3]. An attacker does not need authentication if the targeted resource is otherwise accessible; the flaw is in the CORS enforcement logic itself.

Impact

Successful exploitation allows an attacker to read arbitrary files or access private resources that should be protected by CORS policies [1]. This can lead to information disclosure, including sensitive data stored on the server [4]. The impact is limited to resources that the web application can serve, but it expands the attack surface beyond what the CORS rules intended.

Mitigation

The vulnerability is fixed in rack-cors version 1.0.4. Users should upgrade immediately [2]. The fix involves unescaping and resolving the path using Rack::Utils.clean_path_info and Rack::Utils.unescape_path before performing resource checks [3]. Ubuntu also released a security update (USN-4571-1) for the ruby-rack-cors package [4].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
rack-corsRubyGems
< 1.0.41.0.4

Affected products

1

Patches

1
e4d4fc362a43

Unescape and resolve paths before resource checks

https://github.com/cyu/rack-corsCalvin YuNov 14, 2019via ghsa
5 files changed · +29 10
  • CHANGELOG.md+4 0 modified
    @@ -1,6 +1,10 @@
     # Change Log
     All notable changes to this project will be documented in this file.
     
    +## 1.0.4 - 2019-11-13
    +### Security
    +- Escape and resolve path before evaluating resource rules (thanks to Colby Morgan)
    +
     ## 1.0.3 - 2019-03-24
     ### Changed
     - Don't send 'Content-Type' header with pre-flight requests
    
  • lib/rack/cors.rb+17 9 modified
    @@ -64,24 +64,27 @@ def allow(&block)
         def call(env)
           env[HTTP_ORIGIN] ||= env[HTTP_X_ORIGIN] if env[HTTP_X_ORIGIN]
     
    +      path = evaluate_path(env)
    +
           add_headers = nil
           if env[HTTP_ORIGIN]
             debug(env) do
               [ 'Incoming Headers:',
                 "  Origin: #{env[HTTP_ORIGIN]}",
    +            "  Path-Info: #{path}",
                 "  Access-Control-Request-Method: #{env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]}",
                 "  Access-Control-Request-Headers: #{env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]}"
                 ].join("\n")
             end
             if env[REQUEST_METHOD] == OPTIONS and env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
    -          headers = process_preflight(env)
    +          headers = process_preflight(env, path)
               debug(env) do
                 "Preflight Headers:\n" +
                     headers.collect{|kv| "  #{kv.join(': ')}"}.join("\n")
               end
               return [200, headers, []]
             else
    -          add_headers = process_cors(env)
    +          add_headers = process_cors(env, path)
             end
           else
             Result.miss(env, Result::MISS_NO_ORIGIN)
    @@ -90,7 +93,7 @@ def call(env)
           # This call must be done BEFORE calling the app because for some reason
           # env[PATH_INFO] gets changed after that and it won't match. (At least
           # in rails 4.1.6)
    -      vary_resource = resource_for_path(env[PATH_INFO])
    +      vary_resource = resource_for_path(path)
     
           status, headers, body = @app.call env
     
    @@ -147,14 +150,20 @@ def select_logger(env)
             end
           end
     
    +      def evaluate_path(env)
    +        path = env[PATH_INFO]
    +        path = Rack::Utils.clean_path_info(Rack::Utils.unescape_path(path)) if path
    +        path
    +      end
    +
           def all_resources
             @all_resources ||= []
           end
     
    -      def process_preflight(env)
    +      def process_preflight(env, path)
             result = Result.preflight(env)
     
    -        resource, error = match_resource(env)
    +        resource, error = match_resource(path, env)
             unless resource
               result.miss(error)
               return {}
    @@ -163,8 +172,8 @@ def process_preflight(env)
             return resource.process_preflight(env, result)
           end
     
    -      def process_cors(env)
    -        resource, error = match_resource(env)
    +      def process_cors(env, path)
    +        resource, error = match_resource(path, env)
             if resource
               Result.hit(env)
               cors = resource.to_headers(env)
    @@ -185,8 +194,7 @@ def resource_for_path(path_info)
             nil
           end
     
    -      def match_resource(env)
    -        path   = env[PATH_INFO]
    +      def match_resource(path, env)
             origin = env[HTTP_ORIGIN]
     
             origin_matched = false
    
  • lib/rack/cors/version.rb+1 1 modified
    @@ -1,5 +1,5 @@
     module Rack
       class Cors
    -    VERSION = "1.0.3"
    +    VERSION = "1.0.4"
       end
     end
    
  • test/unit/cors_test.rb+6 0 modified
    @@ -146,6 +146,12 @@ def load_app(name, options = {})
         last_response.headers['Vary'].must_equal 'Origin, Host'
       end
     
    +  it "decode URL and resolve paths before resource matching" do
    +    header 'Origin', 'http://localhost:3000'
    +    get '/public/a/..%2F..%2Fprivate/stuff'
    +    last_response.wont_render_cors_success
    +  end
    +
       describe 'with array of upstream Vary headers' do
         let(:app) { load_app('test', { proxy: true }) }
     
    
  • test/unit/test.ru+1 0 modified
    @@ -41,6 +41,7 @@ use Rack::Cors do
       allow do
         origins '*'
         resource '/public'
    +    resource '/public/*'
         resource '/public_without_credentials', :credentials => false
       end
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.