T09 · Insecure Skill Coding Practices
- Location
scripts/public-upload.mjs:50- Finding
Unenforced Attachment Size Limits Permit Resource Exhaustion
- Content
View full analysis
Vulnerability Details
File Location:
scripts/public-upload.mjs, lines 50-55 and 69-117
Vulnerability Type: Unbounded attachment decoding and upload
Risk Level: MediumVulnerable Code
js const MAX_UPLOAD_BYTES_BY_KIND = { image: 25 * 1024 * 1024, audio: 25 * 1024 * 1024, video: 100 * 1024 * 1024, file: 25 * 1024 * 1024 };js const rawBytesSource = readAttachmentBytesSource(attachment) ?? readAttachmentBytesSource(input.attachment); if (rawBytesSource === null) { return null; } const bytes = decodeBytes(rawBytesSource); if (bytes.length === 0) { throw createUploadError(400, 'VALIDATION_BAD_REQUEST', 'attachment bytes are empty', { bridge_step: 'prepare_upload' }); } const fileName = resolveFileName(attachment, null); const contentType = resolveContentType(attachment, null, fileName, capability); const targetField = inferTargetField(capability, contentType, fileName); return { sourceKind: 'attachment_bytes', bytes, fileName, contentType, targetField };js const fileBuffer = candidate.bytes; const contentType = candidate.contentType ?? resolveFallbackContentType(candidate.targetField); if (!Buffer.isBuffer(fileBuffer) || fileBuffer.length === 0) { throw createUploadError(400, 'VALIDATION_BAD_REQUEST', 'file body is required', { bridge_step: 'prepare_upload' }); } const form = new FormData(); form.set('entry_host', auth.entryHost); form.set('agent_uid', auth.agentUid); form.set('conversation_id', auth.conversationId); if (readText(auth.entryUserKey)) { form.set('entry_user_key', auth.entryUserKey); } form.set('file_name', candidate.fileName); form.set('content_type', contentType); form.set('file', new Blob([fileBuffer], { type: contentType }), candidate.fileName); const response = await fetchImpl(`${auth.baseUrl}/agent/public-bridge/upload-file`, { method: 'POST', body: form });Technical Analysis
The module declares per-media upload limits in `MAX_UPLOAD_BYTES_BY_KIN ...[truncated 2173 chars]
- Remediation
View remediation
Remediation Suggestions
- Enforce
MAX_UPLOAD_BYTES_BY_KINDbefore constructingBloborFormData. - Determine the media kind from the normalized MIME type and target field, then reject buffers exceeding the corresponding limit.
- For base64 strings, estimate decoded size before decoding:
- Remove an allowed data-URL prefix.
- Validate base64 syntax strictly.
- Calculate the expected decoded length from the encoded length and padding.
- Reject oversized input before calling
Buffer.from().
- Repeat the size check after decoding to prevent bypasses caused by malformed metadata or inaccurate estimates.
- Apply a conservative absolute limit when the media kind or MIME type cannot be determined.
- Validate declared MIME types against an explicit allowlist and avoid relying solely on caller-controlled file names or content-type fields.
- Configure request-body limits, concurrency controls, timeouts, and rate limits in the host runtime and gateway.
- Where practical, use bounded streaming rather than retaining the encoded input, decoded buffer, blob, and multipart body in memory simultaneously.
- Add automated tests covering values immediately below, equal to, and above each configured limit, including base64 and data-URL inputs.
- Enforce
