GUIDE · WORDPRESS
How to scan WordPress uploads for malware
WordPress stores whatever your contact form receives. Here is how to check it first — with a plugin, or with three lines of PHP.
WordPress does not scan uploaded files. Not in the media library, not through Contact Form 7, not through WooCommerce. Whatever arrives is written to wp-content/uploads and served back from your domain.
For a site with a public contact form or a job-application page, that is a genuine exposure — and it is usually the finding that prompts people to go looking for a fix.
The easy way: a plugin
If you would rather not touch code, install the UpScan plugin, paste an API key, and every upload is checked before WordPress stores it — media library, Contact Form 7, Gravity Forms, WPForms and WooCommerce included.
The settings you actually need to think about:
- What to scan. Media library uploads and form attachments. Leave both on; public forms are where hostile files actually arrive.
- If the scanner is unreachable. Allow uploads (your site keeps working) or block them (safest). Either way the event is logged.
- Email alerts. Worth turning on — a blocked upload is something you want to know about.
The code way
If you are building something custom, WordPress gives you exactly the right hook. wp_handle_upload_prefilter runs after the file reaches a temporary location but before WordPress moves it into place — so returning an error stops the upload cleanly.
add_filter( 'wp_handle_upload_prefilter', function ( $file ) {
$response = wp_remote_post( 'https://upscan.desaihome.uk/v1/scans', array(
'timeout' => 20,
'headers' => array( 'Authorization' => 'Bearer ' . UPSCAN_KEY ),
'body' => array( 'file' => new CURLFile( $file['tmp_name'] ) ),
) );
if ( is_wp_error( $response ) ) {
return $file; // scanner unreachable — decide your failure mode deliberately
}
$verdict = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! empty( $verdict['verdict'] ) && 'infected' === $verdict['verdict'] ) {
$file['error'] = 'This file was blocked because it contains malware.';
}
return $file;
} );
Setting $file['error'] is what rejects the upload — WordPress surfaces the message to whoever tried.
Form plugins need their own hooks
The prefilter covers media uploads, but form plugins validate attachments separately:
| Plugin | Hook |
|---|---|
| Contact Form 7 | wpcf7_validate_file and wpcf7_validate_file* |
| Gravity Forms | gform_validation |
| WPForms | wpforms_process |
Miss these and your media library is protected while your public contact form is not — which is precisely backwards, since the contact form is the one strangers can reach.
Three things worth getting right
Scan before storing, not after. A file that has already been written to wp-content/uploads is already reachable by URL. The gap between storing and scanning is the window an attacker needs.
Do not trust the extension. WordPress checks the extension against an allowed list, which stops the laziest attacks and nothing more. An executable renamed invoice.pdf passes that check. Content-based checking is what catches it.
Watch out for macro documents. A .docx is a ZIP file, and a macro-enabled one contains vbaProject.bin inside. WordPress cannot tell the difference; a scanner that inspects archive contents can.
Testing it
Use the EICAR test file rather than real malware. Upload it through the media library — it should be rejected. Then zip it and upload again, which confirms archive scanning. Then submit it through your actual contact form, because that is the path that matters.
If all three are blocked and a normal PDF still uploads fine, you are done.
Scan your first file free
100 scans a month, no card required. Files scanned in the UK and deleted the moment scanning finishes.
Start free