feat: journée 17 avril — tests E2E validés, dashboard fleet+audit, VWB bridge, cleaner C2
Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 14s
security-audit / pip-audit (CVE dépendances) (push) Successful in 10s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 13s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped

Pipeline E2E complet validé :
  Capture VM → streaming → serveur → cleaner → replay → audit trail
  Mode apprentissage supervisé fonctionne (Léa échoue → humain → reprise)

Dashboard :
  - Cleanup 14→10 onglets (RCE supprimée)
  - Fleet : enregistrer/révoquer agents, tokens, ZIP pré-configuré téléchargeable
  - Audit trail MVP (/audit) : filtres, tableau, export CSV, conformité AI Act/RGPD
  - Formulaire Fleet simplifié (nom + email, machine_id auto)

VWB bridge Léa→VWB :
  - Compound décomposés en N steps (saisie + raccourci visibles)
  - Layout serpentin 3 colonnes (plus colonne verticale)
  - Badge OS 🪟/🐧, filtre OS retiré (admin Linux voit Windows)
  - Fix import SQLite readonly

Cleaner intelligent :
  - Descriptions lisibles (UIA/C2) + détection doublons
  - Logique C2 : UIElement identifié = jamais parasite
  - Patterns parasites resserrés
  - Message Léa : "Je n'y arrive pas, montrez-moi comment faire"

Config agent (INC-1 à INC-7) :
  - SERVER_URL + SERVER_BASE unifiés
  - RPA_OLLAMA_HOST séparé
  - allow_redirects=False sur POST
  - Middleware réécriture URL serveur

CI Gitea : fix token + Flask-SocketIO + ruff propre
Fleet endpoints : /agents/enroll|uninstall|fleet + agent_registry SQLite
Backup : script quotidien workflows.db + audit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-04-17 17:46:40 +02:00
parent 2fa864b5c7
commit 4f61741420
27 changed files with 5088 additions and 1543 deletions

View File

@@ -544,6 +544,28 @@ class TraceStreamer:
except OSError as e:
logger.debug(f"Purge échouée : {path}{e}")
# =========================================================================
# Protection redirect POST→GET (INC-7)
# =========================================================================
@staticmethod
def _check_redirect(resp, url: str):
"""Detecter et logger une redirection sur un POST.
La lib requests transforme un POST en GET sur 301/302 (RFC 7231).
Avec allow_redirects=False, on recoit le 301/302 directement.
On log un WARNING explicite pour que l'admin corrige l'URL.
"""
if resp.status_code in (301, 302, 307, 308):
location = resp.headers.get("Location", "?")
logger.warning(
f"Redirection {resp.status_code} detectee sur POST {url} "
f"{location}. Verifiez que RPA_SERVER_URL utilise "
f"https:// si le serveur redirige."
)
return True
return False
# =========================================================================
# Envois HTTP
# =========================================================================
@@ -551,15 +573,20 @@ class TraceStreamer:
def _register_session(self):
"""Enregistrer la session auprès du serveur (avec identifiant machine)."""
try:
url = f"{STREAMING_ENDPOINT}/register"
resp = requests.post(
f"{STREAMING_ENDPOINT}/register",
url,
params={
"session_id": self.session_id,
"machine_id": self.machine_id,
},
headers=self._auth_headers(),
timeout=3,
allow_redirects=False,
)
if self._check_redirect(resp, url):
logger.warning("Enregistrement session échoué (redirect)")
return
if resp.ok:
logger.info(
f"Session {self.session_id} enregistrée sur le serveur "
@@ -579,15 +606,18 @@ class TraceStreamer:
C'est la dernière chance de sauver les données de la session.
"""
try:
url = f"{STREAMING_ENDPOINT}/finalize"
resp = requests.post(
f"{STREAMING_ENDPOINT}/finalize",
url,
params={
"session_id": self.session_id,
"machine_id": self.machine_id,
},
headers=self._auth_headers(),
timeout=30, # Le build workflow peut prendre du temps
allow_redirects=False,
)
self._check_redirect(resp, url)
if resp.ok:
result = resp.json()
logger.info(f"Session finalisée: {result}")
@@ -601,6 +631,7 @@ class TraceStreamer:
if not self._server_available:
return False
try:
url = f"{STREAMING_ENDPOINT}/event"
payload = {
"session_id": self.session_id,
"timestamp": time.time(),
@@ -608,11 +639,14 @@ class TraceStreamer:
"machine_id": self.machine_id,
}
resp = requests.post(
f"{STREAMING_ENDPOINT}/event",
url,
json=payload,
headers=self._auth_headers(),
timeout=2,
allow_redirects=False,
)
if self._check_redirect(resp, url):
return False
return resp.ok
except Exception as e:
logger.debug(f"Streaming Event échoué: {e}")
@@ -645,18 +679,22 @@ class TraceStreamer:
"machine_id": self.machine_id,
}
url = f"{STREAMING_ENDPOINT}/image"
if jpeg_buf is not None:
# Envoi du JPEG compressé (BytesIO, pas de fuite possible)
files = {
"file": (f"{shot_id}{suffix}", jpeg_buf, content_type)
}
resp = requests.post(
f"{STREAMING_ENDPOINT}/image",
url,
files=files,
params=params,
headers=self._auth_headers(),
timeout=5,
allow_redirects=False,
)
if self._check_redirect(resp, url):
return ImageSendResult.FAILED
if resp.ok:
self._purge_local_image(path)
return ImageSendResult.OK
@@ -668,12 +706,15 @@ class TraceStreamer:
"file": (f"{shot_id}.png", f, "image/png")
}
resp = requests.post(
f"{STREAMING_ENDPOINT}/image",
url,
files=files,
params=params,
headers=self._auth_headers(),
timeout=5,
allow_redirects=False,
)
if self._check_redirect(resp, url):
return ImageSendResult.FAILED
if resp.ok:
self._purge_local_image(path)
return ImageSendResult.OK