feat: implement CSP and security headers for API responses
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 23s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 23s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Added Content-Security-Policy header to nginx configuration for SPA, enhancing security against XSS attacks. - Introduced middleware in FastAPI to set X-Content-Type-Options header, preventing MIME-sniffing vulnerabilities. - Updated production readiness audit and access layer endpoint audit to reflect security enhancements and ongoing governance practices. - Added tests to verify the presence of security headers in API responses, ensuring compliance with security standards.
This commit is contained in:
parent
9365125969
commit
161d520329
|
|
@ -20,12 +20,14 @@ Fortlaufend gemäß `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` Stufe A–C.
|
|||
| auth | `/api/auth/*` | nein | — | Login/Session | EXEMPT |
|
||||
| catalogs | Katalog-CRUD | nein (global) | `require_auth` | Admin/Trainer je Endpoint | EXEMPT; bei späterem `club_id` nachziehen |
|
||||
| skills | `/api/skills*` | nein (global) | `require_auth` | je Endpoint | EXEMPT |
|
||||
| maturity_models | Admin-Matrix | nein (global) | `require_auth` | Admin | EXEMPT |
|
||||
| maturity_models | Admin-Matrix | nein (global) | `require_auth` | Admin für Schreiben; `GET …/{id}` nur Portal-Admin | EXEMPT |
|
||||
| matrix_stack_bundle | Export/Import Bundles | Plattform/Test | `require_auth` | Admin | EXEMPT |
|
||||
| import_wiki / import_wiki_admin | Wiki-Import | Werkzeug | `require_auth`/Admin | Admin | EXEMPT |
|
||||
|
||||
**Legende:** Router auf der EXEMPT-Liste des Scripts sind globale oder Auth-only-Pfade; sobald ein Router Vereinsdaten oder Bibliotheks-Sichtbarkeit erhält, EXEMPT entfernen und `get_tenant_context` einführen.
|
||||
|
||||
**Pflege / Drift:** Änderungen an Mandanten, Governance (`visibility`/`club_id`) oder neuen inhaltsbezogenen Endpoints → eine Zeile in dieser Tabelle anpassen und `PRODUCTION_READINESS_AUDIT_2026-05.md` prüfen.
|
||||
|
||||
Letzte Änderung: 2026-05-06 — MULTI_TENANCY §3 Gap-Analyse aktualisiert; Audit um geschützte Profil-Endpunkte ergänzt.
|
||||
|
||||
---
|
||||
|
|
@ -33,7 +35,7 @@ Letzte Änderung: 2026-05-06 — MULTI_TENANCY §3 Gap-Analyse aktualisiert; Aud
|
|||
### Changelog (Fortführung)
|
||||
|
||||
- **2026-05-07:** Legacy `GET/PUT /api/profile` auf Session-Profil gehärtet; OpenAPI/Health-Ready Produktionsdefaults; Security-Release-Tests + CI-Schritt `security_release_checks.py` — siehe `PRODUCTION_READINESS_AUDIT_2026-05.md`.
|
||||
- **2026-05-07 (Phase 2):** Geschützte Übungs-Mediendatei-API; `/media`-Static optional; Mitgliederverzeichnis E-Mail eingeschränkt; GET maturity-by-id nur Admin; Postgres `127.0.0.1` im Prod-Compose.
|
||||
- **2026-05-07 (Phase 3):** CSP SPA (nginx); API `nosniff`-Middleware — siehe `PRODUCTION_READINESS_AUDIT_2026-05.md`.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Produktionsreife: Audit-Ergebnis & Umsetzungsplan (Stand 2026-05-07)
|
||||
# Produktionsreife: Audit-Ergebnis & Umsetzungsplan (Stand 2026-05-07, Phase-3-Update)
|
||||
|
||||
**Zweck:** Einheitliche Referenz gegen **Drift** zwischen Sicherheits-/Betriebsanforderungen und Code.
|
||||
**Bezug:** Zugriffsschicht `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`, Cursor-Regel `.cursor/rules/access-layer.mdc`.
|
||||
|
|
@ -30,8 +30,8 @@
|
|||
|
||||
| ID | Befund | Status |
|
||||
|----|--------|--------|
|
||||
| CON-03 | Sparten (`division`) vs. Zielbild ACCESS_LAYER | **Offen** (Roadmap Stufe D) |
|
||||
| OPS-02 | CSP / restliche Browser-Härtung | **Teilweise** (Basis-Header nginx; CSP offen) |
|
||||
| CON-03 | Sparten (`division`) vs. Zielbild ACCESS_LAYER | **Roadmap** — siehe Phase 3 unten (Stufe D); nicht in diesem Schritt implementiert |
|
||||
| OPS-02 | CSP / restliche Browser-Härtung | **Behoben (Basis):** CSP auf SPA-Dokumente (nginx `location /`); API `X-Content-Type-Options: nosniff` (FastAPI-Middleware) |
|
||||
| MISC-01 | `auth.py` Debug-`print` beim Import | **Behoben** (entfernt) |
|
||||
| MISC-02 | `delete_profile`: DELETE auf Mitai-Tabellen schlägt fehl, wenn Tabelle fehlt | **Behoben:** nur löschen, wenn Tabelle existiert |
|
||||
|
||||
|
|
@ -57,9 +57,13 @@
|
|||
|
||||
### Phase 3 — Mittelfristig
|
||||
|
||||
1. CSP und restliche Header fein abstimmen (PWA, eingebettete Medien).
|
||||
2. `ACCESS_LAYER_ENDPOINT_AUDIT.md` bei Änderungen an Tenant/Governance aktualisieren (Arbeitsdisziplin).
|
||||
3. Division-/Sparten-Durchsetzung laut ARCHITEKTUR-Roadmap.
|
||||
1. **CSP & Header:** Content-Security-Policy für die SPA (nginx, `location /`); `X-Content-Type-Options: nosniff` auf allen FastAPI-Responses — ✅ umgesetzt.
|
||||
2. **Audit-Pflege:** bei Tenant/Governance-Änderungen `ACCESS_LAYER_ENDPOINT_AUDIT.md` und dieses Dokument anpassen — fortlaufend (Hinweis im Endpoint-Audit).
|
||||
3. **Division / Sparten (CON-03, Stufe D):** noch **nicht** code-seitig durchgesetzt. Nächste technische Schritte (für eigenes Epic):
|
||||
- Ist-Zustand: `division_id` auf Objekten & `division_lead` in `can_plan_in_club` ohne strikte Objekt-Filter.
|
||||
- Ziel: Lesen/Schreiben nur mit passender Sparten-Mitgliedschaft bzw. Rolle; einheitliche Filter in Listen (`training_groups`, Übungen, Planung).
|
||||
- Tests: Zwei Nutzer, zwei Sparten, kein Cross-Lesen; Superadmin-Pfade getrennt.
|
||||
- Spec: `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` Stufe D.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -79,6 +83,7 @@
|
|||
| `PUBLIC_OPENAPI` | `1` / `true` → OpenAPI trotz Prod einschalten (nur Debugging) |
|
||||
| `HEALTH_READY_PUBLIC_DETAIL` | `1` / `true` → volle Ready-JSON inkl. Tabellenliste in Prod |
|
||||
| `ALLOW_PUBLIC_MEDIA_STATIC` | `1` / `true` → öffentliches Mount von `/media/` (Notfall/Legacy; Standard: aus) |
|
||||
| `CSP` (nginx) | Kein Env: Policy fest in `frontend/nginx.conf` (`location /`). API auf **anderer Host-URL** als SPA: `connect-src` in der Policy erweitern oder Build mit envsubst. |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -87,3 +92,4 @@
|
|||
### Changelog
|
||||
|
||||
- **2026-05-07:** Phase 2 Medien, Mitgliederverzeichnis E-Mail, maturity GET admin-only, Postgres localhost bind, Tests `test_exercise_media_download.py`.
|
||||
- **Phase 3:** CSP (nginx SPA), API `X-Content-Type-Options` (FastAPI), Test `test_api_attachments_x_content_type_options_nosniff`; Division-Durchsetzung nur als Roadmap (CON-03).
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Trainer- und Vereinsplattform für Kampfsport-Trainingsplanung
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
|
@ -85,7 +85,13 @@ app.add_middleware(
|
|||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# TODO: Initialize Database with migrations
|
||||
|
||||
@app.middleware("http")
|
||||
async def add_api_security_headers(request: Request, call_next):
|
||||
"""Konsistente Basis-Header auch für rein JSON-Responses (MIME-Sniffing)."""
|
||||
response = await call_next(request)
|
||||
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
||||
return response
|
||||
|
||||
# Version Endpoint (public, no auth)
|
||||
@app.get("/api/version")
|
||||
|
|
|
|||
|
|
@ -114,3 +114,13 @@ assert "schema_migrations_count" in j
|
|||
{"ENVIRONMENT": "production", "HEALTH_READY_PUBLIC_DETAIL": "1"},
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr + proc.stdout
|
||||
|
||||
|
||||
def test_api_attachments_x_content_type_options_nosniff(client: TestClient) -> None:
|
||||
"""Globales Middleware: keine MIME-Sniffing-Heuristik für API/Health."""
|
||||
r = client.get("/health")
|
||||
assert r.status_code == 200
|
||||
assert r.headers.get("x-content-type-options") == "nosniff"
|
||||
r2 = client.get("/api/version")
|
||||
assert r2.status_code == 200
|
||||
assert r2.headers.get("x-content-type-options") == "nosniff"
|
||||
|
|
|
|||
|
|
@ -49,8 +49,10 @@ server {
|
|||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# SPA routing - serve index.html for all routes
|
||||
location / {
|
||||
# Document-CSP für SPA/PWA — React nutzt häufig inline-styles; Mediendateien & API sind same-origin (Proxy).
|
||||
# Bei separater API-Origin: connect-src hier erweitern oder nginx-Envsubst nutzen.
|
||||
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; object-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self'; media-src 'self' blob: data:; worker-src 'self' blob:; manifest-src 'self';" always;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user