# Chat thumbnails bypass s3\_cdn\_url & use raw S3 bucket URLs

**URL:** https://meta.discourse.org/t/chat-thumbnails-bypass-s3-cdn-url-use-raw-s3-bucket-urls/404174
**Category:** Bug
**Tags:** cdn, s3, chat, fixed
**Created:** [May 31, 2026, 10:13pm UTC](https://meta.discourse.org/t/chat-thumbnails-bypass-s3-cdn-url-use-raw-s3-bucket-urls/404174 "2026-05-31T22:13:47Z")
**Posts on this page:** 1
**Showing post:** 1

<div class="post-metadata">

### Author: ![Lilly](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilly/32/575047_2.png) [@Lilly](https://meta.discourse.org/u/Lilly)
#### Post date: [May 31, 2026, 10:13pm UTC](https://meta.discourse.org/t/chat-thumbnails-bypass-s3-cdn-url-use-raw-s3-bucket-urls/404174/1 "2026-05-31T22:13:47Z")

</div>

i did a recent Cloudflare R2 uploads bucket configuration and my chat thumbnail images were borked. so i did some digging and made a quick fix for my configuration, then found this topic: [Cloudflare R2 Image URL Display Issue: Detailed Explanation and Fix](https://meta.discourse.org/t/cloudflare-r2-image-url-display-issue-detailed-explanation-and-fix/358204). anyways, so i looked at other S3 upload bucket configurations and noted the bug wasn’t really a Cloudflare-specific issue.

* * *

## Description

when an external S3 or compatible object storage is configured for uploads, chat thumbnail images bypass the CDN and are loaded directly from the bucket URL.

for secure external S3-compatible bucket such as Cloudflare R2, chat thumbnails are broken and do not display.

the underlying issue is that the chat serializer is failing to apply the `s3_cdn_url` setting to thumbnails. instead of routing the image through the configured CDN, it is leaking the raw internal S3 bucket URL directly to the browser payloads.

## Steps to reproduce

this is reproducible on Meta and other sites using S3 upload buckets:

1. post an image in chat or a channel
2. inspect the thumbnail image URL in the console
3. click the image to get the larger original, and inspect the url
4. compare it to the thumbnail

here is an example from a Meta chat

**Thumbnail URL:** from bucket

```plaintext
https://cdck-file-uploads-global.s3.dualstack.us-west-2.amazonaws.com/meta/optimized/4X/4/7/9/479815360e0e6e0cd9f4ba565891776e84aea532_2_375x500.jpeg

```

**Original URL:** via CDN

```plaintext
https://global.discourse-cdn.com/meta/original/4X/4/7/9/479815360e0e6e0cd9f4ba565891776e84aea532.jpeg

```

in the console, the html for the thumbnail `<img...` contains `data-large-src` = CloudFront CDN URL, and `src`= AWS bucket URL.

> **screenshot**
>
> ![image](https://global.discourse-cdn.com/meta/original/4X/8/2/7/8272d1de89d8351be54aceaf3255bdb21ad5f6f3.png)

## Impact:

- for S3-compatible storage like Cloudflare R2 that are secure-by-default and block unauthenticated access to raw bucket endpoints, chat thumbnail images (optimized) are broken.
- bandwidth leaks for AWS and other S3-compatible object storage buckets that allow access to raw bucket endpoints, since chat is bypassing the CDN entirely; this results in paying direct S3 egress fees for all chat thumbnail traffic.
- infrastructure leakage: the raw backend storage URLs (including internal bucket names and sometimes account IDs) are being exposed in the client JSON payloads.

### PR:

i have a PR to fix the issue here:

> <https://github.com/discourse/discourse/pull/40419>
>
> \### What does this PR do?
> This PR fixes a bug where Discourse Chat bypasses the… configured \`s3\_cdn\_url\` and exposes raw S3 bucket URLs in the chat stream. 
> 
> It addresses two separate issues preventing the CDN from applying properly:
> 1. \*\*Missing Wrappers in Chat:\*\* The \`chat-upload.gjs\` component was piping raw database URLs directly into the \`\<img src\>\`, \`\<video\>\`, and \`\<audio\>\` tags. This PR imports and applies the \`getURLWithCDN\` utility to all upload types in the chat stream.
> 2. \*\*Protocol Mismatches in \`getURLWithCDN\`:\*\* The JavaScript utility relied on a strict \`.startsWith()\` comparison between the \`url\` and \`S3BaseUrl\`. When the server configures \`S3BaseUrl\` with \`https://\` but sends the payload \`url\` as protocol-relative (\`//\`), the match fails and the CDN is ignored. This PR normalizes both strings by stripping the protocol before comparison, mirroring how Ruby handles domain matching on the backend.
> 
> \### Why is it needed?
> Bypassing the CDN for chat uploads causes two major issues:
> \* \*\*Broken UI for Secure Buckets:\*\* For providers like Cloudflare R2 that block direct, unauthenticated bucket access, bypassing the CDN results in broken images (\`403 Forbidden\`). 
> \* \*\*Bandwidth Leaks:\*\* For standard setups (AWS, DigitalOcean), bypassing the CDN means admins are paying raw S3 egress fees for chat thumbnail traffic instead of utilizing their configured CDN. \*(Note: This bug is currently visible on Meta, where chat thumbnails are serving directly from \`assets-meta-cdck-prod-meta.s3.dualstack.us-west-1.amazonaws.com\` instead of CloudFront).\*
> 
> \### How was this tested?
> \* Added unit tests to \`get-url-test.js\` to verify \`getURLWithCDN\` successfully applies the CDN even when protocol strings mismatch (e.g., \`https://\` vs \`//\`).
> \* Deployed to a cloud dev instance using Cloudflare R2. Verified that both original uploads and optimized thumbnails in the Chat stream now successfully route through the configured \`s3\_cdn\_url\` domain without breaking existing attachment functionality.
> 
> \*\*Files Touched:\*\*
> \* \`frontend/discourse/app/lib/get-url.js\`
> \* \`frontend/discourse/tests/unit/lib/get-url-test.js\`
> \* \`plugins/chat/assets/javascripts/discourse/components/chat-upload.gjs\`

looks like [Sam added](https://meta.discourse.org/t/upload-images-in-chat-cant-be-show-normally-when-use-s3-cdn/365540/15) `getURLWithCDN` to the chat composer preview - however, i don’t think it makes it to the chat stream?

> <https://github.com/discourse/discourse/pull/34507>

i wonder if the composer fix may have been failing for some S3 configurations as well because `getURLWithCDN` crashes on protocol mismatches (`//` vs `https://`)? anyway the above PR simply extends Sam’s work by adding the wrappers to the stream and making it protocol-agnostic.

### Temporary workaround:

before i realized this was more than a cloudflare issue, i made a lightweight theme component. it intercepts the raw S3 domains in the Chat DOM and swaps them out for the proper CDN domain before the browser attempts to download them. this routes the traffic correctly and plugs the bandwidth leak. i adapted it to work for any S3-compatible object storage. just two settings - `Raw S3 bucket URL` and `S3 CDN URL`.

> **[GitHub - Lillinator/chat-s3-thumbnails-fix: Fixes the chat thumbnail path bug for Cloudflare...](https://github.com/Lillinator/chat-s3-thumbnails-fix)**
>
> Fixes the chat thumbnail path bug for Cloudflare R2 object storage uploads

~~(no idea why github oneboxes are broken here)~~ fixed now

---

_[View the full topic](https://meta.discourse.org/t/chat-thumbnails-bypass-s3-cdn-url-use-raw-s3-bucket-urls/404174)._
