CORS does not block hotlinking, it only filters cross-origin JS. A Worker with a Referer check for public content, presigned URLs for private content.
Images served from a bucket show up on third-party sites that eat your bandwidth. The reflex is to turn on CORS. That is the wrong tool for this problem.
CORS does not do what you think
CORS protects the user from cross-origin JavaScript that would read a response. It acts in the browser, after the fact, on script-driven requests. An img tag loading a URL is not subject to CORS at all.
Hotlinking is exactly that: an img src to your bucket from another domain. No CORS header stops it. The file is served, the bill goes up.
Permissive Referer for public, presigned for private
For public content, a Worker in front checks the Referer header. The rule stays permissive: allow your own domains and a missing Referer, which is common and legitimate. Block known third-party domains. It is a comfort filter, not a lock.
For private content, do not rely on the Referer, which is trivial to spoof. Issue presigned URLs: a signed link, short-lived, valid for one specific object. No signature, no access.
The principle: match the control to the content. Referer to discourage public hotlinking, signing to lock down the private. CORS plays no part in either case.