فهرست منبع

fix: render Swagger UI at /docs with a docs-scoped CSP

  The global CSP set script-src 'self', so FastAPI's /docs page rendered
  blank: the inline boot <script> and the cdn.jsdelivr.net swagger-ui
  bundle/CSS were both blocked. /redoc and /docs/oauth2-redirect had the
  same problem.

  Branch the security_headers_middleware to emit a docs-scoped CSP for
  those three paths that allows cdn.jsdelivr.net (scripts + styles), the
  FastAPI/Redoc favicon hosts (images), and 'unsafe-inline' for the
  inline boot script. Every other route keeps the stricter SPA policy
  unchanged.
maziggy 4 ماه پیش
والد
کامیت
30cf384b5a
3فایلهای تغییر یافته به همراه18 افزوده شده و 1 حذف شده
  1. 1 0
      CHANGELOG.md
  2. 16 0
      backend/app/main.py
  3. 1 1
      frontend/src/__tests__/pages/CameraTokensPage.test.tsx

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
CHANGELOG.md


+ 16 - 0
backend/app/main.py

@@ -4417,6 +4417,22 @@ async def security_headers_middleware(request, call_next):
             "frame-src 'self' http: https:; "
             "frame-src 'self' http: https:; "
             "frame-ancestors 'self';"
             "frame-ancestors 'self';"
         )
         )
+    elif request.url.path in ("/docs", "/redoc", "/docs/oauth2-redirect"):
+        # FastAPI's built-in Swagger UI / ReDoc pages load assets from
+        # cdn.jsdelivr.net and bootstrap with an inline <script>, so the
+        # default CSP would render a blank page.
+        response.headers["Content-Security-Policy"] = (
+            "default-src 'self'; "
+            "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; "
+            "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; "
+            "img-src 'self' data: blob: https://fastapi.tiangolo.com https://cdn.redoc.ly; "
+            "connect-src 'self'; "
+            "font-src 'self' data: https://fonts.gstatic.com; "
+            "worker-src 'self' blob:; "
+            "object-src 'none'; "
+            "base-uri 'self'; "
+            "frame-ancestors 'none';"
+        )
     else:
     else:
         response.headers["Content-Security-Policy"] = (
         response.headers["Content-Security-Policy"] = (
             "default-src 'self'; "
             "default-src 'self'; "

+ 1 - 1
frontend/src/__tests__/pages/CameraTokensPage.test.tsx

@@ -10,7 +10,7 @@
  *   refreshed view; covered indirectly via the create-then-refresh flow).
  *   refreshed view; covered indirectly via the create-then-refresh flow).
  */
  */
 
 
-import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
+import { describe, it, expect, afterEach, vi } from 'vitest';
 import { screen, waitFor, within } from '@testing-library/react';
 import { screen, waitFor, within } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import userEvent from '@testing-library/user-event';
 import { http, HttpResponse } from 'msw';
 import { http, HttpResponse } from 'msw';

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است