mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Ported onto the split provider layout: the route now lives in server/providers/openai/debug-log.js and its state belongs to the handler the factory returns rather than to module scope. /api/realtime/debug-log is the one route in its family left unhardened. Both siblings enforce a limiter through openai/rate-limit.js, and this was the only handler still writing with fs.appendFileSync on the request path. - Always-on per-IP limiter (120/min, 400 global). Deliberately not the opt-in openAiRateLimiter(): that one defaults to unlimited so throttling cannot silently change what a user spends, and this sink spends disk and event-loop time rather than provider quota. - Serialized async appends with a 32 MB rotation ceiling and one retained generation. REALTIME_DEBUG_LOG_MAX_BYTES caps a single request body; it never bounded the file those bodies accumulate into, so any local page could grow .gev-logs/realtime-conversations.jsonl for as long as the dev server ran. Serializing also closes a read-then-write race on the size check, where concurrent callers all stat under the ceiling and none rotates. - Generic 400/500 answers separating a malformed record or oversized body from a failed write, instead of echoing error.message — an errno and an absolute path for an fs failure. - readRequestBody marks an oversized body with code 'BODY_TOO_LARGE', the marker its sibling readRequestBodyCapped already sets, so the two cases are distinguishable without matching on text. Coverage extends src/tooling/localServices.test.mjs, which already drives the real handler over a temporary sourceRoot. Both new tests fail on the previous implementation and pass with this one. Refs #23. Does not add authentication: these proxies have no auth mechanism today, so introducing one is a maintainer design decision.
40 lines
1.2 KiB
JavaScript
40 lines
1.2 KiB
JavaScript
/** Read a request body with a hard byte cap; throws { code:'BODY_TOO_LARGE' } past the cap. */
|
|
async function readRequestBodyCapped(req, maxBytes) {
|
|
const chunks = [];
|
|
let total = 0;
|
|
for await (const chunk of req) {
|
|
total += chunk.length;
|
|
if (total > maxBytes) {
|
|
const err = new Error('Request body too large');
|
|
err.code = 'BODY_TOO_LARGE';
|
|
throw err;
|
|
}
|
|
chunks.push(chunk);
|
|
}
|
|
return Buffer.concat(chunks);
|
|
}
|
|
|
|
function readRequestBody(req, maxBytes = 1024 * 1024) {
|
|
return new Promise((resolve, reject) => {
|
|
let total = 0;
|
|
const chunks = [];
|
|
req.on('data', (chunk) => {
|
|
total += chunk.length;
|
|
if (total > maxBytes) {
|
|
// Same marker readRequestBodyCapped sets, so a caller can tell an
|
|
// oversized body from a genuine failure without matching on text.
|
|
const error = new Error(`Request body exceeds ${maxBytes} bytes`);
|
|
error.code = 'BODY_TOO_LARGE';
|
|
reject(error);
|
|
req.destroy();
|
|
return;
|
|
}
|
|
chunks.push(chunk);
|
|
});
|
|
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
req.on('error', reject);
|
|
});
|
|
}
|
|
|
|
export { readRequestBodyCapped, readRequestBody };
|