Jul 14 - Using CDN - Fix web CSP
This commit is contained in:
@@ -1396,6 +1396,7 @@ timeout = 30
|
||||
- Backward-compatible: empty URL arrays (older server) → resolver builds the `/static/` URL as before.
|
||||
|
||||
**Operator cutover runbook (after 4b ships + Phase 3 sync exits clean):**
|
||||
- [ ] **CSP:** the web `img-src` must allow the R2 host or the browser blocks presigned image loads. Handled in `app/__init__.py` `set_security_headers` — the R2 endpoint host is derived from `R2_ENDPOINT_URL` and appended to `img-src` automatically when configured (local backend unaffected). If you use a custom R2 domain for presigned URLs, add that host too.
|
||||
- [ ] Confirm `python scripts/migrate_photos_to_r2.py` prints "✅ SAFE" (0 mismatches) and verified count ≥ Phase 0 baseline present-count.
|
||||
- [ ] Maintenance window: run the sync once more (delta) → set `STORAGE_BACKEND=s3` in `.env` → `systemctl restart janitorial-qc`.
|
||||
- [ ] Smoke test: existing web issue/inspection photos load; existing iPad issue photos load; a **new** upload from web and from iPad lands in R2 and renders; generate an inspection PDF + issue PDF with photos.
|
||||
|
||||
+13
-1
@@ -234,6 +234,18 @@ def create_app(config_name='default'):
|
||||
# ── Security response headers ─────────────────────────────────────────
|
||||
# Applied to every response. Blocks clickjacking, MIME sniffing, and
|
||||
# obvious XSS vectors without breaking Bootstrap CDN / Google Fonts.
|
||||
# Allow R2 presigned photo URLs in the CSP img-src when the s3 storage
|
||||
# backend is configured. Derived from R2_ENDPOINT_URL (the presigned URL
|
||||
# host is the same R2 account endpoint), so nothing is hardcoded and the
|
||||
# local backend is unaffected.
|
||||
_r2_img_src = ''
|
||||
_r2_endpoint = app.config.get('R2_ENDPOINT_URL')
|
||||
if _r2_endpoint:
|
||||
from urllib.parse import urlparse
|
||||
_r2_host = urlparse(_r2_endpoint).netloc
|
||||
if _r2_host:
|
||||
_r2_img_src = f' https://{_r2_host}'
|
||||
|
||||
@app.after_request
|
||||
def set_security_headers(response):
|
||||
response.headers.setdefault('X-Content-Type-Options', 'nosniff')
|
||||
@@ -245,7 +257,7 @@ def create_app(config_name='default'):
|
||||
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; "
|
||||
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; "
|
||||
"font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net; "
|
||||
"img-src 'self' data: blob: https://maps.gstatic.com https://maps.googleapis.com; "
|
||||
f"img-src 'self' data: blob: https://maps.gstatic.com https://maps.googleapis.com{_r2_img_src}; "
|
||||
"connect-src 'self' https://cdn.jsdelivr.net; "
|
||||
"frame-src https://maps.google.com https://www.google.com; "
|
||||
"frame-ancestors 'none';"
|
||||
|
||||
Reference in New Issue
Block a user