fix(security): drop file:// support from createMediaItem mutation#3834
Conversation
…otocols The file protocol was kept in the default $allowed_protocols list when wp_http_validate_url() was added in v1.14.6 (#2840), but that validator rejects file:// URLs, making the entry unreachable in practice. A public code search shows no consumers calling the graphql_media_item_create_allowed_protocols filter, so removing 'file' from the default has no known compatibility impact. Sites that legitimately rely on local-file sideload (e.g., migration scripts) can re-enable it via the filter, which still routes through the wp_upload_bits branch in MediaItemCreate. Adds a regression test that asserts a file:// filePath is rejected and that no attachment URL is returned.
Following the removal of 'file' from the default $allowed_protocols list in the previous commit, the file_get_contents() call and the wp_upload_bits branch that consumed it are unreachable: wp_http_validate_url() rejects file:// URLs (no host, scheme stripped to http/https), so the request errors out before the protocol allowlist check is even consulted. This also corrects a misleading claim in the prior commit message: the graphql_media_item_create_allowed_protocols filter alone never functioned as an escape hatch for local-file sideload, because wp_http_validate_url() gates the request first. The local-sideload feature has been effectively unreachable since v1.14.6. Removing the branch eliminates the file_get_contents() sink named in the external security report and leaves a single, clear download_url() path for createMediaItem.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3834 +/- ##
=======================================
Coverage 83.5% 83.5%
+ Complexity 5201 5198 -3
=======================================
Files 286 286
Lines 22567 22563 -4
=======================================
- Hits 18834 18832 -2
+ Misses 3733 3731 -2
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Heads up — while building the follow-up test hardening on The Concrete demo on The test took ~60s (PHP's default stream-wrapper timeout) because What this means for #3834:
Suggested addition to the PR description (paraphrased): "This also closes an SSRF vector reachable via the same |
Summary
'file'from the default$allowed_protocolslist increateMediaItem, so the mutation no longer advertises local-file sideload as supported.file_get_contents()+wp_upload_bitsbranch that consumedfile://paths — including thefile_get_contents()call named as the sink in a recent external security report.testFileProtocolIsRejected) assertingfile://filePaths are rejected and no attachment URL is returned.Why
'file'was added to the default$allowed_protocolsin v1.14.6 (#2840) alongside awp_http_validate_url()check pushed in the same PR.wp_http_validate_url()strips non-http/httpsschemes and requires a non-empty host, sofile://...URLs have been rejected by core validation before the protocol allowlist is ever consulted. That made:'file'allowlist entry effectively a no-op,wp_upload_bitsbranch (lines 236–239 prior) unreachable in stock WordPress,graphql_media_item_create_allowed_protocolsfilter unable to serve as a real escape hatch forfile://, sincewp_http_validate_url()runs first and has no filter inside it.A public GitHub code search for
graphql_media_item_create_allowed_protocolsreturns only WPGraphQL's own source (vendored copies), no consumer code callingadd_filter(). So removingfilefrom the default has no known compatibility impact.An external security researcher reported the design intent (allowing
file+ callingfile_get_contents()on user input) as a Critical LFI vulnerability. The exploit as written is not reachable on stock WordPress for the reasons above, but the design smell is real defense-in-depth weakness: anything that loosenedwp_http_validate_url()would flip this to exploitable. This PR removes both the smell and the sink.What changed
plugins/wp-graphql/src/Mutation/MediaItemCreate.php$allowed_protocols = [ 'https', 'http', 'file' ]→[ 'https', 'http' ]$file_contents = file_get_contents( $input['filePath'] )and the conditionalwp_upload_bitsbranch that depended on it.plugins/wp-graphql/tests/wpunit/MediaItemMutationsTest.phptestFileProtocolIsRejectedregression test.Test plan
MediaItemMutationsTestruns green locally (28 tests, 41 assertions)Follow-up (not in this PR)
Adjacent
createMediaItemtest coverage is fragile and SSRF-thin. A separate PR will:raw.githubusercontent.com,169.254.169.254, RFC1918 ranges, IPv6 loopback, and redirects to private IPs,wp_handle_sideloadfailure test.