fix(backend): ajoute l'en-tete Cross-Origin-Resource-Policy sur toutes les reponses

This commit is contained in:
Dorian
2026-09-23 09:36:10 +02:00
parent 59f050ec5e
commit cfc194a3fb
4 changed files with 11 additions and 4 deletions
+3
View File
@@ -16,6 +16,9 @@ EN_TETES: Final[dict[str, str]] = {
"X-Content-Type-Options": "nosniff", "X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY", "X-Frame-Options": "DENY",
"Referrer-Policy": "no-referrer", "Referrer-Policy": "no-referrer",
# same-origin : le frontend ne consomme l'API qu'au travers d'un proxy same-origin
# (proxy.conf.json en dev, ingress /api en prod, cf. docs/architecture/20-backend.md).
"Cross-Origin-Resource-Policy": "same-origin",
} }
PREFIXE_AUTHENTIFICATION: Final = "/auth" PREFIXE_AUTHENTIFICATION: Final = "/auth"
+2 -1
View File
@@ -23,8 +23,9 @@ async def interroge(
("x-content-type-options", "nosniff"), ("x-content-type-options", "nosniff"),
("x-frame-options", "DENY"), ("x-frame-options", "DENY"),
("referrer-policy", "no-referrer"), ("referrer-policy", "no-referrer"),
("cross-origin-resource-policy", "same-origin"),
], ],
ids=["nosniff", "anti_iframe", "referrer"], ids=["nosniff", "anti_iframe", "referrer", "corp"],
) )
async def test_every_response_carries_the_security_headers( async def test_every_response_carries_the_security_headers(
client: AsyncClient, entete: str, valeur: str client: AsyncClient, entete: str, valeur: str
+5 -2
View File
@@ -423,8 +423,11 @@ Le reste, par ordre de surface :
écriture des journaux. C'est la troisième ligne de défense : la première est de ne rien passer écriture des journaux. C'est la troisième ligne de défense : la première est de ne rien passer
de secret au logger, la deuxième de ne jamais mettre un jeton dans une URL. de secret au logger, la deuxième de ne jamais mettre un jeton dans une URL.
- En-têtes posés par l'application : `X-Content-Type-Options`, `X-Frame-Options`, - En-têtes posés par l'application : `X-Content-Type-Options`, `X-Frame-Options`,
`Referrer-Policy`, plus `Cache-Control: no-store` sur `/auth/*`. HSTS et CSP appartiennent au `Referrer-Policy`, `Cross-Origin-Resource-Policy: same-origin`, plus `Cache-Control: no-store`
terminateur TLS, que l'application ne connaît pas : le reverse proxy les pose sur `/auth/*`. Le CORP est fixé à `same-origin` parce que le frontend ne consomme l'API qu'au
travers d'un proxy same-origin (`proxy.conf.json` en dev, ingress `/api` en prod), jamais
directement cross-origin depuis le navigateur. HSTS et CSP appartiennent au terminateur TLS, que
l'application ne connaît pas : le reverse proxy les pose
([ADR 0007](../adr/0007-terminaison-tls-et-reverse-proxy-nginx.md)). ([ADR 0007](../adr/0007-terminaison-tls-et-reverse-proxy-nginx.md)).
- Le conteneur tourne en utilisateur non-root, avec un `HEALTHCHECK` sur `/api/v1/health/live`. - Le conteneur tourne en utilisateur non-root, avec un `HEALTHCHECK` sur `/api/v1/health/live`.
- TLS, limitation de débit au frontal et journal d'accès sont portés par le reverse proxy. - TLS, limitation de débit au frontal et journal d'accès sont portés par le reverse proxy.
+1 -1
View File
@@ -39,7 +39,7 @@ lecture seule ; plusieurs lignes resteront à compléter une fois les endpoints
| Cinq gardes de configuration qui refusent le démarrage plutôt que de dégrader silencieusement | `app/core/config.py` | A05 | | Cinq gardes de configuration qui refusent le démarrage plutôt que de dégrader silencieusement | `app/core/config.py` | A05 |
| Documentation interactive fermée hors développement, `/metrics` derrière un jeton, sonde qui ne publie plus de version | `app/main.py`, `app/api/security.py` | A05 | | Documentation interactive fermée hors développement, `/metrics` derrière un jeton, sonde qui ne publie plus de version | `app/main.py`, `app/api/security.py` | A05 |
| Scan dynamique OWASP ZAP de l'API authentifiée (compte `lecteur` jetable), non bloquant, configuration par défaut du backend uniquement (ni TLS ni en-têtes du reverse proxy) | `.github/workflows/dast.yml`, `scripts/dast-token.sh` | A05, API8 Security Misconfiguration | | Scan dynamique OWASP ZAP de l'API authentifiée (compte `lecteur` jetable), non bloquant, configuration par défaut du backend uniquement (ni TLS ni en-têtes du reverse proxy) | `.github/workflows/dast.yml`, `scripts/dast-token.sh` | A05, API8 Security Misconfiguration |
| En-têtes `nosniff`, `DENY`, `no-referrer`, et `no-store` sur les routes d'authentification | `app/api/middleware.py` | A05 | | En-têtes `nosniff`, `DENY`, `no-referrer`, `Cross-Origin-Resource-Policy: same-origin`, et `no-store` sur les routes d'authentification | `app/api/middleware.py` | A05 |
| Refus de rétrograder ou désactiver le dernier administrateur actif | `app/services/user.py` | A04 Insecure Design | | Refus de rétrograder ou désactiver le dernier administrateur actif | `app/services/user.py` | A04 Insecure Design |
| Amorçage du premier administrateur hors dépôt, mot de passe jamais dans `argv` ni dans Git | `app/cli.py` | A02, A05 | | Amorçage du premier administrateur hors dépôt, mot de passe jamais dans `argv` ni dans Git | `app/cli.py` | A02, A05 |
| Réponse de l'API Mock bornée avant écriture : timeout, plafond de sites et de mesures, bornes physiques par grandeur, recopie des seuls champs attendus | `app/etl/mock_api_import.py` | API10 Unsafe Consumption of APIs | | Réponse de l'API Mock bornée avant écriture : timeout, plafond de sites et de mesures, bornes physiques par grandeur, recopie des seuls champs attendus | `app/etl/mock_api_import.py` | API10 Unsafe Consumption of APIs |