restricting icon size to 100kb. (#32834)

fixes https://github.com/fleetdm/fleet/issues/32826

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

- [x] Confirmed that the fix is not expected to adversely impact load
test results
- [x] Alerted the release DRI if additional load testing is needed
This commit is contained in:
Konstantin Sykulev
2025-09-10 18:25:44 -05:00
committed by GitHub
parent db0b92f738
commit e2fd468c22
3 changed files with 71 additions and 7 deletions
@@ -5,6 +5,7 @@ import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import { IAppStoreApp, ISoftwarePackage } from "interfaces/software";
import { NotificationContext } from "context/notification";
import { getErrorReason } from "interfaces/errors";
import softwareAPI from "services/entities/software";
import Modal from "components/Modal";
@@ -529,7 +530,8 @@ const EditIconModal = ({
setIconUploadedAt(new Date().toISOString());
onExitEditIconModal();
} catch (e) {
renderFlash("error", DEFAULT_ERROR_MESSAGE);
const errorMessage = getErrorReason(e) || DEFAULT_ERROR_MESSAGE;
renderFlash("error", errorMessage);
} finally {
setIsUpdatingIcon(false);
}
@@ -12226,6 +12226,47 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareTitleIcons() {
require.Equal(t, iconUrl, *details.SoftwareIconURL)
require.Equal(t, titleID, details.SoftwareTitleID)
// PUT: Icon too large
// Create a fake large file (101KB of data)
largeData := make([]byte, 101*1024)
reader := bytes.NewReader(largeData)
iconFile, err := fleet.NewTempFileReader(reader, func() string { return t.TempDir() })
require.NoError(t, err)
var bufInvalid bytes.Buffer
writer = multipart.NewWriter(&bufInvalid)
fileWriter, err = writer.CreateFormFile("icon", "icon.png")
require.NoError(t, err)
_, err = io.Copy(fileWriter, iconFile)
require.NoError(t, err)
err = writer.Close()
require.NoError(t, err)
headers = map[string]string{
"Content-Type": writer.FormDataContentType(),
"Authorization": fmt.Sprintf("Bearer %s", s.token),
}
resp = s.DoRawWithHeaders(
"PUT",
fmt.Sprintf("/api/latest/fleet/software/titles/%d/icon?team_id=%d", titleID, tm.ID),
bufInvalid.Bytes(),
http.StatusBadRequest,
headers,
)
var errorResp struct {
Message string `json:"message"`
Errors []struct {
Name string `json:"name"`
Reason string `json:"reason"`
} `json:"errors"`
}
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
err = json.Unmarshal(body, &errorResp)
require.NoError(t, err)
assert.Equal(t, "Bad request", errorResp.Message)
require.Len(t, errorResp.Errors, 1)
assert.Contains(t, errorResp.Errors[0].Reason, "icon must be less than 100KB")
// PUT: gitops workflow, passing in sha256 & filename
var storedIcons []fleet.SoftwareTitleIcon
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
+27 -6
View File
@@ -171,6 +171,19 @@ func (putSoftwareTitleIconRequest) DecodeRequest(ctx context.Context, r *http.Re
}
}
// Validate the file if one is provided
if decoded.File != nil {
file, err := decoded.File.Open()
if err != nil {
return nil, &fleet.BadRequestError{Message: "failed to open file"}
}
defer file.Close()
if err := iconValidator(file); err != nil {
return nil, err
}
}
return &decoded, nil
}
@@ -189,12 +202,6 @@ func putSoftwareTitleIconEndpoint(ctx context.Context, request interface{}, svc
}
defer file.Close()
// ensure icon is png and fits sizing restrictions
err = iconValidator(file)
if err != nil {
return putSoftwareTitleIconResponse{Err: err}, nil
}
tfr, err := fleet.NewTempFileReader(file, nil)
if err != nil {
return putSoftwareTitleIconResponse{Err: err}, nil
@@ -228,6 +235,20 @@ func (svc *Service) UploadSoftwareTitleIcon(ctx context.Context, payload *fleet.
}
func iconValidator(file multipart.File) error {
// Check file size first
fileSize, err := file.Seek(0, 2) // Seek to end to get size
if err != nil {
return &fleet.BadRequestError{Message: "failed to read file size"}
}
if _, err := file.Seek(0, 0); err != nil { // Reset to beginning
return &fleet.BadRequestError{Message: "failed to rewind file"}
}
maxSize := int64(100 * 1024) // 100KB
if fileSize > maxSize {
return &fleet.BadRequestError{Message: "icon must be less than 100KB"}
}
config, format, err := image.DecodeConfig(file)
if err != nil || format != "png" {
return &fleet.BadRequestError{Message: "icon must be a PNG image"}