{"record":{"id":"0de975c4d8997729","repo":"sipeed/picoclaw","slug":"media-too-large","errorCode":null,"errorMessage":"media too large","messagePattern":"media too large","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/channels/wecom/media.go","lineNumber":302,"sourceCode":"\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, resourceURL, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create request: %w\", err)\n\t}\n\tresp, err := c.mediaClient.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"download media: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"download media returned HTTP %d\", resp.StatusCode)\n\t}\n\n\tdata, err := io.ReadAll(io.LimitReader(resp.Body, wecomOutboundMediaMaxBytes+1))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"read media: %w\", err)\n\t}\n\tif len(data) > wecomOutboundMediaMaxBytes {\n\t\treturn \"\", fmt.Errorf(\"media too large\")\n\t}\n\n\tif aesKey != \"\" {\n\t\tkey, keyErr := decodeMediaAESKey(aesKey)\n\t\tif keyErr != nil {\n\t\t\treturn \"\", keyErr\n\t\t}\n\t\tdata, err = decryptAESCBC(key, data)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"decrypt media: %w\", err)\n\t\t}\n\t}\n\n\tfilename, contentType := detectWeComMediaMetadata(\n\t\tdata,\n\t\tmsgID+fallbackExt,\n\t\tresp.Header.Get(\"Content-Type\"),\n\t\tresourceURL,","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/wecom/media.go#L284-L320","documentation":"Inbound media downloaded from the WeCom CDN exceeded wecomOutboundMediaMaxBytes = 20 MiB (media.go:28,301-302). The body is read through io.LimitReader(max+1); reading exactly max+1 bytes proves the object is over the cap and the download is aborted before decrypt/store. Note the per-type send limits are tighter (image/voice 2 MiB, video 10 MiB) but the inbound receive cap is a flat 20 MiB.","triggerScenarios":"A WeCom chat message carries a file or video whose CDN object is larger than 20 MiB (or whose encrypted ciphertext is still > 20 MiB before decryption); len(data) == 20 MiB+1 after the limited read.","commonSituations":"Users forwarding large videos, HD screen recordings, or log archives through WeCom; encrypted attachments whose ciphertext slightly exceeds the plaintext size; senders on unlimited plans unaware of the receiver cap.","solutions":["Tell the sender the attachment exceeds 20 MiB and ask for a compressed version or a link","Pre-check size before the full download: HEAD/Content-Length or an early-abort Range read, reject > 20 MiB without transferring","If you self-host and accept the upstream implications, fork and raise wecomOutboundMediaMaxBytes - but WeCom's own upload path tops out at 100 chunks x 512 KiB (~50 MiB)","Convert inbound oversized files into links at the source (upload to a file service, send the URL)"],"exampleFix":"// before: full download then size check\ndata, err := io.ReadAll(io.LimitReader(resp.Body, max+1))\nif len(data) > max { return \"\", fmt.Errorf(\"media too large\") }\n\n// after: reject early from Content-Length when present\nif n, err := strconv.ParseInt(resp.Header.Get(\"Content-Length\"), 10, 64); err == nil && n > int64(max) {\n    return \"\", fmt.Errorf(\"media too large\")\n}","handlingStrategy":"validation","validationCode":"// before triggering an inbound fetch, probe the CDN object size\nfunc sizeWithinLimit(ctx context.Context, rawurl string, max int64) bool {\n    req, err := http.NewRequestWithContext(ctx, http.MethodHead, rawurl, nil)\n    if err != nil {\n        return true // cannot pre-check; runtime check still applies\n    }\n    resp, err := http.DefaultClient.Do(req)\n    if err != nil {\n        return true\n    }\n    defer resp.Body.Close()\n    return resp.ContentLength < 0 || resp.ContentLength <= max\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["pre-check Content-Length before downloading attachments","educate senders: files > 20 MiB cannot transit this channel","if raising the cap in a fork, keep it under the upload ceiling (100 x 512 KiB) or sends will fail later"],"tags":["size-limit","media","wecom","validation"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}