T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/get-daily.js:29
- Finding
- Unbounded Buffering of Remote API Responses<![CDATA[ ## Vulnerability Details **File Location**: `scripts/get-daily.js:29-35`; `scripts/get-article.js:35-41` **Vulnerability Type**: Unbounded response buffering and missing network resource limits **Risk Level**: Medium ### Vulnerable Code `scripts/get-daily.js:29-35`: ```js https.get(url, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { ``` `scripts/get-article.js:35-41`: ```js https.get(url, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { ``` ### Technical Analysis Both scripts accumulate the complete response from `api.cjiot.cc` in a JavaScript string before parsing it. They do not impose a maximum response size, configure a request timeout, inspect `Content-Length`, or abort slow and oversized responses. The use of HTTPS protects the connection against ordinary network modification when certificate validation succeeds, but it does not protect the client from a compromised, malicious, or malfunctioning API server. A server capable of returning an arbitrarily large response can cause the Node.js process to continue allocating memory until the response ends or the process reaches its memory limit. The scripts also do not reject unexpected HTTP status codes or content types before buffering the body. Consequently, large error pages and other non-JSON responses are subject to the same unbounded buffering behavior. ### Attack Path 1. An attacker compromises the configured API service, its hosting environment, or another trusted component capable of controlling its HTTPS responses. 2. A user invokes `get-daily.js` or `get-article.js`. 3. The controlled endpoint returns a very large response, or sends data continuously without completing the response. 4. Each incoming chunk is appended to the `data` string. 5. Memory and connection resources continue to be consumed until the process is terminated, the host ...[truncated 598 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Enforce a strict maximum response size while processing chunks. Track received bytes and call `request.destroy()` or `res.destroy()` immediately when the limit is exceeded. 2. Configure a short connection and response timeout with `request.setTimeout()`. 3. Validate `res.statusCode` before reading the complete response and reject unexpected redirects or error responses. 4. Validate that the response `Content-Type` is an expected JSON media type. 5. Inspect `Content-Length` when present and reject responses that exceed the configured limit. Continue enforcing the streaming byte limit because this header can be absent or inaccurate. 6. Handle aborted responses and stream errors explicitly. 7. Consider parsing through a bounded stream if responses can legitimately become large. Example hardening pattern: ```js const MAX_RESPONSE_BYTES = 2 * 1024 * 1024; const request = https.get(url, (res) => { if (res.statusCode !== 200) { res.resume(); console.error(`Unexpected HTTP status: ${res.statusCode}`); process.exitCode = 1; return; } const contentType = res.headers['content-type'] || ''; if (!contentType.includes('application/json')) { res.resume(); console.error('Unexpected response content type'); process.exitCode = 1; return; } let received = 0; let data = ''; res.on('data', (chunk) => { received += chunk.length; if (received > MAX_RESPONSE_BYTES) { res.destroy(new Error('API response exceeds the size limit')); return; } data += chunk; }); res.on('error', (error) => { console.error(`Response failed: ${error.message}`); }); res.on('end', () => { // Parse the bounded response. }); }); request.setTimeout(10_000, () => { request.destroy(new Error('API request timed out')); }); ``` ]]>
