Skip to content

Add software installer file size check before upload - #50475

Merged
jkatz01 merged 6 commits into
mainfrom
42735-fix-inconsistent-error-message
Aug 5, 2026
Merged

Add software installer file size check before upload#50475
jkatz01 merged 6 commits into
mainfrom
42735-fix-inconsistent-error-message

Conversation

@jkatz01

@jkatz01 jkatz01 commented Aug 3, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #42735

  • Exposes max_software_package_size in the GET /api/v1/fleet/config endpoint
  • Add frontend logic to use it to deny files that are too big before they get uploaded

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

  • Timeouts are implemented and retries are limited to avoid infinite loops

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

Summary by CodeRabbit

  • New Features

    • Added a configurable maximum software package size to application settings.
    • Software uploads exceeding the limit are rejected with a clear, size-specific error message.
    • Packages at or below the configured limit are accepted.
    • Added user-friendly file-size formatting across common units.
    • Upload validation accounts for the complete request size, including scripts and settings.
  • Tests

    • Added coverage for upload validation, boundary conditions, size formatting, and configuration responses.

jkatz01 added 4 commits August 3, 2026 17:00
Adds max_software_package_size and max_software_package_size_human to
GET /config, and checks a selected file against the limit in PackageForm
so the add, multi-package add, and edit flows all fail fast with the
same wording the server uses.
Drops max_software_package_size_human so GET /config gains only the one
new key. PackageForm formats the byte count with formatFileSize, which
mirrors installersize.Human and is pinned to it by tests.

Also populates the limit on the PATCH /config response, which several
pages feed straight into AppContext. Without it the context value went
to zero after any config save and the size check stopped firing.
Adds max_software_package_size to EnrichedAppConfig so fleetctl get
config --include-server-config reports it, and updates the two golden
files. The client silently dropped the unknown key before, so the flag
that exists to dump server config was leaving this one out.

Also covers the PackageForm size check with tests.
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.61538% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.30%. Comparing base (74c2b79) to head (b40b4f0).
⚠️ Report is 56 commits behind head on main.

Files with missing lines Patch % Lines
...twarePage/components/forms/PackageForm/helpers.tsx 7.69% 12 Missing ⚠️
...ePage/components/forms/PackageForm/PackageForm.tsx 66.66% 6 Missing ⚠️
server/fleet/app.go 40.00% 2 Missing and 1 partial ⚠️
server/service/appconfig.go 85.71% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #50475      +/-   ##
==========================================
+ Coverage   68.13%   68.30%   +0.17%     
==========================================
  Files        3947     3950       +3     
  Lines      251501   252686    +1185     
  Branches    13465    13350     -115     
==========================================
+ Hits       171349   172603    +1254     
+ Misses      64833    64648     -185     
- Partials    15319    15435     +116     
Flag Coverage Δ
backend 69.55% <73.68%> (+0.16%) ⬆️
frontend 61.61% <60.86%> (+0.26%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jkatz01
jkatz01 marked this pull request as ready for review August 4, 2026 21:47
@jkatz01
jkatz01 requested review from a team as code owners August 4, 2026 21:47
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5037f4fe-351e-405c-9afe-c18a37c4a5d8

📥 Commits

Reviewing files that changed from the base of the PR and between 11902a9 and b40b4f0.

📒 Files selected for processing (1)
  • frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx

Walkthrough

The server includes max_software_package_size in app configuration responses. The frontend reads this value, estimates upload request size, and rejects oversized packages before upload processing. It displays formatted, add- or edit-specific error messages. Tests cover configuration output, file-size formatting, oversized packages, and the exact size limit.

Possibly related PRs

  • fleetdm/fleet#50143: Related frontend software-package error handling in EditSoftwareModal/helpers.tsx.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: validating software installer size before upload.
Description check ✅ Passed The description identifies issue #42735, summarizes the implementation, and documents automated and manual testing.
Linked Issues check ✅ Passed The changes expose max_software_package_size and add frontend validation and error handling required by issue #42735.
Out of Scope Changes check ✅ Passed The changes remain focused on configuration exposure, upload-size validation, error formatting, and related tests.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 42735-fix-inconsistent-error-message

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tsx`:
- Around line 222-234: Update the package-size validation in PackageForm to
distinguish a missing limit from a configured zero limit, so
maxSoftwarePackageSize of 0 still rejects any nonempty file before upload.
Preserve the existing error notification and return behavior, and add a test
covering the zero-limit case.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e7d8854-80f3-4e8e-ad82-aaea27be7e79

📥 Commits

Reviewing files that changed from the base of the PR and between 74c2b79 and 31896f4.

📒 Files selected for processing (15)
  • changes/42735-fix-inconsistent-error-message
  • cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
  • cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
  • frontend/__mocks__/configMock.ts
  • frontend/interfaces/config.ts
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/helpers.tsx
  • frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tests.tsx
  • frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tsx
  • frontend/utilities/file/fileUtils.tests.tsx
  • frontend/utilities/file/fileUtils.tsx
  • server/fleet/app.go
  • server/fleet/service.go
  • server/mock/service/service_mock.go
  • server/service/appconfig.go
  • server/service/integration_core_test.go

const file = files[0];

// Reject before uploading if file size is too big
if (maxSoftwarePackageSize && file.size > maxSoftwarePackageSize) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the server caps the entire request body at this value, not the file size. The multipart body adds boundaries plus the script fields. Does a file at exactly the max still fail after uploading in full? If so, should this compare against a slightly lower margin, and should the test change too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I see that now. I think it makes sense to try to calculate the size of the whole request.

The only problem I can see is that maybe the error message would be confusing in this case because it's actually the size of the whole request and not just the file, but the error says "maximum file size".

Comment thread frontend/interfaces/config.ts

const DECIMAL_ABBREVIATIONS = ["B", "kB", "MB", "GB", "TB"];
const BINARY_ABBREVIATIONS = ["B", "KiB", "MiB", "GiB", "TiB"];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server's installersize.Human uses go-units tables that run to YB and YiB. These stop at TB and TiB, so the two disagree above 1 PiB. The server returns 1PiB where this returns 1126TB. Worth adding PB/PiB and EB/EiB?

cdcme
cdcme previously approved these changes Aug 4, 2026

@cdcme cdcme left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of questions, but looks great!

@jkatz01
jkatz01 marked this pull request as draft August 5, 2026 14:02
…e sizes

The server caps the whole request body, so the submit handler now measures
the installer plus the encoded scripts and the other fields instead of the
file on its own.

Dropping omitempty means a configured zero reaches clients as a real value
rather than a missing key, so the update endpoint sets it too and the
browser checks treat zero as a limit instead of as unset.
@jkatz01
jkatz01 marked this pull request as ready for review August 5, 2026 18:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx`:
- Around line 241-277: The current estimateUploadSize approximation does not
measure the raw multipart request body or encoded byte lengths. In
frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx at lines
241-277, replace it with a shared serialized multipart-body measurement or
proven byte-accurate upper bound covering framing, fields, scripts, labels,
categories, and the file. In
frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tsx at
lines 284-291, use that shared request-body size when comparing against the
configured limit, before invoking onSubmit.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8bfa9027-7135-44c4-aa01-a7d09135ba46

📥 Commits

Reviewing files that changed from the base of the PR and between 31896f4 and 11902a9.

📒 Files selected for processing (7)
  • frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tests.tsx
  • frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tsx
  • frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx
  • frontend/utilities/file/fileUtils.tests.tsx
  • frontend/utilities/file/fileUtils.tsx
  • server/fleet/app.go
  • server/service/appconfig.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • server/fleet/app.go
  • frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tests.tsx
  • server/service/appconfig.go
  • frontend/utilities/file/fileUtils.tests.tsx

Comment on lines +241 to +277
/** Calculates the size of the payload, because the server limits the whole
* request and not just the installer file. Not all fields are accounted for
* in this calculation so if the final payload sent is over the size limit,
* the server will reject it.
*/
export const estimateUploadSize = (formData: IPackageFormData) => {
const scripts = [
formData.installScript,
formData.uninstallScript,
formData.preInstallQuery,
formData.postInstallScript,
];

// The scripts are base64 encoded on the way out, so encode them to get the
// length that actually goes over the wire.
let scriptsSize = 0;
scripts.forEach((script) => {
scriptsSize += encodeScriptBase64(script)?.length || 0;
});

// The two flags are sent as "true" or "false".
let fieldsSize =
String(formData.selfService).length +
String(formData.automaticInstall).length;

formData.categories.forEach((category) => {
fieldsSize += category.length;
});

// Labels are only sent when the target is Custom, and only the selected ones.
if (formData.targetType === "Custom") {
listNamesFromSelectedLabels(formData.labelTargets).forEach((label) => {
fieldsSize += label.length;
});
}

return (formData.software?.size || 0) + scriptsSize + fieldsSize;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Measure the multipart body that the upload sends.

The server limits the raw request body before multipart parsing. estimateUploadSize omits multipart framing and fields, and it counts category and label strings by JavaScript code units instead of encoded byte length. A request can pass this check but exceed the server limit, so the user still uploads the file and receives the server error.

  • frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx#L241-L277: Replace the approximation with a shared serialized multipart-body measurement, or a proven byte-accurate upper bound.
  • frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tsx#L284-L291: Compare the configured limit against that shared request-body size before calling onSubmit.
📍 Affects 2 files
  • frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx#L241-L277 (this comment)
  • frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tsx#L284-L291
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx` around
lines 241 - 277, The current estimateUploadSize approximation does not measure
the raw multipart request body or encoded byte lengths. In
frontend/pages/SoftwarePage/components/forms/PackageForm/helpers.tsx at lines
241-277, replace it with a shared serialized multipart-body measurement or
proven byte-accurate upper bound covering framing, fields, scripts, labels,
categories, and the file. In
frontend/pages/SoftwarePage/components/forms/PackageForm/PackageForm.tsx at
lines 284-291, use that shared request-body size when comparing against the
configured limit, before invoking onSubmit.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to only go with estimating the size of the form and not the whole multipart request. It is always more permissive, there should never be a case where the frontend rejects something the backend would accept. The backend check is still there so it's not a big deal.

If we want to improve this in the future, it should probably be done for all endpoints affected by max_software_package_size

if (req.Method == http.MethodPost && strings.HasSuffix(req.URL.Path, "/fleet/software/package")) ||
(req.Method == http.MethodPatch && strings.HasSuffix(req.URL.Path, "/package") && strings.Contains(req.URL.Path,
"/fleet/software/titles/")) ||
(req.Method == http.MethodPost && strings.HasSuffix(req.URL.Path, "/bootstrap")) ||
(req.Method == http.MethodPost && strings.HasSuffix(req.URL.Path, "/fleet_maintained_apps")) ||
(req.Method == http.MethodGet && strings.Contains(req.URL.Path, "/package/token")) ||
(req.Method == http.MethodPost && strings.Contains(req.URL.Path, "orbit/software_install/package")) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"and it counts category and label strings by JavaScript code units instead of encoded byte length" seems like a valid finding though.

Both are user written, so a name outside ASCII takes more bytes than the
string has characters and the estimate came in short.
@jkatz01
jkatz01 merged commit a0805e4 into main Aug 5, 2026
47 checks passed
@jkatz01
jkatz01 deleted the 42735-fix-inconsistent-error-message branch August 5, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Software upload error message is different across web browsers

2 participants