Opened 3 days ago
Last modified 3 days ago
#66409 new defect (bug)
REST API: Raw-body media upload leaves its temporary file behind when the multisite size check fails
| Reported by: | ExtremsX | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | REST API | Version: | 4.9.8 |
| Severity: | normal | Keywords: | has-patch has-unit-tests |
| Cc: | Focuses: |
Description
WP_REST_Attachments_Controller::upload_from_data() writes the request body to a file created by wp_tempnam() and then calls check_upload_size(). When that check fails (file too big, not enough upload space, or the user is over quota), the method returns the error without deleting the file:
$size_check = self::check_upload_size( $file_data ); if ( is_wp_error( $size_check ) ) { return $size_check; }
So on a multisite network with upload limits enabled, every rejected raw-body upload (POST /wp/v2/media with the file as the request body) leaves a copy of its body in the temporary directory. A user who can upload files can repeat this to fill the disk, which is what the quota is meant to prevent.
The same method already deletes the file when wp_handle_sideload() fails, and the URL sideload path deletes it after the same check_upload_size() failure.
Steps to reproduce
- Multisite, Network Settings: enable the upload space check and set "Max upload file size" to 1 KB (
fileupload_maxk = 1,upload_space_check_disabled = 0). - As a user who can upload files, send a raw-body upload larger than 1 KB:
curl -u user:application-password -X POST https://example.com/wp-json/wp/v2/media \ -H 'Content-Type: image/png' \ -H 'Content-Disposition: attachment; filename="test.png"' \ --data-binary @five-kb-file.png
- The response is
400 rest_upload_file_too_big, andget_temp_dir()now holdstest-XXXXXX.tmpwith the full body. Each request adds one more.
Reproduced on 7.1.4 (multisite, subdirectory, PHP 8.2); trunk has the same code.
Patch
Delete the temporary file before returning the size error, plus a test that the file is gone after a rejected upload. PR to follow.
Found with code-intruder, my own static analysis tool, which flags temporary files left behind on an exit path. AI tools (Claude Code) helped confirm and reproduce it; I reviewed and tested the change myself.
Change History (3)
This ticket was mentioned in PR #15134 on WordPress/wordpress-develop by @ExtremsX.
3 days ago
#1
@umesh.nevase commented on PR #15134:
3 days ago
#3
Test Report
This report validates that the indicated patch addresses the issue.
Patch tested: https://github.com/WordPress/wordpress-develop/pull/15134
Environment
- WordPress: PHPUnit: 7.2-beta3-64914-src (trunk d49cecb0f0 + PR commit 74f05b7160). Manual: 7.2-beta2-20261028.171134 build (multisite, subdirectory install), with the PR's controller file copied in.
- PHP: 8.4.27
- Server: nginx (Laravel Valet)
- Database: MariaDB 11.7.2
- Browser: N/A (REST API requests via curl)
- OS: macOS (Darwin 25.6.0)
- Theme: N/A
- Plugins: None activated
Steps taken
1. PHPUnit
- Checked out the PR branch on top of trunk.
- Ran:
WP_MULTISITE=1 phpunit -c tests/phpunit/multisite.xml --filter test_create_item_with_data_exceeds_multisite_max_filesize - Reverted only the source change in
class-wp-rest-attachments-controller.php(keeping the new test) and re-ran, to confirm the test fails without the fix. - Restored the patch, re-ran the test, then ran the REST attachments controller tests.
2. Manual test (multisite)
- In Network Settings, set Max upload file size to 1 KB and enabled the upload-space check.
- Logged in as an administrator and sent
POST /wp-json/wp/v2/mediawith the raw body of a ~198 KB JPEG (Content-Type: image/jpeg,Content-Disposition: attachment; filename=canola.jpg). - Checked the PHP temp directory for leftover
canola-*.tmpfiles. - Repeated the request with the PR's
class-wp-rest-attachments-controller.phpin place.
Expected Results
When a raw-body REST upload is rejected by the multisite size check, the temporary file is removed.
Actual Results
- PHPUnit, without the patch:
test_create_item_with_data_exceeds_multisite_max_filesize_removes_temp_filefails, because a leftovercanola-*.tmpfile remains after therest_upload_file_too_bigerror. - PHPUnit, with the patch: the test passes (2 tests, 9 assertions).
- PHPUnit, with the patch: the REST attachments controller tests pass (233 tests, 1268 assertions, 2 skipped), so no regressions were found.
- Manual, without the patch: the API returns
rest_upload_file_too_big(400), but one newcanola-*.tmpfile is left in the temp dir. - Manual, with the patch: the same 400 response is returned and no temp file is left behind.
Additional Notes
- The code path only runs on multisite, because
check_upload_size()returns early on single-site installs. - Not tested manually: the
rest_upload_limited_spaceandrest_upload_user_quota_exceededbranches. They use the same early return, so the same change covers them.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
WP_REST_Attachments_Controller::upload_from_data()writes the request body to afile from
wp_tempnam()before callingcheck_upload_size(). When that checkfails on multisite (file too big, not enough upload space, or user over quota),
the method returned the error without deleting the file, so every rejected
raw-body upload left a copy of its body in the temporary directory.
This deletes the file before returning, as the method already does when
wp_handle_sideload()fails and as the URL sideload path does after the samecheck.
Adds
test_create_item_with_data_exceeds_multisite_max_filesize_removes_temp_file()(
ms-required), which checks that nocanola-*.tmpis left after a rejected upload.Tested on a local 7.1.4 multisite (subdirectory): with
fileupload_maxk = 1, a5 KB raw-body upload returned
400 rest_upload_file_too_bigand left one.tmp(5120 bytes) in the temp dir; with the change, the same request leaves none.Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code, with code-intruder (my own static analysis tool)
Model(s): Claude Opus, Claude Haiku
Used for: code-intruder flagged the temporary file left on the error return and
an AI review triaged the finding. AI helped reproduce it on a local multisite
install (REST request, checking the temp dir before/after) and write the test.
I reviewed and tested the change myself.