Repository navigation
[Hackathon] Bound non-2xx JSON response bodies before parsing API errors #528
Description
Verified against (commit SHA or released version)
Published @testsprite/testsprite-cli@0.14.0 and source commit 988c0355bb05631e9c6c1785e443937ff2a88c0e. macOS 27.2 (arm64), Node.js v26.12.0.
Discord Username / User ID
shydev69
What does this improvement do?
The HTTP client bounds successful JSON responses but parses non-2xx bodies with an unbounded response.json(). Add a bounded error-body read so an unexpectedly large error does not require buffering and parsing its entire body. This extends the current success-only limit; it is not a claim that the documented option already covers errors.
Details / implementation notes
Steps to reproduce
The injected fetch returns an 8,350-byte response while maxResponseBytes is 1,024. It checks HTTP 200 and HTTP 400, with and without Content-Length. No socket is opened.
From a checkout of the verified commit:
npm ci
npm run buildSave the script below as error-body.mjs, then run:
node error-body.mjs ./disterror-body.mjs
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
const dist = resolve(process.argv[2] ?? './dist');
const { HttpClient } = await import(pathToFileURL(resolve(dist, 'lib/http.js')));
const body = JSON.stringify({ error: {
code: 'VALIDATION_ERROR', message: 'Synthetic validation response',
nextAction: 'No action', requestId: 'local-cap-check',
details: { sample: 'x'.repeat(8192) },
} });
for (const status of [200, 400]) {
for (const declaredLength of [true, false]) {
const client = new HttpClient({
baseUrl: 'https://unused.invalid/api/cli/v1', apiKey: 'sk-dummy-cap-check',
maxResponseBytes: 1024,
fetchImpl: async () => new Response(body, { status, headers: {
'content-type': 'application/json',
...(declaredLength ? { 'content-length': String(Buffer.byteLength(body)) } : {}),
} }),
});
try {
await client.get('/synthetic', { retry: false });
console.log(JSON.stringify({ status, declaredLength, result: 'unexpected success' }));
} catch (error) {
console.log(JSON.stringify({ status, declaredLength,
responseBytes: Buffer.byteLength(body), capBytes: 1024,
errorCode: error.code, retainedSampleLength: error.details?.sample?.length ?? 0,
}));
}
}
}Actual result
The result is the same with and without Content-Length:
| HTTP status | Error code | Retained sample length |
|---|---|---|
| 200 | PAYLOAD_TOO_LARGE |
0 |
| 400 | VALIDATION_ERROR |
8192 |
The error path parses and retains content beyond the configured success-body budget. Expected: a separately defined error-body budget prevents unbounded consumption and returns a useful typed error.
Proposed fix
Use a bounded reader for non-2xx JSON responses, with the cap and truncation/error behavior agreed during triage. Preserve abort, interruption, timeout, and normal API-error handling. Check both declared and streamed lengths.
Relevant code: non-2xx read, safeReadJson, and the success-only option contract.
Related: PR #281 deliberately bounds successful responses only. This proposal does not include the docs-upload diagnostic prefix already covered by PR #409.
Confirmations
- I have searched existing issues and this is not a duplicate.
- I have provided my Discord identity above for reward coordination.
Activity
fyZhang66 commentedon Oct 22, 2026
Thanks @ayush-that. Error bodies come only from our API and are small envelopes, so an oversized error response doesn't happen in practice; the cap is deliberately on success bodies. Closing as not planned.