Secure File Uploads in React Actions
Cover photo by Arnold Francisca on Unsplash
React Server Actions make file uploads feel like standard form submissions. I believe this convenience is dangerous because it encourages developers to skip critical validation steps. You must treat every incoming file as a potential exploit attempt.
Don’t trust the client
I see developers rely on input type="file" accept attributes or frontend validation way too much. Those are for user experience, not security. A user can easily bypass your browser checks with a simple curl command or a proxy tool. Your server code needs to be the primary line of defense.
Validate everything immediately
Here is how I handle it in a basic Server Action. You need to verify the file type, check the file size, and rename the file before it hits your storage layer.
"use server";
export async function uploadAction(formData) { const file = formData.get("file"); if (!file || !(file instanceof File)) return { error: "No file found" };
// Check file size (e.g., limit to 5MB) if (file.size > 5 * 1024 * 1024) return { error: "File too large" };
// Validate MIME type (be strict) if (!["image/png", "image/jpeg"].includes(file.type)) { return { error: "Invalid file type" }; }
const buffer = Buffer.from(await file.arrayBuffer()); // Process or upload to cloud storage here}(It is worth noting that file extension checks are not enough because anyone can rename a malicious script to look like an image). You should also consider using a library to verify the file content headers, though that can get complex fast. I admit I sometimes skip the content sniffing if I’m strictly saving files to an isolated bucket that doesn’t execute code.
Stop naming files from the user
“Why not just save the file using the original filename?” Someone might ask this because it seems easier. Please don’t. If you use the user provided filename, you open your system up to path traversal attacks and potential overwriting of critical system files. Always generate a random UUID for the filename on your server.
Use an isolated storage bucket
Even with the best validation, bugs happen. If you save user files to your web server directory, you’re asking for trouble. Keep uploads in a separate storage bucket (like S3 or a similar cloud store) with a different domain. If someone manages to upload a script, it won’t be able to execute in the context of your main application domain (which saves you from a lot of cross-site scripting headaches).
Keep it simple
I prefer keeping my upload logic outside of my database transactions. It keeps things clean. If you can, process the file asynchronously. Upload to storage, add a reference to your database, and let a background worker handle image resizing or virus scanning. It’s not always possible for every project, but it makes your main request cycle much faster.
What is your go-to strategy for scanning files before they hit your infrastructure? I am still looking for a lighter way to do this without ballooning my build times.