← All posts
file upload securityvibe codingAI app securitysecure uploadsNext.js security

Fix Insecure File Uploads in Vibe-Coded Apps: A Step-by-Step Guide

O

OverMCP Team

Quick answer

Insecure file uploads happen when vibe-coded apps fail to validate file types, limit sizes, or scan content. To fix this, restrict uploads to safe extensions, enforce server-side validation, store files outside the webroot, and use a free AI app security scanner like OverMCP to catch leaks before launch.

What to check first

Before you start coding fixes, audit your current upload implementation. Here's a quick checklist:

  • [ ] File type whitelist: Are you checking the MIME type server-side, not just the extension? AI code often only checks the client-side extension.
  • [ ] File size limit: Is there a maximum size enforced? Many AI-generated forms skip this.
  • [ ] Upload directory: Are uploads stored in a publicly accessible folder (e.g., /public/uploads)?
  • [ ] File name sanitization: Are user-provided filenames cleaned to prevent path traversal?
  • [ ] Content scanning: Are you scanning for malware or executable code?
  • [ ] Authentication: Is the upload endpoint protected by authentication?
  • [ ] Rate limiting: Can an attacker flood your server with uploads?
  • Run a free AI app security scanner to automatically detect exposed upload directories and leaked secrets.

    Step-by-step fix

    Let's walk through securing a file upload endpoint in a typical Next.js app built with Cursor or v0.

    1. Validate file type server-side

    AI-generated upload handlers often trust the client's Content-Type. Always re-check on the server.

    // pages/api/upload.js
    import multer from 'multer';
    import path from 'path';
    
    const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
    const MAX_SIZE = 5 * 1024 * 1024; // 5MB
    
    const storage = multer.diskStorage({
      destination: './uploads',
      filename: (req, file, cb) => {
        // Sanitize filename: remove path separators and special chars
        const safeName = file.originalname.replace(/[^a-zA-Z0-9._-]/g, '_');
        cb(null, `${Date.now()}-${safeName}`);
      }
    });
    
    const upload = multer({
      storage,
      limits: { fileSize: MAX_SIZE },
      fileFilter: (req, file, cb) => {
        if (ALLOWED_TYPES.includes(file.mimetype)) {
          cb(null, true);
        } else {
          cb(new Error('Invalid file type'));
        }
      }
    });
    
    export default function handler(req, res) {
      upload.single('file')(req, res, (err) => {
        if (err) return res.status(400).json({ error: err.message });
        res.status(200).json({ message: 'Upload successful' });
      });
    }

    2. Store files outside the webroot

    Never store uploads inside public/. Instead, use a dedicated directory and serve files via a controlled endpoint.

    // Example: serve file through an API route
    import fs from 'fs';
    import path from 'path';
    
    export default function handler(req, res) {
      const filePath = path.join(process.cwd(), 'uploads', req.query.filename);
      if (!fs.existsSync(filePath)) return res.status(404).end();
      // Set appropriate Content-Type and security headers
      res.setHeader('Content-Type', 'image/png');
      res.setHeader('Content-Disposition', 'inline');
      res.setHeader('X-Content-Type-Options', 'nosniff');
      fs.createReadStream(filePath).pipe(res);
    }

    3. Add security headers to upload responses

    Prevent MIME sniffing and other attacks.

    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('Cache-Control', 'no-store');

    4. Scan for secrets in uploaded files

    Use a secret leak scanner to check if any uploaded file contains API keys or credentials.

    5. Enforce authentication and rate limiting

    Wrap your upload handler with authentication middleware and use a rate limiter.

    import rateLimit from 'express-rate-limit';
    
    const limiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 10, // limit each IP to 10 uploads per window
    });
    
    export default async function handler(req, res) {
      await new Promise((resolve) => limiter(req, res, resolve));
      // ... rest of upload logic
    }

    Common mistakes

    Vibe-coded apps often make these mistakes with file uploads:

  • Only client-side validation: AI code typically adds a accept="image/*" attribute but forgets server-side checks. Attackers can bypass this easily.
  • Storing uploads in public folders: Many AI scaffolds put uploads in /public/uploads, making them directly accessible and executable.
  • No size limits: Without limits, attackers can fill your disk or cause denial of service.
  • Trusting filenames: User-provided filenames like ../../etc/passwd can lead to path traversal if not sanitized.
  • Ignoring file content: Even if the extension is .jpg, the content could be a PHP script or malware.
  • Missing authentication: Public upload endpoints can be abused to store illegal content.
  • To catch these issues before they become problems, use continuous security monitoring to scan your app after every deploy.

    FAQ

    What is the most dangerous file upload vulnerability?

    Storing files in a publicly accessible directory without proper type validation can allow attackers to upload executable scripts (e.g., PHP, JS) and run them on your server. This can lead to full server compromise.

    How do I test if my upload endpoint is secure?

    Try uploading a file with a fake extension (e.g., malicious.php.jpg) and check if it's saved with the original name. Use a security headers checker to verify response headers. Also, attempt a path traversal in the filename.

    Can I trust AI-generated upload code?

    No. AI tools like Cursor and v0 often generate minimal, insecure upload handlers. Always audit the code for the common mistakes listed above. Use OverMCP to scan your entire app for file upload vulnerabilities and other OWASP Top 10 issues.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free