VYPR
Moderate severityNVD Advisory· Published Jul 18, 2025· Updated Jul 18, 2025

Arbitrary file read by system admin via path traversal

CVE-2025-6233

Description

Mattermost versions 10.8.x <= 10.8.1, 10.7.x <= 10.7.3, 10.5.x <= 10.5.7, 9.11.x <= 9.11.16 fail to sanitize input paths of file attachments in the bulk import JSONL file, which allows a system admin to read arbitrary system files via path traversal.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/mattermost/mattermost-serverGo
>= 10.8.0, < 10.8.210.8.2
github.com/mattermost/mattermost-serverGo
>= 10.7.0, < 10.7.410.7.4
github.com/mattermost/mattermost-serverGo
>= 10.5.0, < 10.5.810.5.8
github.com/mattermost/mattermost-serverGo
>= 9.11.0, < 9.11.179.11.17
github.com/mattermost/mattermost/server/v8Go
< 8.0.0-20250529054450-d38c27f96fcf8.0.0-20250529054450-d38c27f96fcf

Affected products

1

Patches

1
d38c27f96fcf

[MM-64402] Improve validation of imported attachments (#31201)

https://github.com/mattermost/mattermostClaudio CostaMay 29, 2025via ghsa
7 files changed · +773 32
  • server/channels/app/import_functions.go+2 2 modified
    @@ -193,7 +193,6 @@ func (a *App) importTeam(rctx request.CTX, data *imports.TeamImportData, dryRun
     
     	var team *model.Team
     	team, err := a.Srv().Store().Team().GetByName(teamName)
    -
     	if err != nil {
     		team = &model.Team{
     			Name: teamName,
    @@ -2063,6 +2062,7 @@ func (a *App) updateFileInfoWithPostId(rctx request.CTX, post *model.Post) {
     		}
     	}
     }
    +
     func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelImportData, dryRun bool) *model.AppError {
     	var err *model.AppError
     	if err = imports.ValidateDirectChannelImportData(data); err != nil {
    @@ -2117,7 +2117,7 @@ func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelI
     		return model.NewAppError("BulkImport", "app.import.import_direct_channel.get_channel_members.error", nil, "", http.StatusBadRequest).Wrap(err)
     	}
     
    -	var ems = make([]model.ChannelMember, 0, totalMembers)
    +	ems := make([]model.ChannelMember, 0, totalMembers)
     	var page int
     
     	for int64(len(ems)) < totalMembers {
    
  • server/channels/app/import.go+32 7 modified
    @@ -7,6 +7,7 @@ import (
     	"archive/zip"
     	"bufio"
     	"encoding/json"
    +	"errors"
     	"fmt"
     	"io"
     	"net/http"
    @@ -45,20 +46,32 @@ func processAttachmentPaths(c request.CTX, files *[]imports.AttachmentImportData
     	if files == nil {
     		return nil
     	}
    +
     	var ok bool
    +	var errs []error
     	for i, f := range *files {
     		if f.Path != nil {
    -			path := filepath.Join(basePath, *f.Path)
    +			originalPath := *f.Path
    +
    +			path, valid := imports.ValidateAttachmentPathForImport(originalPath, basePath)
    +
     			*f.Path = path
    +
    +			if !valid {
    +				errs = append(errs, fmt.Errorf("invalid attachment path %q", originalPath))
    +				continue
    +			}
    +
     			if len(filesMap) > 0 {
    -				if (*files)[i].Data, ok = filesMap[path]; !ok {
    -					return fmt.Errorf("attachment %q not found in map", path)
    +				if (*files)[i].Data, ok = filesMap[*f.Path]; !ok {
    +					errs = append(errs, fmt.Errorf("attachment %q not found in map", originalPath))
    +					continue
     				}
     			}
     		}
     	}
     
    -	return nil
    +	return errors.Join(errs...)
     }
     
     func processAttachments(c request.CTX, line *imports.LineImportData, basePath string, filesMap map[string]*zip.File) error {
    @@ -88,7 +101,11 @@ func processAttachments(c request.CTX, line *imports.LineImportData, basePath st
     		}
     	case "user":
     		if line.User.ProfileImage != nil {
    -			path := filepath.Join(basePath, *line.User.ProfileImage)
    +			path, valid := imports.ValidateAttachmentPathForImport(*line.User.ProfileImage, basePath)
    +			if !valid {
    +				return fmt.Errorf("invalid profile image path %q", *line.User.ProfileImage)
    +			}
    +
     			*line.User.ProfileImage = path
     			if len(filesMap) > 0 {
     				if line.User.ProfileImageData, ok = filesMap[path]; !ok {
    @@ -98,7 +115,11 @@ func processAttachments(c request.CTX, line *imports.LineImportData, basePath st
     		}
     	case "bot":
     		if line.Bot.ProfileImage != nil {
    -			path := filepath.Join(basePath, *line.Bot.ProfileImage)
    +			path, valid := imports.ValidateAttachmentPathForImport(*line.Bot.ProfileImage, basePath)
    +			if !valid {
    +				return fmt.Errorf("invalid bot profile image path %q", *line.Bot.ProfileImage)
    +			}
    +
     			*line.Bot.ProfileImage = path
     			if len(filesMap) > 0 {
     				if line.Bot.ProfileImageData, ok = filesMap[path]; !ok {
    @@ -108,7 +129,11 @@ func processAttachments(c request.CTX, line *imports.LineImportData, basePath st
     		}
     	case "emoji":
     		if line.Emoji.Image != nil {
    -			path := filepath.Join(basePath, *line.Emoji.Image)
    +			path, valid := imports.ValidateAttachmentPathForImport(*line.Emoji.Image, basePath)
    +			if !valid {
    +				return fmt.Errorf("invalid emoji image path %q", *line.Emoji.Image)
    +			}
    +
     			*line.Emoji.Image = path
     			if len(filesMap) > 0 {
     				if line.Emoji.Data, ok = filesMap[path]; !ok {
    
  • server/channels/app/imports/import_validators.go+78 0 modified
    @@ -7,6 +7,7 @@ import (
     	"encoding/json"
     	"net/http"
     	"os"
    +	"path/filepath"
     	"strings"
     	"unicode/utf8"
     
    @@ -191,6 +192,10 @@ func ValidateChannelImportData(data *ChannelImportData) *model.AppError {
     
     func ValidateUserImportData(data *UserImportData) *model.AppError {
     	if data.ProfileImage != nil && data.ProfileImageData == nil {
    +		// Check if the resolved path is within the expected base path.
    +		if _, valid := ValidateAttachmentPathForImport(*data.ProfileImage, model.ExportDataDir); !valid {
    +			return model.NewAppError("BulkImport", "app.import.validate_user_import_data.invalid_image_path.error", map[string]any{"Path": *data.ProfileImage}, "", http.StatusBadRequest)
    +		}
     		if _, err := os.Stat(*data.ProfileImage); os.IsNotExist(err) {
     			return model.NewAppError("BulkImport", "app.import.validate_user_import_data.profile_image.error", nil, "", http.StatusNotFound).Wrap(err)
     		} else if err != nil {
    @@ -320,6 +325,11 @@ func ValidateUserImportData(data *UserImportData) *model.AppError {
     
     func ValidateBotImportData(data *BotImportData) *model.AppError {
     	if data.ProfileImage != nil && data.ProfileImageData == nil {
    +		// Check if the resolved path is within the expected base path.
    +		if _, valid := ValidateAttachmentPathForImport(*data.ProfileImage, model.ExportDataDir); !valid {
    +			return model.NewAppError("BulkImport", "app.import.validate_user_import_data.invalid_image_path.error", map[string]any{"Path": *data.ProfileImage}, "", http.StatusBadRequest)
    +		}
    +
     		if _, err := os.Stat(*data.ProfileImage); os.IsNotExist(err) {
     			return model.NewAppError("BulkImport", "app.import.validate_user_import_data.profile_image.error", nil, "", http.StatusNotFound).Wrap(err)
     		} else if err != nil {
    @@ -487,6 +497,14 @@ func ValidateReplyImportData(data *ReplyImportData, parentCreateAt int64, maxPos
     		}
     	}
     
    +	if data.Attachments != nil {
    +		for _, attachment := range *data.Attachments {
    +			if err := ValidateAttachmentImportData(&attachment); err != nil {
    +				return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.attachment.error", nil, "", http.StatusNotFound).Wrap(err)
    +			}
    +		}
    +	}
    +
     	return nil
     }
     
    @@ -537,6 +555,14 @@ func ValidatePostImportData(data *PostImportData, maxPostSize int) *model.AppErr
     		return model.NewAppError("BulkImport", "app.import.validate_post_import_data.props_too_large.error", nil, "", http.StatusBadRequest)
     	}
     
    +	if data.Attachments != nil {
    +		for _, attachment := range *data.Attachments {
    +			if err := ValidateAttachmentImportData(&attachment); err != nil {
    +				return model.NewAppError("BulkImport", "app.import.validate_post_import_data.attachment.error", nil, "", http.StatusNotFound).Wrap(err)
    +			}
    +		}
    +	}
    +
     	return nil
     }
     
    @@ -653,6 +679,14 @@ func ValidateDirectPostImportData(data *DirectPostImportData, maxPostSize int) *
     		}
     	}
     
    +	if data.Attachments != nil {
    +		for _, attachment := range *data.Attachments {
    +			if err := ValidateAttachmentImportData(&attachment); err != nil {
    +				return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.attachment.error", nil, "", http.StatusNotFound).Wrap(err)
    +			}
    +		}
    +	}
    +
     	return nil
     }
     
    @@ -671,6 +705,11 @@ func ValidateEmojiImportData(data *EmojiImportData) *model.AppError {
     		return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.image_missing.error", nil, "", http.StatusBadRequest)
     	}
     
    +	// Check if the resolved path is within the expected base path.
    +	if _, valid := ValidateAttachmentPathForImport(*data.Image, model.ExportDataDir); !valid {
    +		return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.invalid_image_path.error", map[string]any{"Path": *data.Image}, "", http.StatusBadRequest)
    +	}
    +
     	if err := model.IsValidEmojiName(*data.Name); err != nil {
     		return err
     	}
    @@ -749,3 +788,42 @@ func isValidGuestRoles(data UserImportData) bool {
     
     	return true
     }
    +
    +// ValidateAttachmentPathForImport joins 'path' to 'basePath' (defaulting to "." if empty) and ensures
    +// the result does not escape the base directory. Returns the cleaned joined path (and true),
    +// or an empty string (and false) if the result escapes the base.
    +func ValidateAttachmentPathForImport(path, basePath string) (string, bool) {
    +	if basePath == "" {
    +		basePath = "."
    +	}
    +
    +	joined := filepath.Join(basePath, path)
    +
    +	// Check if the resolved joined path is within basePath
    +	rel, err := filepath.Rel(basePath, joined)
    +	if err != nil {
    +		return "", false
    +	}
    +	if strings.HasPrefix(rel, ".."+string(filepath.Separator)) || rel == ".." {
    +		return "", false
    +	}
    +
    +	return joined, true
    +}
    +
    +func ValidateAttachmentImportData(data *AttachmentImportData) *model.AppError {
    +	if data == nil {
    +		return nil
    +	}
    +
    +	if data.Path == nil || *data.Path == "" {
    +		return nil
    +	}
    +
    +	// Check if the resolved path is within the expected base path.
    +	if _, valid := ValidateAttachmentPathForImport(*data.Path, model.ExportDataDir); !valid {
    +		return model.NewAppError("BulkImport", "app.import.validate_attachment_import_data.invalid_path.error", map[string]any{"Path": *data.Path}, "", http.StatusBadRequest)
    +	}
    +
    +	return nil
    +}
    
  • server/channels/app/imports/import_validators_test.go+384 5 modified
    @@ -499,6 +499,12 @@ func TestImportValidateUserImportData(t *testing.T) {
     	err = ValidateUserImportData(&data)
     	require.NotNil(t, err, "Validation should have failed due to not existing profile image file.")
     
    +	// Invalid image path
    +	data.ProfileImage = model.NewPointer("../invalid/path/file.jpg")
    +	err = ValidateUserImportData(&data)
    +	require.NotNil(t, err, "Validation should have failed due to invalid profile image file path.")
    +	require.Equal(t, "app.import.validate_user_import_data.invalid_image_path.error", err.Id)
    +
     	data.ProfileImage = nil
     
     	// Invalid Emails
    @@ -623,8 +629,8 @@ func TestImportValidateUserImportData(t *testing.T) {
     	data.NotifyProps.MentionKeys = model.NewPointer("valid")
     	checkNoError(t, ValidateUserImportData(&data))
     
    -	//Test the email batching interval validators
    -	//Happy paths
    +	// Test the email batching interval validators
    +	// Happy paths
     	data.EmailInterval = model.NewPointer("immediately")
     	checkNoError(t, ValidateUserImportData(&data))
     
    @@ -634,7 +640,7 @@ func TestImportValidateUserImportData(t *testing.T) {
     	data.EmailInterval = model.NewPointer("hour")
     	checkNoError(t, ValidateUserImportData(&data))
     
    -	//Invalid values
    +	// Invalid values
     	data.EmailInterval = model.NewPointer("invalid")
     	checkError(t, ValidateUserImportData(&data))
     
    @@ -731,6 +737,12 @@ func TestImportValidateBotImportData(t *testing.T) {
     	data.Owner = model.NewPointer(strings.Repeat("abcdefghij", 7))
     	err = ValidateBotImportData(&data)
     	require.NotNil(t, err, "Should have failed due to too long OwnerID.")
    +
    +	// Invalid profile image path
    +	data.ProfileImage = model.NewPointer("../invalid/path/file.jpg")
    +	err = ValidateBotImportData(&data)
    +	require.NotNil(t, err, "Should have failed due to invalid profile image file path.")
    +	require.Equal(t, "app.import.validate_user_import_data.invalid_image_path.error", err.Id)
     }
     
     func TestImportValidateUserTeamsImportData(t *testing.T) {
    @@ -943,6 +955,21 @@ func TestImportValidateReplyImportData(t *testing.T) {
     	}
     	err = ValidateReplyImportData(&data, parentCreateAt, maxPostSize)
     	require.NotNil(t, err, "Should have failed due to 0 create-at value.")
    +
    +	// Test with invalid attachment path.
    +	data = ReplyImportData{
    +		User:     model.NewPointer("username"),
    +		Message:  model.NewPointer("message"),
    +		CreateAt: model.NewPointer(model.GetMillis()),
    +		Attachments: &[]AttachmentImportData{
    +			{
    +				Path: model.NewPointer("invalid/../../../path/to/file.txt"),
    +			},
    +		},
    +	}
    +	err = ValidateReplyImportData(&data, parentCreateAt, maxPostSize)
    +	require.NotNil(t, err, "Should have failed due to invalid attachment path.")
    +	require.Equal(t, err.Id, "app.import.validate_reply_import_data.attachment.error")
     }
     
     func TestImportValidatePostImportData(t *testing.T) {
    @@ -1085,6 +1112,24 @@ func TestImportValidatePostImportData(t *testing.T) {
     		require.NotNil(t, err, "Should have failed due to long props.")
     		assert.Equal(t, err.Id, "app.import.validate_post_import_data.props_too_large.error")
     	})
    +
    +	t.Run("Test with invalid attachment path", func(t *testing.T) {
    +		data := PostImportData{
    +			Team:     model.NewPointer("teamname"),
    +			Channel:  model.NewPointer("channelname"),
    +			User:     model.NewPointer("username"),
    +			Message:  model.NewPointer("message"),
    +			CreateAt: model.NewPointer(model.GetMillis()),
    +			Attachments: &[]AttachmentImportData{
    +				{
    +					Path: model.NewPointer("invalid/../../../path/to/file.txt"),
    +				},
    +			},
    +		}
    +		err := ValidatePostImportData(&data, maxPostSize)
    +		require.NotNil(t, err)
    +		assert.Equal(t, err.Id, "app.import.validate_post_import_data.attachment.error")
    +	})
     }
     
     func TestImportValidateDirectChannelImportData(t *testing.T) {
    @@ -1438,10 +1483,29 @@ func TestImportValidateDirectPostImportData(t *testing.T) {
     
     	err = ValidateDirectPostImportData(&data, maxPostSize)
     	require.Nil(t, err, "Validation should succeed with valid optional parameters")
    +
    +	// Test with invalid attachment path.
    +	data = DirectPostImportData{
    +		ChannelMembers: &[]string{
    +			model.NewId(),
    +			model.NewId(),
    +		},
    +		User:     model.NewPointer("username"),
    +		Message:  model.NewPointer("message"),
    +		CreateAt: model.NewPointer(model.GetMillis()),
    +		Attachments: &[]AttachmentImportData{
    +			{
    +				Path: model.NewPointer("invalid/../../../path/to/file.txt"),
    +			},
    +		},
    +	}
    +	err = ValidateDirectPostImportData(&data, maxPostSize)
    +	require.NotNil(t, err, "Should have failed due to invalid attachment path.")
    +	require.Equal(t, err.Id, "app.import.validate_direct_post_import_data.attachment.error")
     }
     
     func TestImportValidateEmojiImportData(t *testing.T) {
    -	var testCases = []struct {
    +	testCases := []struct {
     		testName          string
     		name              *string
     		image             *string
    @@ -1456,6 +1520,7 @@ func TestImportValidateEmojiImportData(t *testing.T) {
     		{"nil name", nil, model.NewPointer("/path/to/image"), true, false},
     		{"nil image", model.NewPointer("parrot2"), nil, true, false},
     		{"nil name and image", nil, nil, true, false},
    +		{"invalid image path", model.NewPointer("parrot2"), model.NewPointer("../invalid/path/to/emoji.png"), true, false},
     	}
     
     	for _, tc := range testCases {
    @@ -1485,7 +1550,7 @@ func checkNoError(t *testing.T, err *model.AppError) {
     }
     
     func TestIsValidGuestRoles(t *testing.T) {
    -	var testCases = []struct {
    +	testCases := []struct {
     		name     string
     		input    UserImportData
     		expected bool
    @@ -1596,3 +1661,317 @@ func TestIsValidGuestRoles(t *testing.T) {
     		})
     	}
     }
    +
    +func TestValidateAttachmentPathForImport(t *testing.T) {
    +	for _, tc := range []struct {
    +		name         string
    +		path         string
    +		basePath     string
    +		expectedPath string
    +		expectedRes  bool
    +	}{
    +		{
    +			name:         "valid relative path",
    +			path:         "valid/path/to/attachment",
    +			basePath:     "data",
    +			expectedPath: "data/valid/path/to/attachment",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "valid absolute path",
    +			path:         "/valid/path/to/attachment",
    +			basePath:     "data",
    +			expectedPath: "data/valid/path/to/attachment",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "valid relative path with empty base",
    +			path:         "data/file.jpg",
    +			basePath:     "",
    +			expectedPath: "data/file.jpg",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "absolute path with empty base is converted to relative",
    +			path:         "/data/file.jpg",
    +			basePath:     "",
    +			expectedPath: "data/file.jpg",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "valid path with dot segments",
    +			path:         "path/./to/attachment",
    +			basePath:     "data",
    +			expectedPath: "data/path/to/attachment",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "valid path with internal parent reference",
    +			path:         "path/to/../to/attachment",
    +			basePath:     "data",
    +			expectedPath: "data/path/to/attachment",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "valid path with filename containing dots",
    +			path:         "path/to/file..txt",
    +			basePath:     "data",
    +			expectedPath: "data/path/to/file..txt",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "valid path with default base path",
    +			path:         "file.txt",
    +			basePath:     "",
    +			expectedPath: "file.txt",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "valid path with spaces",
    +			path:         "path/to/file with spaces.jpg",
    +			basePath:     "data",
    +			expectedPath: "data/path/to/file with spaces.jpg",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "invalid path with parent directory traversal",
    +			path:         "../file.txt",
    +			basePath:     "data",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		{
    +			name:         "invalid path with multiple parent directory traversal",
    +			path:         "../../file.txt",
    +			basePath:     "data",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		{
    +			name:         "invalid path with parent directory traversal in middle",
    +			path:         "path/../../file.txt",
    +			basePath:     "data",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		{
    +			name:         "invalid absolute path with traversal",
    +			path:         "/path/../../../not/valid",
    +			basePath:     "data",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		{
    +			name:         "invalid relative path with substring in path",
    +			path:         "../data_dir/attachment",
    +			basePath:     "data",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		{
    +			name:         "empty path",
    +			path:         "",
    +			basePath:     "data",
    +			expectedPath: "data",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "path is just a dot",
    +			path:         ".",
    +			basePath:     "data",
    +			expectedPath: "data",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "path with only parent reference",
    +			path:         "..",
    +			basePath:     "data",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		// Additional security test cases
    +		{
    +			name:         "valid path with double dots in filename",
    +			path:         "....//file.txt",
    +			basePath:     "data",
    +			expectedPath: "data/..../file.txt",
    +			expectedRes:  true, // Double dots in filename are valid, not traversal
    +		},
    +		{
    +			name:         "valid path with quadruple dots in filename",
    +			path:         "..../file.txt",
    +			basePath:     "data",
    +			expectedPath: "data/..../file.txt",
    +			expectedRes:  true, // Quadruple dots in filename are valid, not traversal
    +		},
    +		{
    +			name:         "valid path with backslashes (treated as literal on Unix)",
    +			path:         "path\\..\\..\\file.txt",
    +			basePath:     "data",
    +			expectedPath: "data/path\\..\\..\\file.txt",
    +			expectedRes:  true, // Backslashes are literal characters on Unix systems
    +		},
    +		{
    +			name:         "valid path with mixed separators (backslash literal)",
    +			path:         "path\\../file.txt",
    +			basePath:     "data",
    +			expectedPath: "data/path\\../file.txt",
    +			expectedRes:  true, // Backslash is literal, only forward slash is normalized
    +		},
    +		{
    +			name:         "valid URL encoded characters (treated as literals)",
    +			path:         "%2e%2e%2ffile.txt",
    +			basePath:     "data",
    +			expectedPath: "data/%2e%2e%2ffile.txt",
    +			expectedRes:  true, // URL encoding should be treated as literal characters
    +		},
    +		{
    +			name:         "valid null byte in filename (treated as literal)",
    +			path:         "file.txt\x00../etc/passwd",
    +			basePath:     "data",
    +			expectedPath: "data/file.txt\x00../etc/passwd",
    +			expectedRes:  true, // Null bytes should be treated as literal characters
    +		},
    +		{
    +			name:         "invalid complex traversal pattern",
    +			path:         "./././../../../file.txt",
    +			basePath:     "data",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		{
    +			name:         "valid path with multiple slashes (normalized)",
    +			path:         "path///../file.txt",
    +			basePath:     "data",
    +			expectedPath: "data/file.txt",
    +			expectedRes:  true, // Multiple slashes get normalized, no traversal occurs
    +		},
    +		{
    +			name:         "invalid deep traversal attempt",
    +			path:         "../../../../../../../../../etc/passwd",
    +			basePath:     "data",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		{
    +			name:         "valid path with multiple internal dots",
    +			path:         "path/to/file...with...dots.txt",
    +			basePath:     "data",
    +			expectedPath: "data/path/to/file...with...dots.txt",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "invalid traversal with valid-looking suffix",
    +			path:         "../trusted_NOT/secrets.txt",
    +			basePath:     "/trusted",
    +			expectedPath: "",
    +			expectedRes:  false,
    +		},
    +		{
    +			name:         "valid path with base path containing special chars",
    +			path:         "file.txt",
    +			basePath:     "data-dir_v1.0",
    +			expectedPath: "data-dir_v1.0/file.txt",
    +			expectedRes:  true,
    +		},
    +		{
    +			name:         "valid Windows-style paths (backslashes literal on Unix)",
    +			path:         "..\\..\\windows\\system32\\config",
    +			basePath:     "data",
    +			expectedPath: "data/..\\..\\windows\\system32\\config",
    +			expectedRes:  true, // Backslashes are literal on Unix, no traversal
    +		},
    +		{
    +			name:         "valid path with Unicode characters",
    +			path:         "path/to/файл.txt",
    +			basePath:     "data",
    +			expectedPath: "data/path/to/файл.txt",
    +			expectedRes:  true,
    +		},
    +	} {
    +		t.Run(tc.name, func(t *testing.T) {
    +			path, ok := ValidateAttachmentPathForImport(tc.path, tc.basePath)
    +			require.Equal(t, tc.expectedPath, path)
    +			require.Equal(t, tc.expectedRes, ok)
    +		})
    +	}
    +}
    +
    +func TestValidateAttachmentImportData(t *testing.T) {
    +	for _, tc := range []struct {
    +		name string
    +		data *AttachmentImportData
    +		err  string
    +	}{
    +		{
    +			name: "nil data",
    +		},
    +		{
    +			name: "empty path",
    +			data: &AttachmentImportData{},
    +		},
    +		{
    +			name: "valid absolute path",
    +			data: &AttachmentImportData{
    +				Path: model.NewPointer("/valid/path/to/attachment"),
    +			},
    +		},
    +		{
    +			name: "invalid relative path",
    +			data: &AttachmentImportData{
    +				Path: model.NewPointer("../attachment"),
    +			},
    +			err: "BulkImport: app.import.validate_attachment_import_data.invalid_path.error",
    +		},
    +		{
    +			name: "invalid relative path",
    +			data: &AttachmentImportData{
    +				Path: model.NewPointer("path/to/../../../attachment"),
    +			},
    +			err: "BulkImport: app.import.validate_attachment_import_data.invalid_path.error",
    +		},
    +
    +		{
    +			name: "invalid relative path",
    +			data: &AttachmentImportData{
    +				Path: model.NewPointer("../data_dir/attachment"),
    +			},
    +			err: "BulkImport: app.import.validate_attachment_import_data.invalid_path.error",
    +		},
    +
    +		{
    +			name: "valid relative path",
    +			data: &AttachmentImportData{
    +				Path: model.NewPointer("./path/to/attachment"),
    +			},
    +		},
    +		{
    +			name: "valid relative path",
    +			data: &AttachmentImportData{
    +				Path: model.NewPointer("path/../to/attachment"),
    +			},
    +		},
    +		{
    +			name: "valid relative path",
    +			data: &AttachmentImportData{
    +				Path: model.NewPointer("path/to/attachment"),
    +			},
    +		},
    +		{
    +			name: "valid relative path",
    +			data: &AttachmentImportData{
    +				Path: model.NewPointer("path/to/attachment/attachment..ext"),
    +			},
    +		},
    +	} {
    +		t.Run(tc.name, func(t *testing.T) {
    +			err := ValidateAttachmentImportData(tc.data)
    +			if tc.err != "" {
    +				require.NotNil(t, err, "Expected error but got none")
    +				require.EqualError(t, err, tc.err, "Expected error did not match")
    +			} else {
    +				require.Nil(t, err, "Expected no error but got one")
    +			}
    +		})
    +	}
    +}
    
  • server/channels/app/import_test.go+193 18 modified
    @@ -157,7 +157,7 @@ func TestImportBulkImport(t *testing.T) {
     	username3 := model.NewUsername()
     	emojiName := model.NewId()
     	testsDir, _ := fileutils.FindDir("tests")
    -	testImage := filepath.Join(testsDir, "test.png")
    +	testImage := "test.png"
     	teamTheme1 := `{\"awayIndicator\":\"#DBBD4E\",\"buttonBg\":\"#23A1FF\",\"buttonColor\":\"#FFFFFF\",\"centerChannelBg\":\"#ffffff\",\"centerChannelColor\":\"#333333\",\"codeTheme\":\"github\",\"image\":\"/static/files/a4a388b38b32678e83823ef1b3e17766.png\",\"linkColor\":\"#2389d7\",\"mentionBg\":\"#2389d7\",\"mentionColor\":\"#ffffff\",\"mentionHighlightBg\":\"#fff2bb\",\"mentionHighlightLink\":\"#2f81b7\",\"newMessageSeparator\":\"#FF8800\",\"onlineIndicator\":\"#7DBE00\",\"sidebarBg\":\"#fafafa\",\"sidebarHeaderBg\":\"#3481B9\",\"sidebarHeaderTextColor\":\"#ffffff\",\"sidebarText\":\"#333333\",\"sidebarTextActiveBorder\":\"#378FD2\",\"sidebarTextActiveColor\":\"#111111\",\"sidebarTextHoverBg\":\"#e6f2fa\",\"sidebarUnreadText\":\"#333333\",\"type\":\"Mattermost\"}`
     	teamTheme2 := `{\"awayIndicator\":\"#DBBD4E\",\"buttonBg\":\"#23A100\",\"buttonColor\":\"#EEEEEE\",\"centerChannelBg\":\"#ffffff\",\"centerChannelColor\":\"#333333\",\"codeTheme\":\"github\",\"image\":\"/static/files/a4a388b38b32678e83823ef1b3e17766.png\",\"linkColor\":\"#2389d7\",\"mentionBg\":\"#2389d7\",\"mentionColor\":\"#ffffff\",\"mentionHighlightBg\":\"#fff2bb\",\"mentionHighlightLink\":\"#2f81b7\",\"newMessageSeparator\":\"#FF8800\",\"onlineIndicator\":\"#7DBE00\",\"sidebarBg\":\"#fafafa\",\"sidebarHeaderBg\":\"#3481B9\",\"sidebarHeaderTextColor\":\"#ffffff\",\"sidebarText\":\"#333333\",\"sidebarTextActiveBorder\":\"#378FD2\",\"sidebarTextActiveColor\":\"#222222\",\"sidebarTextHoverBg\":\"#e6f2fa\",\"sidebarUnreadText\":\"#444444\",\"type\":\"Mattermost\"}`
     
    @@ -178,13 +178,13 @@ func TestImportBulkImport(t *testing.T) {
     {"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789015}}
     {"type": "emoji", "emoji": {"name": "` + emojiName + `", "image": "` + testImage + `"}}`
     
    -	line, err := th.App.BulkImport(th.Context, strings.NewReader(data1), nil, false, 2)
    +	line, err := th.App.BulkImportWithPath(th.Context, strings.NewReader(data1), nil, false, false, 2, testsDir)
     	require.Nil(t, err, "BulkImport should have succeeded")
     	require.Equal(t, 0, line, "BulkImport line should be 0")
     
     	// Run bulk import using a string that contains a line with invalid json.
     	data2 := `{"type": "version", "version": 1`
    -	line, err = th.App.BulkImport(th.Context, strings.NewReader(data2), nil, false, 2)
    +	line, err = th.App.BulkImportWithPath(th.Context, strings.NewReader(data2), nil, false, false, 2, testsDir)
     	require.NotNil(t, err, "Should have failed due to invalid JSON on line 1.")
     	require.Equal(t, 1, line, "Should have failed due to invalid JSON on line 1.")
     
    @@ -193,7 +193,7 @@ func TestImportBulkImport(t *testing.T) {
     {"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
     {"type": "user", "user": {"username": "kufjgnkxkrhhfgbrip6qxkfsaa", "email": "kufjgnkxkrhhfgbrip6qxkfsaa@example.com"}}
     {"type": "user", "user": {"username": "bwshaim6qnc2ne7oqkd5b2s2rq", "email": "bwshaim6qnc2ne7oqkd5b2s2rq@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}]}]}}`
    -	line, err = th.App.BulkImport(th.Context, strings.NewReader(data3), nil, false, 2)
    +	line, err = th.App.BulkImportWithPath(th.Context, strings.NewReader(data3), nil, false, false, 2, testsDir)
     	require.NotNil(t, err, "Should have failed due to missing version line on line 1.")
     	require.Equal(t, 1, line, "Should have failed due to missing version line on line 1.")
     
    @@ -231,6 +231,54 @@ func TestImportBulkImport(t *testing.T) {
     		require.Nil(t, err, "BulkImport should have succeeded")
     		require.Equal(t, 0, line, "BulkImport line should be 0")
     	})
    +
    +	t.Run("Invalid post attachment path", func(t *testing.T) {
    +		data7 := `{"type": "version", "version": 1}
    +{"type": "team", "team": {"type": "O", "display_name": "lskmw2d7a5ao7ppwqh5ljchvr4", "name": "` + teamName + `"}}
    +{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
    +{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme1 + `", "channels": [{"name": "` + channelName + `"}]}]}}
    +{"type": "user", "user": {"username": "` + username2 + `", "email": "` + username2 + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme2 + `", "channels": [{"name": "` + channelName + `"}]}]}}
    +{"type": "user", "user": {"username": "` + username3 + `", "email": "` + username3 + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}], "delete_at": 123456789016}]}}
    +{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username + `", "message": "Hello World", "create_at": 123456789012, "attachments":[{"path": "test.png"}]}}
    +{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username3 + `", "message": "Hey Everyone!", "create_at": 123456789013, "attachments":[{"path": "../test.png"}]}}`
    +
    +		// Import should not fail for a single invalid attachment path.
    +		line, err := th.App.BulkImportWithPath(th.Context, strings.NewReader(data7), nil, false, false, 2, testsDir)
    +		require.Nil(t, err, "BulkImport should have succeeded")
    +		require.Equal(t, 0, line, "BulkImport line should be 0")
    +	})
    +
    +	t.Run("Invalid reply attachment path", func(t *testing.T) {
    +		data8 := `{"type": "version", "version": 1}
    +{"type": "team", "team": {"type": "O", "display_name": "lskmw2d7a5ao7ppwqh5ljchvr4", "name": "` + teamName + `"}}
    +{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
    +{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme1 + `", "channels": [{"name": "` + channelName + `"}]}]}}
    +{"type": "user", "user": {"username": "` + username2 + `", "email": "` + username2 + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme2 + `", "channels": [{"name": "` + channelName + `"}]}]}}
    +{"type": "user", "user": {"username": "` + username3 + `", "email": "` + username3 + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}], "delete_at": 123456789016}]}}
    +{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username + `", "message": "Hello World", "create_at": 123456789012, "attachments":[{"path": "test.png"}]}}
    +{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username3 + `", "message": "Hey Everyone!", "create_at": 123456789013, "replies": [{"create_at": 123456789015, "user": "` + username + `", "message": "reply", "attachments":[{"path": "../test.png"}]}]}}`
    +
    +		// Import should not fail for a single invalid attachment path.
    +		line, err := th.App.BulkImportWithPath(th.Context, strings.NewReader(data8), nil, false, false, 2, testsDir)
    +		require.Nil(t, err, "BulkImport should have succeeded")
    +		require.Equal(t, 0, line, "BulkImport line should be 0")
    +	})
    +
    +	t.Run("Invalid direct post attachment path", func(t *testing.T) {
    +		data9 := `{"type": "version", "version": 1}
    +{"type": "team", "team": {"type": "O", "display_name": "lskmw2d7a5ao7ppwqh5ljchvr4", "name": "` + teamName + `"}}
    +{"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}}
    +{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme1 + `", "channels": [{"name": "` + channelName + `"}]}]}}
    +{"type": "user", "user": {"username": "` + username2 + `", "email": "` + username2 + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme2 + `", "channels": [{"name": "` + channelName + `"}]}]}}
    +{"type": "user", "user": {"username": "` + username3 + `", "email": "` + username3 + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}], "delete_at": 123456789016}]}}
    +{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username + `"]}}
    +{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username + `"], "user": "` + username + `", "message": "Hello Direct Channel to myself", "create_at": 123456789014, "attachments":[{"path": "../test.png"}]}}`
    +
    +		// Import should not fail for a single invalid attachment path.
    +		line, err := th.App.BulkImportWithPath(th.Context, strings.NewReader(data9), nil, false, false, 2, testsDir)
    +		require.Nil(t, err, "BulkImport should have succeeded")
    +		require.Equal(t, 0, line, "BulkImport line should be 0")
    +	})
     }
     
     func TestImportProcessImportDataFileVersionLine(t *testing.T) {
    @@ -275,6 +323,133 @@ func AssertFileIdsInPost(files []*model.FileInfo, th *TestHelper, t *testing.T)
     	}
     }
     
    +func TestProcessAttachmentPaths(t *testing.T) {
    +	c := request.TestContext(t)
    +
    +	t.Run("nil attachments", func(t *testing.T) {
    +		err := processAttachmentPaths(c, nil, "", nil)
    +		require.NoError(t, err)
    +	})
    +
    +	t.Run("missing file in map", func(t *testing.T) {
    +		attachments := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer("file.jpg"),
    +			},
    +		}
    +
    +		filesMap := map[string]*zip.File{
    +			"./import/other-file.jpg": nil,
    +		}
    +
    +		err := processAttachmentPaths(c, attachments, "", filesMap)
    +		require.Error(t, err)
    +		require.EqualError(t, err, "attachment \"file.jpg\" not found in map")
    +	})
    +
    +	t.Run("valid paths", func(t *testing.T) {
    +		attachments := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer("file.jpg"),
    +			},
    +			{
    +				Path: model.NewPointer("somedir/file.jpg"),
    +			},
    +			{
    +				Path: model.NewPointer("./someotherdir/file.jpg"),
    +			},
    +		}
    +
    +		expected := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer("data/file.jpg"),
    +			},
    +			{
    +				Path: model.NewPointer("data/somedir/file.jpg"),
    +			},
    +			{
    +				Path: model.NewPointer("data/someotherdir/file.jpg"),
    +			},
    +		}
    +
    +		err := processAttachmentPaths(c, attachments, model.ExportDataDir, nil)
    +		require.NoError(t, err)
    +		require.Equal(t, expected, attachments)
    +	})
    +
    +	t.Run("uncleaned paths", func(t *testing.T) {
    +		attachments := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer("../dir/invalid.txt"),
    +			},
    +			{
    +				Path: model.NewPointer("somedir/./normal-file.jpg"),
    +			},
    +		}
    +
    +		expected := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer("/path/to/import/dir/invalid.txt"),
    +			},
    +			{
    +				Path: model.NewPointer("/path/to/import/dir/somedir/normal-file.jpg"),
    +			},
    +		}
    +
    +		err := processAttachmentPaths(c, attachments, "/path/to/import/dir", nil)
    +		require.NoError(t, err)
    +		require.Equal(t, expected, attachments)
    +	})
    +
    +	t.Run("paths outside base path", func(t *testing.T) {
    +		attachments := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer("../../invalid.txt"),
    +			},
    +			{
    +				Path: model.NewPointer("../../../invalid.txt"),
    +			},
    +		}
    +
    +		expected := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer(""),
    +			},
    +			{
    +				Path: model.NewPointer(""),
    +			},
    +		}
    +
    +		err := processAttachmentPaths(c, attachments, "data", nil)
    +		require.EqualError(t, err, "invalid attachment path \"../../invalid.txt\"\ninvalid attachment path \"../../../invalid.txt\"")
    +		require.Equal(t, expected, attachments)
    +	})
    +
    +	t.Run("mix of valid and invalid paths", func(t *testing.T) {
    +		attachments := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer("../../invalid.txt"),
    +			},
    +			{
    +				Path: model.NewPointer("valid/path/to/file"),
    +			},
    +		}
    +
    +		expected := &[]imports.AttachmentImportData{
    +			{
    +				Path: model.NewPointer(""),
    +			},
    +			{
    +				Path: model.NewPointer("data/valid/path/to/file"),
    +			},
    +		}
    +
    +		err := processAttachmentPaths(c, attachments, "data", nil)
    +		require.EqualError(t, err, "invalid attachment path \"../../invalid.txt\"")
    +		require.Equal(t, expected, attachments)
    +	})
    +}
    +
     func TestProcessAttachments(t *testing.T) {
     	c := request.TestContext(t)
     
    @@ -340,35 +515,35 @@ func TestProcessAttachments(t *testing.T) {
     	t.Run("valid path", func(t *testing.T) {
     		expected := &[]imports.AttachmentImportData{
     			{
    -				Path: model.NewPointer("/tmp/file.jpg"),
    +				Path: model.NewPointer("tmp/file.jpg"),
     			},
     			{
    -				Path: model.NewPointer("/tmp/somedir/file.jpg"),
    +				Path: model.NewPointer("tmp/somedir/file.jpg"),
     			},
     		}
     
     		t.Run("post attachments", func(t *testing.T) {
    -			err := processAttachments(c, &line, "/tmp", nil)
    +			err := processAttachments(c, &line, "tmp", nil)
     			require.NoError(t, err)
     			require.Equal(t, expected, line.Post.Attachments)
     		})
     
     		t.Run("direct post attachments", func(t *testing.T) {
    -			err := processAttachments(c, &line2, "/tmp", nil)
    +			err := processAttachments(c, &line2, "tmp", nil)
     			require.NoError(t, err)
     			require.Equal(t, expected, line2.DirectPost.Attachments)
     		})
     
     		t.Run("profile image", func(t *testing.T) {
    -			expected := "/tmp/profile.jpg"
    -			err := processAttachments(c, &userLine, "/tmp", nil)
    +			expected := "tmp/profile.jpg"
    +			err := processAttachments(c, &userLine, "tmp", nil)
     			require.NoError(t, err)
     			require.Equal(t, expected, *userLine.User.ProfileImage)
     		})
     
     		t.Run("emoji", func(t *testing.T) {
    -			expected := "/tmp/emoji.png"
    -			err := processAttachments(c, &emojiLine, "/tmp", nil)
    +			expected := "tmp/emoji.png"
    +			err := processAttachments(c, &emojiLine, "tmp", nil)
     			require.NoError(t, err)
     			require.Equal(t, expected, *emojiLine.Emoji.Image)
     		})
    @@ -377,24 +552,24 @@ func TestProcessAttachments(t *testing.T) {
     	t.Run("with filesMap", func(t *testing.T) {
     		t.Run("post attachments", func(t *testing.T) {
     			filesMap := map[string]*zip.File{
    -				"/tmp/file.jpg": nil,
    +				"tmp/file.jpg": nil,
     			}
     			err := processAttachments(c, &line, "", filesMap)
     			require.Error(t, err)
     
    -			filesMap["/tmp/somedir/file.jpg"] = nil
    +			filesMap["tmp/somedir/file.jpg"] = nil
     			err = processAttachments(c, &line, "", filesMap)
     			require.NoError(t, err)
     		})
     
     		t.Run("direct post attachments", func(t *testing.T) {
     			filesMap := map[string]*zip.File{
    -				"/tmp/file.jpg": nil,
    +				"tmp/file.jpg": nil,
     			}
     			err := processAttachments(c, &line2, "", filesMap)
     			require.Error(t, err)
     
    -			filesMap["/tmp/somedir/file.jpg"] = nil
    +			filesMap["tmp/somedir/file.jpg"] = nil
     			err = processAttachments(c, &line2, "", filesMap)
     			require.NoError(t, err)
     		})
    @@ -406,7 +581,7 @@ func TestProcessAttachments(t *testing.T) {
     			err := processAttachments(c, &userLine, "", filesMap)
     			require.Error(t, err)
     
    -			filesMap["/tmp/profile.jpg"] = nil
    +			filesMap["tmp/profile.jpg"] = nil
     			err = processAttachments(c, &userLine, "", filesMap)
     			require.NoError(t, err)
     		})
    @@ -418,7 +593,7 @@ func TestProcessAttachments(t *testing.T) {
     			err := processAttachments(c, &emojiLine, "", filesMap)
     			require.Error(t, err)
     
    -			filesMap["/tmp/emoji.png"] = nil
    +			filesMap["tmp/emoji.png"] = nil
     			err = processAttachments(c, &emojiLine, "", filesMap)
     			require.NoError(t, err)
     		})
    
  • server/cmd/mmctl/commands/import_test.go+60 0 modified
    @@ -475,4 +475,64 @@ func (s *MmctlUnitTestSuite) TestImportValidateCmdF() {
     		s.Require().Empty(res.Errors)
     		s.Equal("Validation complete\n", printer.GetLines()[2])
     	})
    +
    +	s.Run("invalid file attachment path", func() {
    +		file, err := os.Create(importFilePath)
    +		s.Require().NoError(err)
    +
    +		zipWr := zip.NewWriter(file)
    +		wr, err := zipWr.Create("import.jsonl")
    +		s.Require().NoError(err)
    +
    +		_, err = wr.Write([]byte(importBase))
    +		s.Require().NoError(err)
    +
    +		_, err = wr.Write([]byte(`
    +{"type":"post","post":{"team":"ad-1","channel":"iusto-9","user":"ashley.berry","message":"message","props":{},"create_at":1603398068740,"reactions":null,"replies":null,"attachments":[{"path": "data/../../invalid.jpg"}]}}`))
    +		s.Require().NoError(err)
    +
    +		err = zipWr.Close()
    +		s.Require().NoError(err)
    +
    +		err = file.Close()
    +		s.Require().NoError(err)
    +
    +		printer.Clean()
    +
    +		s.client.
    +			EXPECT().
    +			GetUsers(context.TODO(), 0, 200, "").
    +			Return(nil, &model.Response{}, nil).
    +			Times(1)
    +
    +		s.client.
    +			EXPECT().
    +			GetAllTeams(context.TODO(), "", 0, 200).
    +			Return(nil, &model.Response{}, nil).
    +			Times(1)
    +
    +		s.client.
    +			EXPECT().
    +			GetOldClientConfig(context.TODO(), "").
    +			Return(map[string]string{
    +				"MaxPostSize": fmt.Sprintf("%d", model.PostMessageMaxRunesV2*2),
    +			}, &model.Response{}, nil).
    +			Times(1)
    +
    +		err = importValidateCmdF(s.client, ImportValidateCmd, []string{importFilePath})
    +		s.Require().Nil(err)
    +
    +		s.Empty(printer.GetErrorLines())
    +		s.Equal(Statistics{
    +			Teams:          2,
    +			Channels:       1,
    +			DirectChannels: 1,
    +			Users:          1,
    +			Posts:          1,
    +		}, printer.GetLines()[0].(Statistics))
    +		res := printer.GetLines()[1].(ImportValidationResult)
    +		s.Require().Len(res.Errors, 2)
    +		s.Require().Equal("app.import.validate_post_import_data.attachment.error", res.Errors[0].Err.(*model.AppError).Id)
    +		s.Equal("Validation complete\n", printer.GetLines()[2])
    +	})
     }
    
  • server/i18n/en.json+24 0 modified
    @@ -5614,6 +5614,10 @@
         "id": "app.import.profile_image.read_data.app_error",
         "translation": "Failed to read profile image data."
       },
    +  {
    +    "id": "app.import.validate_attachment_import_data.invalid_path.error",
    +    "translation": "Failed to validate attachment import data. Invalid path: \"{{.Path}}\""
    +  },
       {
         "id": "app.import.validate_bot_import_data.owner_missing.error",
         "translation": "Bot owner is missing"
    @@ -5678,6 +5682,10 @@
         "id": "app.import.validate_direct_channel_import_data.unknown_favoriter.error",
         "translation": "Direct channel can only be favorited by members. \"{{.Username}}\" is not a member."
       },
    +  {
    +    "id": "app.import.validate_direct_post_import_data.attachment.error",
    +    "translation": "Failed to validate direct post attachment data."
    +  },
       {
         "id": "app.import.validate_direct_post_import_data.channel_members_required.error",
         "translation": "Missing required direct post property: channel_members"
    @@ -5722,10 +5730,18 @@
         "id": "app.import.validate_emoji_import_data.image_missing.error",
         "translation": "Import emoji image field missing or blank."
       },
    +  {
    +    "id": "app.import.validate_emoji_import_data.invalid_image_path.error",
    +    "translation": "Import emoji image field has an invalid path: \"{{.Path}}\""
    +  },
       {
         "id": "app.import.validate_emoji_import_data.name_missing.error",
         "translation": "Import emoji name field missing or blank."
       },
    +  {
    +    "id": "app.import.validate_post_import_data.attachment.error",
    +    "translation": "Failed to validate post attachment data."
    +  },
       {
         "id": "app.import.validate_post_import_data.channel_missing.error",
         "translation": "Missing required Post property: Channel."
    @@ -5782,6 +5798,10 @@
         "id": "app.import.validate_reaction_import_data.user_missing.error",
         "translation": "Missing required Reaction property: User."
       },
    +  {
    +    "id": "app.import.validate_reply_import_data.attachment.error",
    +    "translation": "Failed to validate reply attachment data."
    +  },
       {
         "id": "app.import.validate_reply_import_data.create_at_missing.error",
         "translation": "Missing required Reply property: create_at."
    @@ -5946,6 +5966,10 @@
         "id": "app.import.validate_user_import_data.guest_roles_conflict.error",
         "translation": "User roles are not consistent with guest status."
       },
    +  {
    +    "id": "app.import.validate_user_import_data.invalid_image_path.error",
    +    "translation": "User profile image path is invalid: \"{{.Path}}\""
    +  },
       {
         "id": "app.import.validate_user_import_data.last_name_length.error",
         "translation": "User Last Name is too long."
    

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.