Skip to content

[Bug][Level 2] deploy endpoints accept files array with no count or per-entry size limits — authenticated users can exhaust server memory #383

Description

@anshul23102

Summary

Both deploy routes (/api/deploy/vercel and /api/deploy/netlify) parse a files field from the request body and iterate over it with no validation of array length or per-entry content size. Sending an array with thousands of large entries exhausts Node.js heap memory before any data reaches Vercel or Netlify.

The upload-zip route in the same codebase correctly applies MAX_TOTAL_UNCOMPRESSED_SIZE = 10 MB and MAX_SINGLE_FILE_SIZE = 500 KB. The deploy routes have no equivalent guard.

Affected Files

  • app/api/deploy/vercel/route.ts
  • app/api/deploy/netlify/route.ts

Root Cause

// vercel/route.ts
const { files, name, userApiKey } = await req.json();

if (!files || !Array.isArray(files)) {
    return NextResponse.json({ error: "No files provided" }, { status: 400 });
}

// No length check, no per-file size check
const flatFiles = files.map(f => ({
    file: f.path,
    data: f.content          // f.content can be arbitrarily large
}));
// netlify/route.ts
files.forEach((f: any) => {
    zip.file(f.path, f.content);  // same pattern, no size guard
});

Attack Scenario

An authenticated user sends:

{
  "files": [
    { "path": "index.html", "content": "<1MB string>" },
    ... repeated 500 times ...
  ]
}

500 entries x 1 MB each = 500 MB held in memory while files.map(...) and zip.generateAsync(...) run. On a typical serverless instance (256-512 MB) this kills the function mid-request. Repeated calls keep the function restarting, degrading the deploy feature for all users.

Fix

Add a count cap and a per-entry content size cap before the mapping step. Reuse the constants already defined in upload-zip:

const MAX_FILE_COUNT = 500;
const MAX_ENTRY_SIZE = 500_000;  // 500 KB per file
const MAX_TOTAL_SIZE = 10 * 1024 * 1024;  // 10 MB aggregate

if (files.length > MAX_FILE_COUNT) {
    return NextResponse.json({ error: `Too many files. Maximum is ${MAX_FILE_COUNT}.` }, { status: 413 });
}

let totalSize = 0;
for (const f of files) {
    if (typeof f.content !== "string") {
        return NextResponse.json({ error: "Each file entry must have a string content field." }, { status: 400 });
    }
    const entrySize = Buffer.byteLength(f.content, "utf8");
    if (entrySize > MAX_ENTRY_SIZE) {
        return NextResponse.json({ error: `File ${f.path} exceeds the 500 KB per-file limit.` }, { status: 413 });
    }
    totalSize += entrySize;
    if (totalSize > MAX_TOTAL_SIZE) {
        return NextResponse.json({ error: "Total payload exceeds the 10 MB limit." }, { status: 413 });
    }
}

Apply the same block in both /vercel/route.ts and /netlify/route.ts.

GSSoC'26 contribution

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions