[{"data":1,"prerenderedAt":319},["ShallowReactive",2],{"blog-best-practices/file-uploads":3},{"id":4,"title":5,"body":6,"category":294,"date":295,"dateModified":296,"description":297,"draft":298,"extension":299,"faq":300,"featured":298,"headerVariant":304,"image":305,"keywords":305,"meta":306,"navigation":307,"ogDescription":308,"ogTitle":305,"path":309,"readTime":310,"schemaOrg":311,"schemaType":312,"seo":313,"sitemap":314,"stem":315,"tags":316,"twitterCard":317,"__hash__":318},"blog/blog/best-practices/file-uploads.md","File Upload Best Practices: Validation, Storage, and Security",{"type":7,"value":8,"toc":283},"minimark",[9,16,25,30,33,48,52,55,64,68,71,80,84,87,96,100,103,112,116,194,200,222,226,229,252,271],[10,11,12],"tldr",{},[13,14,15],"p",{},"The #1 file upload security best practice is to never trust file extensions or client-provided MIME types. Validate file content, generate random filenames, store outside web root, limit file sizes, and use a CDN or cloud storage. These practices prevent 85% of file upload vulnerabilities.",[17,18,19],"quotable-box",{},[20,21,22],"blockquote",{},[13,23,24],{},"\"Every uploaded file is a potential attack vector. Treat uploads like untrusted user input-validate everything, trust nothing.\"",[26,27,29],"h2",{"id":28},"best-practice-1-validate-file-type-3-min","Best Practice 1: Validate File Type 3 min",[13,31,32],{},"Check both MIME type and file content, not just extension:",[34,35,37],"code-block",{"label":36},"Complete file validation",[38,39,44],"pre",{"className":40,"code":42,"language":43},[41],"language-text","import { fileTypeFromBuffer } from 'file-type';\n\nconst ALLOWED_TYPES = {\n  'image/jpeg': ['.jpg', '.jpeg'],\n  'image/png': ['.png'],\n  'image/webp': ['.webp'],\n  'application/pdf': ['.pdf'],\n};\nconst MAX_SIZE = 10 * 1024 * 1024; // 10MB\n\nasync function validateFile(file) {\n  const errors = [];\n\n  // Check size\n  if (file.size > MAX_SIZE) {\n    errors.push('File too large (max 10MB)');\n  }\n\n  // Read file buffer to check actual type\n  const buffer = await file.arrayBuffer();\n  const fileType = await fileTypeFromBuffer(buffer);\n\n  if (!fileType || !ALLOWED_TYPES[fileType.mime]) {\n    errors.push('Invalid file type');\n    return { valid: false, errors };\n  }\n\n  // Verify extension matches actual type\n  const ext = file.name.split('.').pop()?.toLowerCase();\n  if (!ALLOWED_TYPES[fileType.mime].includes(`.${ext}`)) {\n    errors.push('File extension does not match content');\n  }\n\n  return {\n    valid: errors.length === 0,\n    errors,\n    mimeType: fileType.mime,\n  };\n}\n","text",[45,46,42],"code",{"__ignoreMap":47},"",[26,49,51],{"id":50},"best-practice-2-generate-safe-filenames-2-min","Best Practice 2: Generate Safe Filenames 2 min",[13,53,54],{},"Never use user-provided filenames directly:",[34,56,58],{"label":57},"Safe filename generation",[38,59,62],{"className":60,"code":61,"language":43},[41],"import { randomUUID } from 'crypto';\nimport path from 'path';\n\nfunction generateSafeFilename(originalName, mimeType) {\n  // Get extension from MIME type, not original filename\n  const extensions = {\n    'image/jpeg': 'jpg',\n    'image/png': 'png',\n    'image/webp': 'webp',\n    'application/pdf': 'pdf',\n  };\n\n  const ext = extensions[mimeType];\n  const uuid = randomUUID();\n\n  return `${uuid}.${ext}`;\n\n  // WRONG: Using original filename\n  // return originalName; // Could be \"../../../etc/passwd.jpg\"\n}\n\n// Store original filename in database if needed\nawait db.file.create({\n  data: {\n    storedName: safeFilename,\n    originalName: file.name,\n    mimeType: fileType.mime,\n    size: file.size,\n    userId: user.id,\n  },\n});\n",[45,63,61],{"__ignoreMap":47},[26,65,67],{"id":66},"best-practice-3-store-outside-web-root-3-min","Best Practice 3: Store Outside Web Root 3 min",[13,69,70],{},"Uploaded files should not be directly accessible:",[34,72,74],{"label":73},"Secure file storage",[38,75,78],{"className":76,"code":77,"language":43},[41],"// WRONG: Storing in public directory\nconst uploadPath = './public/uploads/' + filename;\n// File accessible at: https://site.com/uploads/file.jpg\n\n// CORRECT: Store outside web root, serve via API\nconst uploadPath = './private-uploads/' + filename;\n\n// Serve files through authenticated endpoint\napp.get('/api/files/:id', authenticate, async (req, res) => {\n  const file = await db.file.findUnique({\n    where: { id: req.params.id },\n  });\n\n  // Check authorization\n  if (file.userId !== req.user.id) {\n    return res.status(403).json({ error: 'Access denied' });\n  }\n\n  const filePath = path.join('./private-uploads', file.storedName);\n  res.sendFile(filePath);\n});\n",[45,79,77],{"__ignoreMap":47},[26,81,83],{"id":82},"best-practice-4-use-cloud-storage-5-min","Best Practice 4: Use Cloud Storage 5 min",[13,85,86],{},"Cloud storage (S3, GCS) is more secure and scalable:",[34,88,90],{"label":89},"S3 upload with signed URLs",[38,91,94],{"className":92,"code":93,"language":43},[41],"import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nconst s3 = new S3Client({ region: process.env.AWS_REGION });\n\nasync function uploadToS3(file, filename) {\n  const command = new PutObjectCommand({\n    Bucket: process.env.S3_BUCKET,\n    Key: `uploads/${filename}`,\n    Body: file,\n    ContentType: file.type,\n  });\n\n  await s3.send(command);\n}\n\n// Generate signed URL for download (temporary access)\nasync function getDownloadUrl(filename) {\n  const command = new GetObjectCommand({\n    Bucket: process.env.S3_BUCKET,\n    Key: `uploads/${filename}`,\n  });\n\n  return getSignedUrl(s3, command, { expiresIn: 3600 }); // 1 hour\n}\n",[45,95,93],{"__ignoreMap":47},[26,97,99],{"id":98},"best-practice-5-scan-for-malware-4-min","Best Practice 5: Scan for Malware 4 min",[13,101,102],{},"For sensitive applications, scan uploads:",[34,104,106],{"label":105},"Malware scanning integration",[38,107,110],{"className":108,"code":109,"language":43},[41],"// Using ClamAV\nimport NodeClam from 'clamscan';\n\nconst clamscan = await new NodeClam().init({\n  removeInfected: true,\n  scanLog: '/var/log/clamscan.log',\n});\n\nasync function scanFile(filePath) {\n  const { isInfected, viruses } = await clamscan.scanFile(filePath);\n\n  if (isInfected) {\n    console.warn('Infected file detected:', viruses);\n    await fs.unlink(filePath); // Delete infected file\n    throw new Error('Malicious file detected');\n  }\n\n  return true;\n}\n",[45,111,109],{"__ignoreMap":47},[26,113,115],{"id":114},"common-file-upload-mistakes","Common File Upload Mistakes",[117,118,119,135],"table",{},[120,121,122],"thead",{},[123,124,125,129,132],"tr",{},[126,127,128],"th",{},"Mistake",[126,130,131],{},"Risk",[126,133,134],{},"Prevention",[136,137,138,150,161,172,183],"tbody",{},[123,139,140,144,147],{},[141,142,143],"td",{},"Trusting extensions",[141,145,146],{},"Malicious file execution",[141,148,149],{},"Check actual file content",[123,151,152,155,158],{},[141,153,154],{},"Using original filename",[141,156,157],{},"Path traversal attacks",[141,159,160],{},"Generate random names",[123,162,163,166,169],{},[141,164,165],{},"Storing in web root",[141,167,168],{},"Direct file access",[141,170,171],{},"Store outside, serve via API",[123,173,174,177,180],{},[141,175,176],{},"No size limits",[141,178,179],{},"DoS via large uploads",[141,181,182],{},"Enforce strict size limits",[123,184,185,188,191],{},[141,186,187],{},"No rate limiting",[141,189,190],{},"Storage exhaustion",[141,192,193],{},"Limit uploads per user",[195,196,197],"info-box",{},[13,198,199],{},"External Resources:\nFor comprehensive file upload security guidance, see the\nOWASP File Upload Cheat Sheet\nand the\nOWASP Unrestricted File Upload\ndocumentation for industry-standard security recommendations.",[201,202,203,210,216],"faq-section",{},[204,205,207],"faq-item",{"question":206},"Should I resize images on upload?",[13,208,209],{},"Yes, for several reasons: it reduces storage costs, improves load times, and can strip malicious metadata. Use libraries like Sharp to resize and convert images to safe formats.",[204,211,213],{"question":212},"How do I handle large file uploads?",[13,214,215],{},"Use multipart uploads with resumable support. For cloud storage, generate presigned URLs so clients upload directly to S3/GCS, bypassing your server.",[204,217,219],{"question":218},"Should I keep original filenames?",[13,220,221],{},"Store them in your database for display purposes, but never use them for actual file storage. Always generate random, safe filenames for storage.",[26,223,225],{"id":224},"further-reading","Further Reading",[13,227,228],{},"Put these practices into action with our step-by-step guides.",[230,231,232,240,246],"ul",{},[233,234,235],"li",{},[236,237,239],"a",{"href":238},"/blog/how-to/add-security-headers","Add security headers to your app",[233,241,242],{},[236,243,245],{"href":244},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[233,247,248],{},[236,249,251],{"href":250},"/blog/getting-started/first-scan","Run your first security scan",[253,254,255,261,266],"related-articles",{},[256,257],"related-card",{"description":258,"href":259,"title":260},"Validate all user input","/blog/best-practices/input-validation","Input Validation",[256,262],{"description":263,"href":264,"title":265},"Secure API endpoints","/blog/best-practices/api-design","API Security",[256,267],{"description":268,"href":269,"title":270},"Prevent upload abuse","/blog/best-practices/rate-limiting","Rate Limiting",[272,273,276,280],"cta-box",{"href":274,"label":275},"/","Start Free Scan",[26,277,279],{"id":278},"check-your-file-upload-security","Check Your File Upload Security",[13,281,282],{},"Scan your application for file upload vulnerabilities.",{"title":47,"searchDepth":284,"depth":284,"links":285},2,[286,287,288,289,290,291,292,293],{"id":28,"depth":284,"text":29},{"id":50,"depth":284,"text":51},{"id":66,"depth":284,"text":67},{"id":82,"depth":284,"text":83},{"id":98,"depth":284,"text":99},{"id":114,"depth":284,"text":115},{"id":224,"depth":284,"text":225},{"id":278,"depth":284,"text":279},"best-practices","2026-01-26","2026-02-09","File upload security best practices. Learn to validate uploads, store files safely, prevent malicious uploads, and protect against common file upload vulnerabilities.",false,"md",[301,302,303],{"question":206,"answer":209},{"question":212,"answer":215},{"question":218,"answer":221},"vibe-green",null,{},true,"Secure your file uploads with proper validation and storage practices.","/blog/best-practices/file-uploads","12 min read","[object Object]","Article",{"title":5,"description":297},{"loc":309},"blog/best-practices/file-uploads",[],"summary_large_image","Q62M11sW8GLfYgfaEn0Ivb-VW4S2TlxoSqa-ixUYNDs",1775843926075]