VYPR
High severity7.5NVD Advisory· Published Dec 30, 2025· Updated Apr 16, 2026

CVE-2025-61594

CVE-2025-61594

Description

URI is a module providing classes to handle Uniform Resource Identifiers. In versions 0.12.4 and earlier (bundled in Ruby 3.2 series) 0.13.2 and earlier (bundled in Ruby 3.3 series), 1.0.3 and earlier (bundled in Ruby 3.4 series), when using the + operator to combine URIs, sensitive information like passwords from the original URI can be leaked, violating RFC3986 and making applications vulnerable to credential exposure. This is a a bypass for the fix to CVE-2025-27221 that can expose user credentials. This issue has been fixed in versions 0.12.5, 0.13.3 and 1.0.4.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
uriRubyGems
< 0.12.50.12.5
uriRubyGems
>= 0.13.0, < 0.13.30.13.3
uriRubyGems
>= 1.0.0, < 1.0.41.0.4

Affected products

1
  • cpe:2.3:a:ruby-lang:uri:*:*:*:*:*:ruby:*:*
    Range: <0.12.5

Patches

3
d3116ca66a3b

Merge branch 'CVE-2025-61594-3-4' into HEAD

https://github.com/ruby/uriHiroshi SHIBATAOct 7, 2025via ghsa
2 files changed · +31 13
  • lib/uri/generic.rb+21 8 modified
    @@ -186,18 +186,18 @@ def initialize(scheme,
     
           if arg_check
             self.scheme = scheme
    -        self.userinfo = userinfo
             self.hostname = host
             self.port = port
    +        self.userinfo = userinfo
             self.path = path
             self.query = query
             self.opaque = opaque
             self.fragment = fragment
           else
             self.set_scheme(scheme)
    -        self.set_userinfo(userinfo)
             self.set_host(host)
             self.set_port(port)
    +        self.set_userinfo(userinfo)
             self.set_path(path)
             self.query = query
             self.set_opaque(opaque)
    @@ -511,7 +511,7 @@ def set_userinfo(user, password = nil)
             user, password = split_userinfo(user)
           end
           @user     = user
    -      @password = password if password
    +      @password = password
     
           [@user, @password]
         end
    @@ -522,7 +522,7 @@ def set_userinfo(user, password = nil)
         # See also URI::Generic.user=.
         #
         def set_user(v)
    -      set_userinfo(v, @password)
    +      set_userinfo(v, nil)
           v
         end
         protected :set_user
    @@ -574,6 +574,12 @@ def password
           @password
         end
     
    +    # Returns the authority info (array of user, password, host and
    +    # port), if any is set.  Or returns +nil+.
    +    def authority
    +      return @user, @password, @host, @port if @user || @password || @host || @port
    +    end
    +
         # Returns the user component after URI decoding.
         def decoded_user
           URI.decode_uri_component(@user) if @user
    @@ -615,6 +621,13 @@ def set_host(v)
         end
         protected :set_host
     
    +    # Protected setter for the authority info (+user+, +password+, +host+
    +    # and +port+).  If +port+ is +nil+, +default_port+ will be set.
    +    #
    +    protected def set_authority(user, password, host, port = nil)
    +      @user, @password, @host, @port = user, password, host, port || self.default_port
    +    end
    +
         #
         # == Args
         #
    @@ -639,6 +652,7 @@ def set_host(v)
         def host=(v)
           check_host(v)
           set_host(v)
    +      set_userinfo(nil)
           v
         end
     
    @@ -729,6 +743,7 @@ def set_port(v)
         def port=(v)
           check_port(v)
           set_port(v)
    +      set_userinfo(nil)
           port
         end
     
    @@ -1121,7 +1136,7 @@ def merge(oth)
     
           base = self.dup
     
    -      authority = rel.userinfo || rel.host || rel.port
    +      authority = rel.authority
     
           # RFC2396, Section 5.2, 2)
           if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
    @@ -1134,9 +1149,7 @@ def merge(oth)
     
           # RFC2396, Section 5.2, 4)
           if authority
    -        base.set_userinfo(rel.userinfo)
    -        base.set_host(rel.host)
    -        base.set_port(rel.port || base.default_port)
    +        base.set_authority(*authority)
             base.set_path(rel.path)
           elsif base.path && rel.path
             base.set_path(merge_path(base.path, rel.path))
    
  • test/uri/test_generic.rb+10 5 modified
    @@ -283,6 +283,9 @@ def test_merge_authority
         u0 = URI.parse('http://new.example.org/path')
         u1 = u.merge('//new.example.org/path')
         assert_equal(u0, u1)
    +    u0 = URI.parse('http://other@example.net')
    +    u1 = u.merge('//other@example.net')
    +    assert_equal(u0, u1)
       end
     
       def test_route
    @@ -748,17 +751,18 @@ def test_join
       def test_set_component
         uri = URI.parse('http://foo:bar@baz')
         assert_equal('oof', uri.user = 'oof')
    -    assert_equal('http://oof:bar@baz', uri.to_s)
    +    assert_equal('http://oof@baz', uri.to_s)
         assert_equal('rab', uri.password = 'rab')
         assert_equal('http://oof:rab@baz', uri.to_s)
         assert_equal('foo', uri.userinfo = 'foo')
    -    assert_equal('http://foo:rab@baz', uri.to_s)
    +    assert_equal('http://foo@baz', uri.to_s)
         assert_equal(['foo', 'bar'], uri.userinfo = ['foo', 'bar'])
         assert_equal('http://foo:bar@baz', uri.to_s)
         assert_equal(['foo'], uri.userinfo = ['foo'])
    -    assert_equal('http://foo:bar@baz', uri.to_s)
    +    assert_equal('http://foo@baz', uri.to_s)
         assert_equal('zab', uri.host = 'zab')
    -    assert_equal('http://foo:bar@zab', uri.to_s)
    +    assert_equal('http://zab', uri.to_s)
    +    uri.userinfo = ['foo', 'bar']
         uri.port = ""
         assert_nil(uri.port)
         uri.port = "80"
    @@ -768,7 +772,8 @@ def test_set_component
         uri.port = " 080 "
         assert_equal(80, uri.port)
         assert_equal(8080, uri.port = 8080)
    -    assert_equal('http://foo:bar@zab:8080', uri.to_s)
    +    assert_equal('http://zab:8080', uri.to_s)
    +    uri = URI.parse('http://foo:bar@zab:8080')
         assert_equal('/', uri.path = '/')
         assert_equal('http://foo:bar@zab:8080/', uri.to_s)
         assert_equal('a=1', uri.query = 'a=1')
    
20157e3e29b1

Merge branch 'CVE-2025-61594-3-3' into v0-13

https://github.com/ruby/uriHiroshi SHIBATAOct 7, 2025via ghsa
2 files changed · +31 13
  • lib/uri/generic.rb+21 8 modified
    @@ -186,18 +186,18 @@ def initialize(scheme,
     
           if arg_check
             self.scheme = scheme
    -        self.userinfo = userinfo
             self.hostname = host
             self.port = port
    +        self.userinfo = userinfo
             self.path = path
             self.query = query
             self.opaque = opaque
             self.fragment = fragment
           else
             self.set_scheme(scheme)
    -        self.set_userinfo(userinfo)
             self.set_host(host)
             self.set_port(port)
    +        self.set_userinfo(userinfo)
             self.set_path(path)
             self.query = query
             self.set_opaque(opaque)
    @@ -511,7 +511,7 @@ def set_userinfo(user, password = nil)
             user, password = split_userinfo(user)
           end
           @user     = user
    -      @password = password if password
    +      @password = password
     
           [@user, @password]
         end
    @@ -522,7 +522,7 @@ def set_userinfo(user, password = nil)
         # See also URI::Generic.user=.
         #
         def set_user(v)
    -      set_userinfo(v, @password)
    +      set_userinfo(v, nil)
           v
         end
         protected :set_user
    @@ -574,6 +574,12 @@ def password
           @password
         end
     
    +    # Returns the authority info (array of user, password, host and
    +    # port), if any is set.  Or returns +nil+.
    +    def authority
    +      return @user, @password, @host, @port if @user || @password || @host || @port
    +    end
    +
         # Returns the user component after URI decoding.
         def decoded_user
           URI.decode_uri_component(@user) if @user
    @@ -615,6 +621,13 @@ def set_host(v)
         end
         protected :set_host
     
    +    # Protected setter for the authority info (+user+, +password+, +host+
    +    # and +port+).  If +port+ is +nil+, +default_port+ will be set.
    +    #
    +    protected def set_authority(user, password, host, port = nil)
    +      @user, @password, @host, @port = user, password, host, port || self.default_port
    +    end
    +
         #
         # == Args
         #
    @@ -639,6 +652,7 @@ def set_host(v)
         def host=(v)
           check_host(v)
           set_host(v)
    +      set_userinfo(nil)
           v
         end
     
    @@ -729,6 +743,7 @@ def set_port(v)
         def port=(v)
           check_port(v)
           set_port(v)
    +      set_userinfo(nil)
           port
         end
     
    @@ -1121,7 +1136,7 @@ def merge(oth)
     
           base = self.dup
     
    -      authority = rel.userinfo || rel.host || rel.port
    +      authority = rel.authority
     
           # RFC2396, Section 5.2, 2)
           if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
    @@ -1134,9 +1149,7 @@ def merge(oth)
     
           # RFC2396, Section 5.2, 4)
           if authority
    -        base.set_userinfo(rel.userinfo)
    -        base.set_host(rel.host)
    -        base.set_port(rel.port || base.default_port)
    +        base.set_authority(*authority)
             base.set_path(rel.path)
           elsif base.path && rel.path
             base.set_path(merge_path(base.path, rel.path))
    
  • test/uri/test_generic.rb+10 5 modified
    @@ -272,6 +272,9 @@ def test_merge_authority
         u0 = URI.parse('http://new.example.org/path')
         u1 = u.merge('//new.example.org/path')
         assert_equal(u0, u1)
    +    u0 = URI.parse('http://other@example.net')
    +    u1 = u.merge('//other@example.net')
    +    assert_equal(u0, u1)
       end
     
       def test_route
    @@ -737,17 +740,18 @@ def test_join
       def test_set_component
         uri = URI.parse('http://foo:bar@baz')
         assert_equal('oof', uri.user = 'oof')
    -    assert_equal('http://oof:bar@baz', uri.to_s)
    +    assert_equal('http://oof@baz', uri.to_s)
         assert_equal('rab', uri.password = 'rab')
         assert_equal('http://oof:rab@baz', uri.to_s)
         assert_equal('foo', uri.userinfo = 'foo')
    -    assert_equal('http://foo:rab@baz', uri.to_s)
    +    assert_equal('http://foo@baz', uri.to_s)
         assert_equal(['foo', 'bar'], uri.userinfo = ['foo', 'bar'])
         assert_equal('http://foo:bar@baz', uri.to_s)
         assert_equal(['foo'], uri.userinfo = ['foo'])
    -    assert_equal('http://foo:bar@baz', uri.to_s)
    +    assert_equal('http://foo@baz', uri.to_s)
         assert_equal('zab', uri.host = 'zab')
    -    assert_equal('http://foo:bar@zab', uri.to_s)
    +    assert_equal('http://zab', uri.to_s)
    +    uri.userinfo = ['foo', 'bar']
         uri.port = ""
         assert_nil(uri.port)
         uri.port = "80"
    @@ -757,7 +761,8 @@ def test_set_component
         uri.port = " 080 "
         assert_equal(80, uri.port)
         assert_equal(8080, uri.port = 8080)
    -    assert_equal('http://foo:bar@zab:8080', uri.to_s)
    +    assert_equal('http://zab:8080', uri.to_s)
    +    uri = URI.parse('http://foo:bar@zab:8080')
         assert_equal('/', uri.path = '/')
         assert_equal('http://foo:bar@zab:8080/', uri.to_s)
         assert_equal('a=1', uri.query = 'a=1')
    
7e521b2da083

Merge branch 'CVE-2025-61594-3-2' into v0-12

https://github.com/ruby/uriHiroshi SHIBATAOct 7, 2025via ghsa
2 files changed · +31 13
  • lib/uri/generic.rb+21 8 modified
    @@ -186,18 +186,18 @@ def initialize(scheme,
     
           if arg_check
             self.scheme = scheme
    -        self.userinfo = userinfo
             self.hostname = host
             self.port = port
    +        self.userinfo = userinfo
             self.path = path
             self.query = query
             self.opaque = opaque
             self.fragment = fragment
           else
             self.set_scheme(scheme)
    -        self.set_userinfo(userinfo)
             self.set_host(host)
             self.set_port(port)
    +        self.set_userinfo(userinfo)
             self.set_path(path)
             self.query = query
             self.set_opaque(opaque)
    @@ -511,7 +511,7 @@ def set_userinfo(user, password = nil)
             user, password = split_userinfo(user)
           end
           @user     = user
    -      @password = password if password
    +      @password = password
     
           [@user, @password]
         end
    @@ -522,7 +522,7 @@ def set_userinfo(user, password = nil)
         # See also URI::Generic.user=.
         #
         def set_user(v)
    -      set_userinfo(v, @password)
    +      set_userinfo(v, nil)
           v
         end
         protected :set_user
    @@ -574,6 +574,12 @@ def password
           @password
         end
     
    +    # Returns the authority info (array of user, password, host and
    +    # port), if any is set.  Or returns +nil+.
    +    def authority
    +      return @user, @password, @host, @port if @user || @password || @host || @port
    +    end
    +
         # Returns the user component after URI decoding.
         def decoded_user
           URI.decode_uri_component(@user) if @user
    @@ -615,6 +621,13 @@ def set_host(v)
         end
         protected :set_host
     
    +    # Protected setter for the authority info (+user+, +password+, +host+
    +    # and +port+).  If +port+ is +nil+, +default_port+ will be set.
    +    #
    +    protected def set_authority(user, password, host, port = nil)
    +      @user, @password, @host, @port = user, password, host, port || self.default_port
    +    end
    +
         #
         # == Args
         #
    @@ -639,6 +652,7 @@ def set_host(v)
         def host=(v)
           check_host(v)
           set_host(v)
    +      set_userinfo(nil)
           v
         end
     
    @@ -729,6 +743,7 @@ def set_port(v)
         def port=(v)
           check_port(v)
           set_port(v)
    +      set_userinfo(nil)
           port
         end
     
    @@ -1121,7 +1136,7 @@ def merge(oth)
     
           base = self.dup
     
    -      authority = rel.userinfo || rel.host || rel.port
    +      authority = rel.authority
     
           # RFC2396, Section 5.2, 2)
           if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
    @@ -1134,9 +1149,7 @@ def merge(oth)
     
           # RFC2396, Section 5.2, 4)
           if authority
    -        base.set_userinfo(rel.userinfo)
    -        base.set_host(rel.host)
    -        base.set_port(rel.port || base.default_port)
    +        base.set_authority(*authority)
             base.set_path(rel.path)
           elsif base.path && rel.path
             base.set_path(merge_path(base.path, rel.path))
    
  • test/uri/test_generic.rb+10 5 modified
    @@ -272,6 +272,9 @@ def test_merge_authority
         u0 = URI.parse('http://new.example.org/path')
         u1 = u.merge('//new.example.org/path')
         assert_equal(u0, u1)
    +    u0 = URI.parse('http://other@example.net')
    +    u1 = u.merge('//other@example.net')
    +    assert_equal(u0, u1)
       end
     
       def test_route
    @@ -737,17 +740,18 @@ def test_join
       def test_set_component
         uri = URI.parse('http://foo:bar@baz')
         assert_equal('oof', uri.user = 'oof')
    -    assert_equal('http://oof:bar@baz', uri.to_s)
    +    assert_equal('http://oof@baz', uri.to_s)
         assert_equal('rab', uri.password = 'rab')
         assert_equal('http://oof:rab@baz', uri.to_s)
         assert_equal('foo', uri.userinfo = 'foo')
    -    assert_equal('http://foo:rab@baz', uri.to_s)
    +    assert_equal('http://foo@baz', uri.to_s)
         assert_equal(['foo', 'bar'], uri.userinfo = ['foo', 'bar'])
         assert_equal('http://foo:bar@baz', uri.to_s)
         assert_equal(['foo'], uri.userinfo = ['foo'])
    -    assert_equal('http://foo:bar@baz', uri.to_s)
    +    assert_equal('http://foo@baz', uri.to_s)
         assert_equal('zab', uri.host = 'zab')
    -    assert_equal('http://foo:bar@zab', uri.to_s)
    +    assert_equal('http://zab', uri.to_s)
    +    uri.userinfo = ['foo', 'bar']
         uri.port = ""
         assert_nil(uri.port)
         uri.port = "80"
    @@ -757,7 +761,8 @@ def test_set_component
         uri.port = " 080 "
         assert_equal(80, uri.port)
         assert_equal(8080, uri.port = 8080)
    -    assert_equal('http://foo:bar@zab:8080', uri.to_s)
    +    assert_equal('http://zab:8080', uri.to_s)
    +    uri = URI.parse('http://foo:bar@zab:8080')
         assert_equal('/', uri.path = '/')
         assert_equal('http://foo:bar@zab:8080/', uri.to_s)
         assert_equal('a=1', uri.query = 'a=1')
    

Vulnerability mechanics

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

References

11

News mentions

0

No linked articles in our index yet.