Fix remaining CORB warnings on cross-origin player photo loads #210

Open
opened 2026-03-28 17:31:16 +00:00 by forgejo_admin · 0 comments

Type

Feature

Lineage

Follows from #207 / PR #208 which fixed the primary CORS issue (StaticFiles mount moved so CORSMiddleware applies). 5 CORB warnings remain in the browser console. The page works but some photos may silently fail to load.

Repo

forgejo_admin/basketball-api

User Story

WS-S6: As a coach or admin viewing the draft board, I expect all player photos to load without browser console warnings, so the roster view is reliable and complete.

Context

Investigation findings from code audit (2026-03-28):

PR #208 fixed the main CORS issue by moving StaticFiles("/uploads/photos", ...) to module level so CORSMiddleware wraps it with Access-Control-Allow-Origin headers. However, 5 CORB (Cross-Origin Read Blocking) warnings persist in the console.

CORB is a browser-side protection (distinct from CORS). It blocks cross-origin responses where the Content-Type header doesn't match what the browser expects for the resource type.

Root cause analysis — three contributing factors:

  1. Content-Type may be wrong for some files. Starlette's StaticFiles uses Python's mimetypes module to guess Content-Type from file extensions. On minimal container images, the mimetypes DB may be incomplete, causing some .jpg/.jpeg files to be served as application/octet-stream. CORB blocks application/octet-stream for <img> requests.

  2. No explicit MIME type validation on the serving side. The upload route (upload.py line 42) validates file.content_type starts with image/ at upload time, but the static file server relies entirely on extension-based guessing at serve time. There's no fallback or override.

  3. Frontend <img> tags lack crossorigin attribute. The admin teams page (admin/teams/+page.svelte line 196) and player profile page (players/[id]/+page.svelte lines 265, 277) load photos cross-origin without the crossorigin attribute. When CORS headers are present but the fetch mode is "no-cors" (browser default for <img>), some browsers emit CORB warnings.

Current architecture:

  • Photos uploaded via POST /upload/photo → saved to /data/uploads/photos/{uuid}.{ext}
  • Served via StaticFiles("/uploads/photos", directory=settings.upload_dir)
  • Loaded cross-origin by westsidekingsandqueens.tail5b443a.ts.net from basketball-api.tail5b443a.ts.net/uploads/photos/...
  • CORS allow_origins list is complete (covers prod, dev, capacitor, localhost)

File Targets

basketball-api (backend):

  • src/basketball_api/main.py — Add explicit MIME type mapping or a custom middleware that sets correct Content-Type for image responses from /uploads/photos/
  • src/basketball_api/routes/upload.py — Consider storing the original Content-Type alongside the file, or validate extension-to-MIME mapping at upload time

westside-app (frontend — separate repo):

  • src/routes/(app)/admin/teams/+page.svelte (line 196) — Add crossorigin="anonymous" to player photo <img> tags
  • src/routes/(app)/players/[id]/+page.svelte (lines 265, 277) — Add crossorigin="anonymous" to player photo <img> tags

Acceptance Criteria

  • Zero CORB warnings in browser console when loading the admin teams draft board with player photos
  • Zero CORB warnings on player profile pages with photos
  • All player photos served from /uploads/photos/ return correct Content-Type headers (image/jpeg, image/png, or image/webp — never application/octet-stream)
  • Existing uploaded photos continue to load correctly (no regression)
  • CORS headers still present on static file responses (no regression from PR #208)

Test Expectations

  • Unit test: verify /uploads/photos/{file}.jpg returns Content-Type: image/jpeg
  • Unit test: verify /uploads/photos/{file}.png returns Content-Type: image/png
  • Integration: load admin teams page cross-origin, confirm zero CORB warnings in console
  • Regression: existing photo upload + display flow still works end-to-end

Constraints

  • Backend fix ships in basketball-api. Frontend fix ships in westside-app (separate PR).
  • Do not change the upload directory structure or URL scheme.
  • Container base image may have minimal mimetypes DB — solution must not depend on system mime.types file.

Checklist

  • Backend: ensure correct Content-Type on all static photo responses
  • Frontend: add crossorigin="anonymous" to cross-origin <img> tags (westside-app PR)
  • Verify fix in browser DevTools Network tab (Content-Type) and Console (no CORB warnings)
  • Run existing test suite — no regressions
  • Forgejo issue: #207 (original CORS fix scope)
  • PR: #208 (StaticFiles mount fix — merged)
  • Board: board-westside-basketball
  • Story: WS-S6
### Type Feature ### Lineage Follows from #207 / PR #208 which fixed the primary CORS issue (StaticFiles mount moved so CORSMiddleware applies). 5 CORB warnings remain in the browser console. The page works but some photos may silently fail to load. ### Repo `forgejo_admin/basketball-api` ### User Story **WS-S6**: As a coach or admin viewing the draft board, I expect all player photos to load without browser console warnings, so the roster view is reliable and complete. ### Context **Investigation findings from code audit (2026-03-28):** PR #208 fixed the main CORS issue by moving `StaticFiles("/uploads/photos", ...)` to module level so `CORSMiddleware` wraps it with `Access-Control-Allow-Origin` headers. However, 5 CORB (Cross-Origin Read Blocking) warnings persist in the console. CORB is a **browser-side** protection (distinct from CORS). It blocks cross-origin responses where the `Content-Type` header doesn't match what the browser expects for the resource type. **Root cause analysis — three contributing factors:** 1. **Content-Type may be wrong for some files.** Starlette's `StaticFiles` uses Python's `mimetypes` module to guess Content-Type from file extensions. On minimal container images, the `mimetypes` DB may be incomplete, causing some `.jpg`/`.jpeg` files to be served as `application/octet-stream`. CORB blocks `application/octet-stream` for `<img>` requests. 2. **No explicit MIME type validation on the serving side.** The upload route (`upload.py` line 42) validates `file.content_type` starts with `image/` at upload time, but the static file server relies entirely on extension-based guessing at serve time. There's no fallback or override. 3. **Frontend `<img>` tags lack `crossorigin` attribute.** The admin teams page (`admin/teams/+page.svelte` line 196) and player profile page (`players/[id]/+page.svelte` lines 265, 277) load photos cross-origin without the `crossorigin` attribute. When CORS headers are present but the fetch mode is "no-cors" (browser default for `<img>`), some browsers emit CORB warnings. **Current architecture:** - Photos uploaded via `POST /upload/photo` → saved to `/data/uploads/photos/{uuid}.{ext}` - Served via `StaticFiles("/uploads/photos", directory=settings.upload_dir)` - Loaded cross-origin by `westsidekingsandqueens.tail5b443a.ts.net` from `basketball-api.tail5b443a.ts.net/uploads/photos/...` - CORS allow_origins list is complete (covers prod, dev, capacitor, localhost) ### File Targets **basketball-api (backend):** - `src/basketball_api/main.py` — Add explicit MIME type mapping or a custom middleware that sets correct `Content-Type` for image responses from `/uploads/photos/` - `src/basketball_api/routes/upload.py` — Consider storing the original Content-Type alongside the file, or validate extension-to-MIME mapping at upload time **westside-app (frontend — separate repo):** - `src/routes/(app)/admin/teams/+page.svelte` (line 196) — Add `crossorigin="anonymous"` to player photo `<img>` tags - `src/routes/(app)/players/[id]/+page.svelte` (lines 265, 277) — Add `crossorigin="anonymous"` to player photo `<img>` tags ### Acceptance Criteria - [ ] Zero CORB warnings in browser console when loading the admin teams draft board with player photos - [ ] Zero CORB warnings on player profile pages with photos - [ ] All player photos served from `/uploads/photos/` return correct `Content-Type` headers (`image/jpeg`, `image/png`, or `image/webp` — never `application/octet-stream`) - [ ] Existing uploaded photos continue to load correctly (no regression) - [ ] CORS headers still present on static file responses (no regression from PR #208) ### Test Expectations - Unit test: verify `/uploads/photos/{file}.jpg` returns `Content-Type: image/jpeg` - Unit test: verify `/uploads/photos/{file}.png` returns `Content-Type: image/png` - Integration: load admin teams page cross-origin, confirm zero CORB warnings in console - Regression: existing photo upload + display flow still works end-to-end ### Constraints - Backend fix ships in basketball-api. Frontend fix ships in westside-app (separate PR). - Do not change the upload directory structure or URL scheme. - Container base image may have minimal mimetypes DB — solution must not depend on system mime.types file. ### Checklist - [ ] Backend: ensure correct Content-Type on all static photo responses - [ ] Frontend: add `crossorigin="anonymous"` to cross-origin `<img>` tags (westside-app PR) - [ ] Verify fix in browser DevTools Network tab (Content-Type) and Console (no CORB warnings) - [ ] Run existing test suite — no regressions ### Related - Forgejo issue: #207 (original CORS fix scope) - PR: #208 (StaticFiles mount fix — merged) - Board: board-westside-basketball - Story: WS-S6
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/basketball-api#210
No description provided.