VYPR
High severityNVD Advisory· Published Apr 29, 2020· Updated Aug 4, 2024

Authentication and extension bypass in Faye

CVE-2020-11020

Description

Faye (NPM, RubyGem) versions greater than 0.5.0 and before 1.0.4, 1.1.3 and 1.2.5, has the potential for authentication bypass in the extension system. The vulnerability allows any client to bypass checks put in place by server-side extensions, by appending extra segments to the message channel. It is patched in versions 1.0.4, 1.1.3 and 1.2.5.

AI Insight

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

Faye (NPM, RubyGem) before 1.0.4, 1.1.3 and 1.2.5 allows authentication bypass by appending extra segments to meta channels.

Vulnerability

CVE-2020-11020 is an authentication bypass in Faye, a simple pub/sub messaging library for web applications. The vulnerability affects NPM and RubyGem versions greater than 0.5.0 and before 1.0.4, 1.1.3 and 1.2.5 [1]. It resides in Faye's extension system, which allows server-side extensions to check and authorize messages. A flaw in how the server recognizes special /meta/* channels means that any client can bypass these checks by appending extra segments to the message channel [2].

Exploitation

To exploit the vulnerability, a client sends a message to a channel like /meta/subscribe/x instead of the exact /meta/subscribe. The server treats any channel that is a prefix-match for one of the special channels (/meta/handshake, /meta/connect, /meta/subscribe, /meta/unsubscribe, /meta/disconnect) as though it were an exact match [2]. As a result, the extension's channel check (which often verifies only the exact channel name) is skipped, but the message is still processed as a valid subscription or connection event [1][2]. No authentication is required to perform this attack; any client can submit such a channel.

Impact

An unauthenticated attacker can subscribe to arbitrary channels without providing the necessary credentials required by server-side extensions [1][2]. This could allow unauthorized access to sensitive data streams or event notifications that the extension intended to protect, leading to information disclosure or privilege escalation within the pub/sub system.

Mitigation

The vulnerability has been patched in Faye versions 1.0.4, 1.1.3, and 1.2.5 [1][2]. Users of the affected versions should upgrade immediately. There are no known workarounds, as the fix addresses the core channel-matching logic. The vulnerability was reported on April 20, 2020, and existed since version 0.5.0 when extensions were introduced [2].

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
fayeRubyGems
>= 0.5.0, < 1.0.41.0.4
fayeRubyGems
>= 1.1.0, < 1.1.31.1.3
fayeRubyGems
>= 1.2.0, < 1.2.51.2.5

Affected products

2
  • ghsa-coords
    Range: >= 0.5.0, < 1.0.4
  • faye/Fayev5
    Range: >= 0.5.0, < 1.0.4

Patches

1
65d297d341b6

Strict meta channel recognition in server

https://github.com/faye/fayeJames CoglanApr 25, 2020via ghsa
4 files changed · +98 8
  • lib/faye/protocol/server.rb+12 4 modified
    @@ -6,8 +6,6 @@ class Server
         include Logging
         include Extensible
     
    -    META_METHODS = %w[handshake connect disconnect subscribe unsubscribe]
    -
         attr_reader :engine
     
         def initialize(options = {})
    @@ -107,9 +105,9 @@ def handle(message, local = false, &callback)
         end
     
         def handle_meta(message, local, &callback)
    -      method = Channel.parse(message['channel'])[1]
    +      method = method_for(message)
     
    -      unless META_METHODS.include?(method)
    +      unless method
             response = make_response(message)
             response['error'] = Faye::Error.channel_forbidden(message['channel'])
             response['successful'] = false
    @@ -123,6 +121,16 @@ def handle_meta(message, local, &callback)
           end
         end
     
    +    def method_for(message)
    +      case message['channel']
    +      when Channel::HANDSHAKE   then :handshake
    +      when Channel::CONNECT     then :connect
    +      when Channel::SUBSCRIBE   then :subscribe
    +      when Channel::UNSUBSCRIBE then :unsubscribe
    +      when Channel::DISCONNECT  then :disconnect
    +      end
    +    end
    +
         def advize(response, connection_type)
           return unless [Channel::HANDSHAKE, Channel::CONNECT].include?(response['channel'])
     
    
  • spec/javascript/server/extensions_spec.js+36 0 modified
    @@ -36,6 +36,42 @@ jstest.describe("Server extensions", function() { with(this) {
         }})
       }})
     
    +  describe("with subscription auth installed", function() { with(this) {
    +    before(function() { with(this) {
    +      var extension = {
    +        incoming: function(message, callback) {
    +          if (message.channel === "/meta/subscribe" && !message.auth) {
    +            message.error = "Invalid auth"
    +          }
    +          callback(message)
    +        }
    +      }
    +      server.addExtension(extension)
    +    }})
    +
    +    it("does not subscribe using the intended channel", function() { with(this) {
    +      var message = {
    +        channel: "/meta/subscribe",
    +        clientId: "fakeclientid",
    +        subscription: "/foo"
    +      }
    +      stub(engine, "clientExists").yields([true])
    +      expect(engine, "subscribe").exactly(0)
    +      server.process(message, false, function() {})
    +    }})
    +
    +    it("does not subscribe using an extended channel", function() { with(this) {
    +      var message = {
    +        channel: "/meta/subscribe/x",
    +        clientId: "fakeclientid",
    +        subscription: "/foo"
    +      }
    +      stub(engine, "clientExists").yields([true])
    +      expect(engine, "subscribe").exactly(0)
    +      server.process(message, false, function() {})
    +    }})
    +  }})
    +
       describe("with an outgoing extension installed", function() { with(this) {
         before(function() { with(this) {
           var extension = {
    
  • spec/ruby/server/extensions_spec.rb+36 0 modified
    @@ -40,6 +40,42 @@ def incoming(message, callback)
         end
       end
     
    +  describe "with subscription auth installed" do
    +    before do
    +      extension = Class.new do
    +        def incoming(message, callback)
    +          if message["channel"] == "/meta/subscribe" and !message["auth"]
    +            message["error"] = "Invalid auth"
    +          end
    +          callback.call(message)
    +        end
    +      end
    +      server.add_extension(extension.new)
    +    end
    +
    +    it "does not subscribe using the intended channel" do
    +      message = {
    +        "channel" => "/meta/subscribe",
    +        "clientId" => "fakeclientid",
    +        "subscription" => "/foo"
    +      }
    +      engine.stub(:client_exists).and_yield(true)
    +      engine.should_not_receive(:subscribe)
    +      server.process(message, false) {}
    +    end
    +
    +    it "does not subscribe using an extended channel" do
    +      message = {
    +        "channel" => "/meta/subscribe/x",
    +        "clientId" => "fakeclientid",
    +        "subscription" => "/foo"
    +      }
    +      engine.stub(:client_exists).and_yield(true)
    +      engine.should_not_receive(:subscribe)
    +      server.process(message, false) {}
    +    end
    +  end
    +
       describe "with an outgoing extension installed" do
         before do
           extension = Class.new do
    
  • src/protocol/server.js+14 4 modified
    @@ -13,8 +13,6 @@ var Class      = require('../util/class'),
         Socket     = require('./socket');
     
     var Server = Class({ className: 'Server',
    -  META_METHODS: ['handshake', 'connect', 'disconnect', 'subscribe', 'unsubscribe'],
    -
       initialize: function(options) {
         this._options  = options || {};
         var engineOpts = this._options.engine || {};
    @@ -120,10 +118,10 @@ var Server = Class({ className: 'Server',
       },
     
       _handleMeta: function(message, local, callback, context) {
    -    var method = Channel.parse(message.channel)[1],
    +    var method = this._methodFor(message),
             response;
     
    -    if (array.indexOf(this.META_METHODS, method) < 0) {
    +    if (method === null) {
           response = this._makeResponse(message);
           response.error = Error.channelForbidden(message.channel);
           response.successful = false;
    @@ -137,6 +135,18 @@ var Server = Class({ className: 'Server',
         }, this);
       },
     
    +  _methodFor: function(message) {
    +    var channel = message.channel;
    +
    +    if (channel === Channel.HANDSHAKE)   return 'handshake';
    +    if (channel === Channel.CONNECT)     return 'connect';
    +    if (channel === Channel.SUBSCRIBE)   return 'subscribe';
    +    if (channel === Channel.UNSUBSCRIBE) return 'unsubscribe';
    +    if (channel === Channel.DISCONNECT)  return 'disconnect';
    +
    +    return null;
    +  },
    +
       _advize: function(response, connectionType) {
         if (array.indexOf([Channel.HANDSHAKE, Channel.CONNECT], response.channel) < 0)
           return;
    

Vulnerability mechanics

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

References

5

News mentions

0

No linked articles in our index yet.