VYPR
Moderate severityOSV Advisory· Published Dec 17, 2025· Updated Dec 17, 2025

DoS in Calls plugin via malformed UTF-8 in WebSocket request

CVE-2025-12689

Description

Mattermost versions 11.0.x <= 11.0.4, 10.12.x <= 10.12.2, 10.11.x <= 10.11.6 fail to check WebSocket request field for proper UTF-8 format, which allows attacker to crash Calls plug-in via sending malformed request.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/mattermost/mattermost-plugin-callsGo
< 1.11.01.11.0

Affected products

1

Patches

1
f68b41980e12

MM-66161 MM-66170 Improve validation of websocket request (#1092)

3 files changed · +136 0
  • server/client_message.go+25 0 modified
    @@ -39,3 +39,28 @@ func (m *clientMessage) ToJSON() ([]byte, error) {
     func (m *clientMessage) FromJSON(data []byte) error {
     	return json.Unmarshal(data, &m)
     }
    +
    +var validClientMessageTypes = map[string]bool{
    +	clientMessageTypeJoin:        true,
    +	clientMessageTypeLeave:       true,
    +	clientMessageTypeReconnect:   true,
    +	clientMessageTypeSDP:         true,
    +	clientMessageTypeICE:         true,
    +	clientMessageTypeMute:        true,
    +	clientMessageTypeUnmute:      true,
    +	clientMessageTypeVoiceOn:     true,
    +	clientMessageTypeVoiceOff:    true,
    +	clientMessageTypeScreenOn:    true,
    +	clientMessageTypeScreenOff:   true,
    +	clientMessageTypeRaiseHand:   true,
    +	clientMessageTypeUnraiseHand: true,
    +	clientMessageTypeReact:       true,
    +	clientMessageTypeCaption:     true,
    +	clientMessageTypeMetric:      true,
    +	clientMessageTypeCallState:   true,
    +	"ping":                       true, // Special case: standard ping message
    +}
    +
    +func isValidClientMessageType(msgType string) bool {
    +	return validClientMessageTypes[msgType]
    +}
    
  • server/websocket.go+14 0 modified
    @@ -10,6 +10,7 @@ import (
     	"strings"
     	"sync/atomic"
     	"time"
    +	"unicode/utf8"
     
     	"github.com/mattermost/mattermost-plugin-calls/server/batching"
     	"github.com/mattermost/mattermost-plugin-calls/server/public"
    @@ -1089,9 +1090,22 @@ func (p *Plugin) handleCallStateRequest(channelID, userID, connID string) error
     }
     
     func (p *Plugin) WebSocketMessageHasBeenPosted(connID, userID string, req *model.WebSocketRequest) {
    +	if !utf8.ValidString(req.Action) {
    +		p.LogError("invalid UTF-8 in action")
    +		return
    +	}
    +	if !strings.HasPrefix(req.Action, wsActionPrefix) {
    +		return
    +	}
     	var msg clientMessage
     	msg.Type = strings.TrimPrefix(req.Action, wsActionPrefix)
     
    +	// Validate message type against known valid types
    +	if !isValidClientMessageType(msg.Type) {
    +		p.LogError("invalid message type", "type", msg.Type)
    +		return
    +	}
    +
     	// This is the standard ping message handled by Mattermost server. Nothing to do here.
     	if msg.Type == "ping" {
     		return
    
  • server/websocket_test.go+97 0 modified
    @@ -669,6 +669,103 @@ func TestPublishWebSocketEvent(t *testing.T) {
     	})
     }
     
    +func TestWebSocketMessageHasBeenPostedUTF8Validation(t *testing.T) {
    +	mockAPI := &pluginMocks.MockAPI{}
    +
    +	defer mockAPI.AssertExpectations(t)
    +
    +	p := Plugin{
    +		MattermostPlugin: plugin.MattermostPlugin{
    +			API: mockAPI,
    +		},
    +	}
    +
    +	t.Run("invalid UTF-8 in action", func(_ *testing.T) {
    +		// Create action with invalid UTF-8 bytes (0xFF, 0xFE)
    +		malformedAction := wsActionPrefix + "join" + string([]byte{0xFF, 0xFE})
    +
    +		req := &model.WebSocketRequest{
    +			Action: malformedAction,
    +			Data: map[string]interface{}{
    +				"channelID": model.NewId(),
    +			},
    +		}
    +
    +		mockAPI.On("LogError", "invalid UTF-8 in action", "origin", mock.AnythingOfType("string")).Once()
    +
    +		p.WebSocketMessageHasBeenPosted(model.NewId(), model.NewId(), req)
    +	})
    +
    +	t.Run("missing action prefix - returns early", func(t *testing.T) {
    +		// Action without the correct prefix should be ignored
    +		req := &model.WebSocketRequest{
    +			Action: "some_other_plugin_action",
    +			Data: map[string]interface{}{
    +				"channelID": model.NewId(),
    +			},
    +		}
    +
    +		// Explicitly assert no API methods are called - proves early return
    +		p.WebSocketMessageHasBeenPosted(model.NewId(), model.NewId(), req)
    +
    +		mockAPI.AssertNotCalled(t, "LogError")
    +		mockAPI.AssertNotCalled(t, "LogDebug")
    +		mockAPI.AssertNotCalled(t, "LogInfo")
    +	})
    +
    +	t.Run("invalid message type", func(_ *testing.T) {
    +		// Action with correct prefix but unknown message type
    +		req := &model.WebSocketRequest{
    +			Action: wsActionPrefix + "invalid_unknown_action",
    +			Data:   map[string]interface{}{},
    +		}
    +
    +		mockAPI.On("LogError", "invalid message type", "origin", mock.AnythingOfType("string"), "type", "invalid_unknown_action").Once()
    +
    +		p.WebSocketMessageHasBeenPosted(model.NewId(), model.NewId(), req)
    +	})
    +
    +	t.Run("valid message type - ping", func(t *testing.T) {
    +		// Ping is a valid message type that should be handled
    +		req := &model.WebSocketRequest{
    +			Action: wsActionPrefix + "ping",
    +			Data:   map[string]interface{}{},
    +		}
    +
    +		// Ping should return early after validation, no other API calls
    +		p.WebSocketMessageHasBeenPosted(model.NewId(), model.NewId(), req)
    +
    +		mockAPI.AssertNotCalled(t, "LogError")
    +	})
    +
    +	t.Run("valid message types recognized", func(t *testing.T) {
    +		// Test that isValidClientMessageType recognizes all defined message types
    +		validTypes := []string{
    +			"join", "leave", "reconnect", "sdp", "ice",
    +			"mute", "unmute", "voice_on", "voice_off",
    +			"screen_on", "screen_off", "raise_hand", "unraise_hand",
    +			"react", "caption", "metric", "call_state", "ping",
    +		}
    +
    +		for _, msgType := range validTypes {
    +			if !isValidClientMessageType(msgType) {
    +				t.Errorf("Valid message type %q not recognized by isValidClientMessageType", msgType)
    +			}
    +		}
    +
    +		// Test that invalid types are rejected
    +		invalidTypes := []string{
    +			"invalid", "unknown", "hack", "exploit", "",
    +		}
    +
    +		for _, msgType := range invalidTypes {
    +			if isValidClientMessageType(msgType) {
    +				t.Errorf("Invalid message type %q was incorrectly accepted by isValidClientMessageType", msgType)
    +			}
    +		}
    +	})
    +}
    +
     func TestHandleJoin(t *testing.T) {
     	mockAPI := &pluginMocks.MockAPI{}
     	mockMetrics := &serverMocks.MockMetrics{}
    

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

4

News mentions

0

No linked articles in our index yet.