fix(draco): résolution dev/prod par l'hôte de la requête (7.82.4)
FlowDeck CI / test (push) Failing after 3h13m27s
FlowDeck CI / lint (push) Successful in 1m47s
FlowDeck CI / docker (push) Skipped

Le mode console du panneau restait une page morte sur flowdeck-dev :
CSP frame-src/connect-src et DRACO_PANNEAU_CONFIG.agent_url pointaient
l'agent prod (pas encore installée). Deux trous de la 7.82.3 :
agent_url() sans aucune dérivation, et aucune résolution lisant
l'hôte de la requête.

Pour tout ce qui est rendu à une requête (barre/DRACO_URLS, iframe
du panneau, CSP, boutons et redirections SSO) : explicite >
hôte public de la requête (motif fermé
^(og|flowdeck|tiktak|kanbante|agent)(SUFFIXE).dracodev.net$,
suffixe appliqué aux sœurs et à l'agent) > dérivation par les
adresses de l'agent connues en config (élargie : agent_url() se
dérive de l'URL d'événements explicite) > défaut officiel.
L'hôte est posé par le middleware CSP dans un ContextVar (patron
du nonce CSP) : iframe et CSP de la même réponse consomment la
même origine résolue. Contextes sans requête inchangés sinon.

Tests : +12 dans test_draco_urls_devprod.py (25/25) — Host dev
sans aucun env → tout en -dev ; Host prod → officiels ; Host LAN
→ repli config/défauts ; explicite gagne sur l'hôte ; motif fermé.
Suite complète : 4 failed / 26 errors identiques sur arbre propre
(familles préexistantes MCP/realtime/my_tasks/gallery/sync).
This commit is contained in:
Spark
2026-10-26 03:04:50 +00:00
parent 283e226adf
commit fd79a49d63
7 changed files with 403 additions and 18 deletions
+14 -4
View File
@@ -134,8 +134,9 @@ class ContentSecurityPolicyMiddleware(BaseHTTPMiddleware):
# service d'agents commun (premier parti, même programme) —
# seul hôte externe nommé, rien d'autre ne s'ouvre.
# v7.82.0 (dev/prod) : cet hôte n'est plus figé — {agent} est
# l'origine résolue par la config (DRACO_AGENT_URL ; défaut =
# nom officiel, un dev déclare son agent-dev dans le .env).
# l'origine résolue par draco_urls (DRACO_AGENT_URL explicite,
# sinon hôte de la requête — v7.82.4 —, sinon dérivation par
# l'agent connu en config ; défaut = nom officiel).
# Même origine pour frame-src : la console du service est
# encadrée en iframe par le panneau (surimpression v3).
"connect-src 'self' {agent} ws://{host} wss://{host}; "
@@ -152,6 +153,17 @@ class ContentSecurityPolicyMiddleware(BaseHTTPMiddleware):
# tâche créée maintenant → le contexte (donc le nonce) y est copié,
# exactement ce que les templates liront via `csp_nonce()`.
CSP_NONCE.set(nonce)
# v7.82.4 : même patron pour l'hôte public de la requête —
# `draco_urls` le lit pour résoudre l'environnement (dev/prod)
# de tout ce qui est rendu à cette requête (barre, iframe du
# panneau, SSO), et la CSP ci-dessous consomme la MÊME origine
# agent résolue. X-Forwarded-Host prime, comme
# `get_redirect_uri()` (app/routers/auth.py).
from app.services import draco_urls
draco_urls.definir_hote_requete(
request.headers.get("x-forwarded-host", "")
or request.headers.get("host", ""))
response = await call_next(request)
# Only set CSP on HTML responses
content_type = response.headers.get("content-type", "")
@@ -160,8 +172,6 @@ class ContentSecurityPolicyMiddleware(BaseHTTPMiddleware):
# on retire quand même tout caractère hors base URL par sécurité.
host = re.sub(r"[^0-9A-Za-z.\-:\[\]]", "",
request.headers.get("host", ""))
from app.services import draco_urls
agent = re.sub(r"[^0-9A-Za-z.\-:/]", "",
draco_urls.origine(draco_urls.agent_url()))
response.headers[self.CSP_HEADER] = self.CSP_VALUE.format(