Depends on: #631, #632, #633, #634, #635
Context
blog_content already supports posts, pages, gallery/media-style content blocks, SEO metadata, public /news routes, widgets, and ads. In full-online R2-only news portal mode, every news image reference must resolve to a verified Cloudflare R2 media object instead of a local path or arbitrary external URL.
Objective
Require R2 media object references for featured images, gallery blocks, SEO images, and related blog/news image surfaces when full-online R2-only mode is enabled.
Scope
Apply R2-only validation to:
featured_image
content_json gallery/image blocks
post/page SEO og:image and twitter:image
homepage section image references
widget image references when used by news portal
ads image references when used by news portal
Allowed reference shape:
{
"mediaObjectId": "uuid",
"alt": "Descriptive alt text",
"caption": "Optional caption"
}
Rejected shapes in full-online R2-only mode:
{ "url": "/uploads/news/image.jpg" }
{ "url": "https://external.example/image.jpg" }
Behavior requirements
- Referenced media object must exist.
- Referenced media object must belong to the same tenant.
- Referenced media object must have
storage_driver = 'cloudflare_r2'.
- Referenced media object must be
verified or otherwise safe according to the registry status model.
- Public renderer must generate image URLs from trusted R2 metadata only.
- Public renderer must not output local paths such as
/uploads, /public, /assets/user-content, or Docker volume paths.
- Public renderer must not output arbitrary external image URLs for news images.
Out of scope
- Creating upload sessions.
- Image transformation/CDN resizing.
- Importing legacy media.
- Offline/LAN behavior.
Acceptance criteria
Security notes
- Do not downgrade this to a warning in production.
- Never allow raw HTML image tags from content to bypass the R2 media object policy.
- Keep validation server-side; UI validation is supplementary only.
Depends on: #631, #632, #633, #634, #635
Context
blog_contentalready supports posts, pages, gallery/media-style content blocks, SEO metadata, public/newsroutes, widgets, and ads. In full-online R2-only news portal mode, every news image reference must resolve to a verified Cloudflare R2 media object instead of a local path or arbitrary external URL.Objective
Require R2 media object references for featured images, gallery blocks, SEO images, and related blog/news image surfaces when full-online R2-only mode is enabled.
Scope
Apply R2-only validation to:
Allowed reference shape:
{ "mediaObjectId": "uuid", "alt": "Descriptive alt text", "caption": "Optional caption" }Rejected shapes in full-online R2-only mode:
{ "url": "/uploads/news/image.jpg" }{ "url": "https://external.example/image.jpg" }Behavior requirements
storage_driver = 'cloudflare_r2'.verifiedor otherwise safe according to the registry status model./uploads,/public,/assets/user-content, or Docker volume paths.Out of scope
Acceptance criteria
mediaObjectIdin full-online R2-only mode./newsroutes never render local upload paths for news images.bun run api:spec:checkpasses.bun run testpasses.bun run buildpasses.bun run checkpasses.Security notes