The extension-only check in the original version is a classic mistake precisely because it looks sufficient in casual testing. Real MIME sniffing plus disabling execution in the upload directory is the combination that actually closes the gap, not either one alone.
Code
Build a secure custom file upload handler
A front-end resume upload scenario covering real MIME sniffing versus extension checks, execution-proofing the uploads directory, and safe filename handling.
Works with Claude / GPT1,300 uses★ 4.3
The prompt
code-secure-file-upload-handler
I need a custom front-end form where job applicants upload a resume (PDF or DOCX, max 5MB) that gets attached to a custom post type entry. This is not going through the standard wp-admin media uploader, it's a public-facing form on a careers page. CONTEXT: [logged-out visitors can submit this form, and a previous version of this feature only checked the file extension, which is how a security scan flagged it as a potential arbitrary file upload risk] Rebuild this like a senior dev doing a security-focused rewrite, in order: 1. Explain precisely why extension-checking alone is insufficient: a file named `resume.pdf` can still contain executable PHP if the server is misconfigured to execute it, or can be a disguised file type that later processing (a PDF parser, a preview generator) chokes on unexpectedly. Show the correct check using `wp_check_filetype_and_ext()` combined with a real content sniff via PHP's `finfo` class, not just `pathinfo()`. 2. Enforce a strict allowlist of exactly two MIME types (`application/pdf` and `application/vnd.openxmlformats-officedocument.wordprocessingml.document`), reject anything else with a clear error, and enforce the 5MB limit server-side (not just via the HTML `accept` and a JS size check, which a direct POST bypasses entirely). 3. Use `wp_handle_upload()` with a custom `upload_dir` filter to store these in a dedicated subdirectory, and show the `.htaccess` (or Nginx equivalent) rule to disable PHP execution in that directory entirely, so even if a malicious file somehow got past validation, it can't be executed by requesting it directly. 4. Sanitize the filename with `sanitize_file_name()` and generate a randomized prefix to prevent path traversal attempts and to avoid collisions or information leakage from predictable filenames (an attacker guessing other applicants' resume URLs). 5. Require a nonce and a lightweight rate limit (e.g. a transient keyed by IP) on the submission endpoint itself, since this form is reachable by anyone without login, making it a target for automated abuse even before considering the file content. Adjust the file types, size limit, and storage location for your actual use case.