VYPR
Low severity3.7NVD Advisory· Published May 18, 2026· Updated May 19, 2026

CVE-2026-4273

CVE-2026-4273

Description

Mattermost versions 11.5.x <= 11.5.1, 10.11.x <= 10.11.13 fail to validate that the RefreshedToken differs from the original invite token during remote cluster invite confirmation which allows an authenticated attacker to bypass token rotation and reuse the original invite token via sending a crafted invite confirmation with a RefreshedToken matching the original token. Mattermost Advisory ID: MMSA-2026-00575

AI Insight

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

Mattermost fails to validate that the refreshed token differs from the original invite token during remote cluster confirmation, allowing token reuse.

Vulnerability

Mattermost versions 11.5.x <= 11.5.1 and 10.11.x <= 10.11.13 fail to validate that the RefreshedToken differs from the original invite token during remote cluster invite confirmation. This allows an authenticated attacker to bypass the intended token rotation by sending a crafted invite confirmation where the RefreshedToken matches the original token, effectively reusing the same token [1].

Exploitation

An authenticated attacker with network access to a Mattermost instance can exploit this vulnerability by intercepting or observing a valid invite token. The attacker then crafts an invite confirmation request, setting the RefreshedToken field to the same value as the original invite token. Because the server does not check that the new token is different from the old one, the request is accepted, and the token is not rotated as expected.

Impact

Successful exploitation allows the attacker to reuse the original invite token, bypassing the security mechanism of token rotation. This could enable persistent unauthorized access to remote cluster resources or allow multiple uses of a single invite token. The CVSS score of 3.7 (Low) reflects the requirement for authentication and the limited scope of the compromise, but it undermines the integrity of the token lifecycle.

Mitigation

Mattermost has not yet disclosed a fixed version in the available references; the advisory (MMSA-2026-00575) is expected to be published on the security updates page. Users are advised to monitor the Mattermost security updates page for a patch release and to apply custom validation or restrict remote cluster invite processes until an official fix is available [1].

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 products

1

Patches

2
8c4ed0b65ba3

Validate RefreshedToken differs from original invite token (#34864) (#35656)

https://github.com/mattermost/mattermostMattermost BuildMar 17, 2026Fixed in 10.11.14via llm-release-walk
2 files changed · +44 0
  • server/platform/services/remotecluster/recv.go+3 0 modified
    @@ -71,6 +71,9 @@ func (rcs *Service) ReceiveInviteConfirmation(confirm model.RemoteClusterInvite)
     
     	// If the accepting cluster sent a RefreshedToken (its RemoteToken), set it as our Token
     	if confirm.Version >= 2 && confirm.RefreshedToken != "" {
    +		if confirm.RefreshedToken == rc.Token {
    +			return nil, fmt.Errorf("cannot accept invite confirmation for remote %s: RefreshedToken must be different from the original invite token", confirm.RemoteId)
    +		}
     		rc.Token = confirm.RefreshedToken
     	} else {
     		// For older versions or if no RefreshedToken was provided, generate a new token
    
  • server/platform/services/remotecluster/recv_test.go+41 0 modified
    @@ -258,6 +258,47 @@ func TestReceiveInviteConfirmation_TokenInvalidation(t *testing.T) {
     
     		remoteClusterStoreMock.AssertExpectations(t)
     	})
    +
    +	t.Run("Protocol v2+ with RefreshedToken equal to original token - rejected (MM-67098)", func(t *testing.T) {
    +		// Security: RefreshedToken must be different from the original invite token.
    +		// If the remote sends back the same token, they did not actually refresh; reject.
    +		originalToken := model.NewId()
    +		remoteId := model.NewId()
    +
    +		originalRC := &model.RemoteCluster{
    +			RemoteId: remoteId,
    +			Token:    originalToken,
    +			SiteURL:  model.SiteURLPending + model.NewId(),
    +			CreateAt: model.GetMillis(),
    +		}
    +
    +		remoteClusterStoreMock := &mocks.RemoteClusterStore{}
    +		remoteClusterStoreMock.On("Get", remoteId, false).Return(originalRC, nil)
    +		// Update must NOT be called when RefreshedToken == rc.Token
    +
    +		storeMock := &mocks.Store{}
    +		storeMock.On("RemoteCluster").Return(remoteClusterStoreMock)
    +
    +		mockServer := newMockServerWithStore(t, storeMock)
    +		mockApp := newMockApp(t, nil)
    +		service, err := NewRemoteClusterService(mockServer, mockApp)
    +		require.NoError(t, err)
    +
    +		confirm := model.RemoteClusterInvite{
    +			RemoteId:       remoteId,
    +			SiteURL:        "http://example.com",
    +			Token:          model.NewId(),
    +			RefreshedToken: originalToken, // Same as rc.Token - invalid, must be different
    +			Version:        3,
    +		}
    +
    +		rcUpdated, err := service.ReceiveInviteConfirmation(confirm)
    +
    +		require.Error(t, err)
    +		assert.Nil(t, rcUpdated)
    +		assert.Contains(t, err.Error(), "RefreshedToken must be different from the original invite token")
    +		remoteClusterStoreMock.AssertNotCalled(t, "Update", mock.Anything)
    +	})
     }
     
     // TestReceiveInviteConfirmation_EdgeCases tests various edge cases
    
ff3242c23170

Validate RefreshedToken differs from original invite token (#34864) (#35653)

https://github.com/mattermost/mattermostMattermost BuildMar 17, 2026Fixed in 11.5.2via llm-release-walk
2 files changed · +44 0
  • server/platform/services/remotecluster/recv.go+3 0 modified
    @@ -71,6 +71,9 @@ func (rcs *Service) ReceiveInviteConfirmation(confirm model.RemoteClusterInvite)
     
     	// If the accepting cluster sent a RefreshedToken (its RemoteToken), set it as our Token
     	if confirm.Version >= 2 && confirm.RefreshedToken != "" {
    +		if confirm.RefreshedToken == rc.Token {
    +			return nil, fmt.Errorf("cannot accept invite confirmation for remote %s: RefreshedToken must be different from the original invite token", confirm.RemoteId)
    +		}
     		rc.Token = confirm.RefreshedToken
     	} else {
     		// For older versions or if no RefreshedToken was provided, generate a new token
    
  • server/platform/services/remotecluster/recv_test.go+41 0 modified
    @@ -258,6 +258,47 @@ func TestReceiveInviteConfirmation_TokenInvalidation(t *testing.T) {
     
     		remoteClusterStoreMock.AssertExpectations(t)
     	})
    +
    +	t.Run("Protocol v2+ with RefreshedToken equal to original token - rejected (MM-67098)", func(t *testing.T) {
    +		// Security: RefreshedToken must be different from the original invite token.
    +		// If the remote sends back the same token, they did not actually refresh; reject.
    +		originalToken := model.NewId()
    +		remoteId := model.NewId()
    +
    +		originalRC := &model.RemoteCluster{
    +			RemoteId: remoteId,
    +			Token:    originalToken,
    +			SiteURL:  model.SiteURLPending + model.NewId(),
    +			CreateAt: model.GetMillis(),
    +		}
    +
    +		remoteClusterStoreMock := &mocks.RemoteClusterStore{}
    +		remoteClusterStoreMock.On("Get", remoteId, false).Return(originalRC, nil)
    +		// Update must NOT be called when RefreshedToken == rc.Token
    +
    +		storeMock := &mocks.Store{}
    +		storeMock.On("RemoteCluster").Return(remoteClusterStoreMock)
    +
    +		mockServer := newMockServerWithStore(t, storeMock)
    +		mockApp := newMockApp(t, nil)
    +		service, err := NewRemoteClusterService(mockServer, mockApp)
    +		require.NoError(t, err)
    +
    +		confirm := model.RemoteClusterInvite{
    +			RemoteId:       remoteId,
    +			SiteURL:        "http://example.com",
    +			Token:          model.NewId(),
    +			RefreshedToken: originalToken, // Same as rc.Token - invalid, must be different
    +			Version:        3,
    +		}
    +
    +		rcUpdated, err := service.ReceiveInviteConfirmation(confirm)
    +
    +		require.Error(t, err)
    +		assert.Nil(t, rcUpdated)
    +		assert.Contains(t, err.Error(), "RefreshedToken must be different from the original invite token")
    +		remoteClusterStoreMock.AssertNotCalled(t, "Update", mock.Anything)
    +	})
     }
     
     // TestReceiveInviteConfirmation_EdgeCases tests various edge cases
    

Vulnerability mechanics

Root cause

"Missing validation that the RefreshedToken differs from the original invite token in ReceiveInviteConfirmation allows token rotation to be bypassed."

Attack vector

An authenticated attacker controlling a remote cluster sends a crafted invite confirmation (model.RemoteClusterInvite) where the RefreshedToken field is set to the same value as the original invite token stored on the inviting cluster. When the inviting cluster processes this confirmation, the missing equality check [CWE-863] in ReceiveInviteConfirmation (server/platform/services/remotecluster/recv.go) causes it to accept the unchanged token as a valid refresh, thereby bypassing token rotation. The attacker can then continue using the original invite token indefinitely.

Affected code

The vulnerability is in the ReceiveInviteConfirmation function in server/platform/services/remotecluster/recv.go. When confirm.Version >= 2 and confirm.RefreshedToken is non-empty, the code directly assigns rc.Token = confirm.RefreshedToken without verifying that the refreshed token differs from the current token. The test file recv_test.go contains the corresponding test case for this scenario.

What the fix does

The patch adds a guard clause in ReceiveInviteConfirmation (recv.go) that checks whether confirm.RefreshedToken equals rc.Token. If they match, the function returns an error and refuses to update the remote cluster record. This ensures that a remote cluster cannot reuse the original invite token as its RefreshedToken, forcing a genuine token rotation on every confirmation. The accompanying test (recv_test.go) verifies that the update is not called when the tokens are identical [patch_id=918575][patch_id=918570].

Preconditions

  • authAttacker must be an authenticated user controlling a remote cluster.
  • networkAttacker must be able to send a crafted RemoteClusterInvite confirmation to the inviting cluster.
  • inputThe crafted confirmation must have RefreshedToken set equal to the original invite token stored on the inviting cluster.

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

References

1

News mentions

0

No linked articles in our index yet.