chenhg5/cc-connect · error

wecom-ws: empty ciphertext

Error message

wecom-ws: empty ciphertext

What it means

wecomDecryptFile rejects an empty ciphertext slice before doing any crypto work. WeCom websocket media downloads are AES-256-CBC encrypted with IV = first 16 key bytes; decrypting zero bytes is meaningless, so the function fails fast rather than returning empty output.

Source

Thrown at platform/wecom/websocket_media.go:272

}

func isHexString(s string) bool {
	for i := 0; i < len(s); i++ {
		c := s[i]
		switch {
		case c >= '0' && c <= '9', c >= 'a' && c <= 'f', c >= 'A' && c <= 'F':
		default:
			return false
		}
	}
	return true
}

// wecomDecryptFile decrypts payload from WeCom WS media URLs (AES-256-CBC, IV = first 16 key bytes).
// Same algorithm as @wecom/aibot-node-sdk decryptFile.
func wecomDecryptFile(ciphertext []byte, aesKeyB64 string) ([]byte, error) {
	if len(ciphertext) == 0 {
		return nil, fmt.Errorf("wecom-ws: empty ciphertext")
	}
	key, err := decodeWeComAESKey(aesKeyB64)
	if err != nil {
		return nil, err
	}
	key32 := key[:32]
	iv := key32[:16]

	block, err := aes.NewCipher(key32)
	if err != nil {
		return nil, err
	}
	if len(ciphertext)%aes.BlockSize != 0 {
		return nil, fmt.Errorf("wecom-ws: ciphertext not multiple of block size")
	}
	plain := make([]byte, len(ciphertext))
	cipher.NewCBCDecrypter(block, iv).CryptBlocks(plain, ciphertext)
	return pkcs7UnpadWeCom(plain)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check len(raw) > 0 after the download and before decryption; log the HTTP status and headers.
  2. Re-request the media URL — WeCom media URLs are short-lived; fetch a fresh one via the API.
  3. If aesKey is set but server now returns plaintext, verify whether the URL is actually an encrypted WS media URL.

Example fix

// before
raw, _ := io.ReadAll(resp.Body)
return wecomDecryptFile(raw, aesKey)
// after
raw, _ := io.ReadAll(resp.Body)
if len(raw) == 0 {
    return nil, fmt.Errorf("wecom media download returned empty body (status %s)", resp.Status)
}
return wecomDecryptFile(raw, aesKey)
Defensive patterns

Strategy: try-catch

Validate before calling

if len(body) == 0 {
    return fmt.Errorf("media download returned empty body")
}

Try / catch

raw, err := downloadWeComWSMedia(url, key)
if err != nil {
    if strings.Contains(err.Error(), "empty ciphertext") {
        // fetch a fresh media URL and retry once
    }
}

Prevention

When it happens

Trigger: downloadWeComWSMedia passes the downloaded body to wecomDecryptFile when aesKey != "", but the HTTP response body was empty (0 bytes) — e.g. server returned 200 with no content, or a LimitReader consumed nothing.

Common situations: Expired or already-downloaded WeCom media URL returning an empty 200; a proxy stripping the body; fetch succeeded but the media was deleted server-side.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/f0e4eb5b86a069bb. Report an issue: GitHub.