koala73/worldmonitor · error · Error

payload_too_large

Error message

payload_too_large

What it means

Thrown by readBodyWithLimit() in the security report edge function when the Content-Length request header is present, finite, and exceeds MAX_REPORT_BYTES (32 KB / 32768 bytes). This is the fast-path rejection — the body is never read, saving edge function execution time and memory. The function is used to protect the CSP/COEP/CORP violation-reporting endpoint from oversized payloads.

Source

Thrown at api/security/report.js:68

    effectivePolicy: shortString(body.effectivePolicy, 120),
    blockedURLOrigin: safeOrigin(body.blockedURL),
    destination: shortString(body.destination, 80),
  };
}

function summarizeReports(payload) {
  const reports = Array.isArray(payload) ? payload : [payload];
  return {
    count: reports.length,
    truncated: reports.length > MAX_REPORT_ITEMS,
    reports: reports.slice(0, MAX_REPORT_ITEMS).map(summarizeReportItem),
  };
}

async function readBodyWithLimit(req) {
  const contentLength = Number(req.headers.get('content-length') ?? 0);
  if (Number.isFinite(contentLength) && contentLength > MAX_REPORT_BYTES) {
    throw new Error('payload_too_large');
  }

  if (!req.body) return '';

  const reader = req.body.getReader();
  const chunks = [];
  let total = 0;

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;

    total += value.byteLength;
    if (total > MAX_REPORT_BYTES) {
      await reader.cancel();
      throw new Error('payload_too_large');
    }
    chunks.push(value);

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Reduce the report payload to under 32 KB — send fewer reports per POST, or split a large batch into multiple requests (MAX_REPORT_ITEMS is 20).
  2. If the report is a single oversized item, trim or summarize it client-side before sending.
  3. If legitimate traffic routinely exceeds 32 KB, consider raising MAX_REPORT_BYTES (but weigh edge function memory/execution limits).
  4. Clients should set a batch flush timer or count limit so they never accumulate >32 KB before POSTing.

Example fix

// before — client sends 500 reports in one POST (>32KB)
fetch('/api/security/report', { method: 'POST', body: JSON.stringify(allReports) })
// after — batch into chunks of 20
for (let i = 0; i < allReports.length; i += 20) {
  await fetch('/api/security/report', { method: 'POST', body: JSON.stringify(allReports.slice(i, i + 20)) });
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate payload size before POSTing to the report endpoint
const MAX_REPORT_BYTES = 32 * 1024; // 32 KB — mirror the server limit
const MAX_REPORT_ITEMS = 20;
function batchReports(reports) {
  return reports.slice(0, MAX_REPORT_ITEMS).map(r => JSON.stringify(r)).join('');
}
const serialized = batchReports(myReports);
if (new Blob([serialized]).size > MAX_REPORT_BYTES) {
  // Split into smaller batches
  const chunks = chunkArray(myReports, 10); // halve the batch
  for (const chunk of chunks) {
    await postReport(chunk);
  }
} else {
  await postReport(myReports);
}

Prevention

When it happens

Trigger: POSTing to /api/security/report with a Content-Length header greater than 32768 bytes. This could be a single very large report or an array of reports whose total serialized size exceeds 32 KB. The check uses the header value before any body reading, so even a streaming body is never consumed.

Common situations: A browser sending a large violation report batch (many CSP violations in one POST); a malicious or buggy client sending an oversized payload; a reporting client that does not batch-limit its reports; a misconfigured report endpoint collecting too many violations before flushing.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/2a4bd343a8aad207. Report an issue: GitHub.