T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/doc2markdown.js:148
- Finding
- Unbounded Response Buffering and ZIP Extraction Enable Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/doc2markdown.js:148-169`, `scripts/doc2markdown.js:541-583` **Vulnerability Type**: Uncontrolled resource consumption through unbounded network buffering and archive extraction **Risk Level**: Medium ### Vulnerable Code ```js const req = httpModule.request(reqOptions, (res) => { const chunks = []; res.on('data', (chunk) => { chunks.push(chunk); }); res.on('end', () => { const buffer = Buffer.concat(chunks); let data; if (options.responseType === 'arraybuffer') { data = buffer; } else { const text = buffer.toString('utf8'); try { data = JSON.parse(text); } catch (e) { data = text; } } resolve({ status: res.statusCode, data: data }); }); }); ``` ```js const entries = []; const diskEntries = buffer.readUInt16LE(pos + 8); const dirStart = buffer.readUInt32LE(pos + 16); pos = dirStart; for (let i = 0; i < diskEntries; i++) { if (buffer.readUInt32LE(pos) !== 0x02014b50) break; const flags = buffer.readUInt16LE(pos + 8); const method = buffer.readUInt16LE(pos + 10); const nameLen = buffer.readUInt16LE(pos + 28); const extraLen = buffer.readUInt16LE(pos + 30); const commentLen = buffer.readUInt16LE(pos + 32); const offset = buffer.readUInt32LE(pos + 42); const name = buffer.toString('utf8', pos + 46, pos + 46 + nameLen); entries.push({ offset, method, name, encrypted: !!(flags & 1) }); pos += 46 + nameLen + extraLen + commentLen; } // Extract each file for (const ent of entries) { if (ent.encrypted || ent.name.endsWith('/')) continue; const o = ent.offset; const sig = buffer.readUInt32LE(o); if (sig !== 0x04034b50) continue; const nameLen = buffer.readUInt16LE(o + 26); const extraLen = buffer.readUInt16LE(o + 28); const csize = buffer ...[truncated 3475 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Enforce a maximum download size** - Validate `Content-Length` when present and reject responses above a configured threshold. - Track the cumulative number of bytes received and immediately destroy the request when the limit is exceeded. - Do not rely solely on `Content-Length`, because responses may omit or falsify it. 2. **Stream downloads instead of buffering entire responses** - Stream archive data to a securely created temporary file. - Use exclusive file creation and restrictive permissions. - Remove temporary and partially extracted files in a `finally` block. 3. **Apply strict archive limits before and during extraction** - Limit the total number of ZIP entries. - Limit compressed and uncompressed size per entry. - Limit total uncompressed bytes across the archive. - Reject entries with an excessive compression ratio. - Verify that offsets, lengths, and metadata remain within the archive buffer or file boundaries. 4. **Use bounded asynchronous decompression** - Replace `zlib.inflateSync` with streaming asynchronous decompression. - Count decompressed bytes while streaming and abort as soon as an entry or aggregate limit is exceeded. - Apply an extraction timeout or cancellation mechanism to constrain CPU use. 5. **Protect filesystem capacity** - Check available storage where supported. - Extract into a staging directory and only move completed output into place after successful validation. - Apply deployment-level filesystem quotas and process memory limits as defense in depth. 6. **Validate the downloaded format** - Verify the expected content type and ZIP signatures before extraction. - Reject malformed central-directory records and unsupported compression methods. - Treat all conversion-service output as untrusted, even though it is delivered over HTTPS. ]]>
