Ctrl K
ComunidadGuías…

AUDIT-0.12.9-SUITE.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Audit complémentaire de 0.12.9 — 7 septembre 2026

Base : `c3044f4`, version publiée 0.12.9. Les douze corrections précédentes
restent en place. Les cinq défauts ci-dessous ont été reproduits, puis
**corrigés pour 0.12.10**. Les descriptions des
reproductions et les numéros de ligne correspondent à la version avant correction.

## 1. P1 — Suppression d'un MP4 hors du dossier des directs

Sources : `serve.py:69`, `serve.py:3285`, `serve.py:3513`.

La validation `IDENTITY` accepte les composants `..`. La classification des
enregistrements directs vérifie seulement que `racine_direct / identity`
désigne un fichier. La suppression emploie ensuite ce chemin sans vérifier
que sa résolution reste sous la racine autorisée.

Reproduction : POST `/api/appliquer-selection` avec une identité contenant
`../` et le nom d'un MP4 témoin placé dans le parent du dossier des directs.
Le témoin est supprimé et la réponse annonce `ok: true`, `supprime`.
Le test utilise uniquement une sentinelle créée dans un répertoire temporaire.

Portée : la requête doit passer les contrôles d'hôte et de jeton de
l'application ; ce diagnostic ne démontre pas une attaque distante sans
authentification. La cible doit être un MP4 accessible au compte exécutant
l'application. Aucun appel Blink n'est nécessaire pour déclencher ce chemin.

Correction appliquée : validation stricte de chaque identité, résolution et
confinement des chemins avant toute mutation du lot. Les traversées et les
liens sortant de la racine sont refusés. Nouvelle vérification avant suppression ;
le nettoyage des dossiers vides ne supprime jamais la racine des directs.
Tests : `test_serve_direct_confinement.py` et
`test_serve_appliquer_selection_lock.py`.

## 2. P1 — Retour arrière de mise à jour non préservé après un échec de restauration

Source : `maj.py:677`, particulièrement les lignes 692 et 698–707.

Le remplacement intercepte les erreurs de restauration sans les distinguer
d'un retour arrière réussi. Le finaliseur réessaie alors la permutation ;
chaque essai peut supprimer ou remplacer les sauvegardes `.ancien`.

Reproduction sur fichiers factices : la copie des nouvelles bibliothèques
échoue ; la restauration de l'ancien exécutable est refusée une seule fois.
Après trois appels à `_permuter`, tous renvoient `False`, mais l'ancien
exécutable n'existe plus, ni en place ni en sauvegarde. L'installation
contient l'exécutable neuf avec les bibliothèques anciennes.

Ce scénario exige plusieurs erreurs d'E/S ; il n'est pas observé sur une
mise à jour réelle. Il démontre néanmoins que le message annonçant une
version précédente intacte n'est pas garanti dans ce chemin.

Correction appliquée : marqueur exclusif avant la permutation, retour arrière
en ordre inverse et retrait des éléments nouveaux partiellement copiés. Une
restauration incomplète interdit les nouveaux essais, la relance et le nettoyage
des sauvegardes/préparations. Un arrêt brutal conserve aussi ce garde-fou.
Un verrou interprocess commun au nettoyage et à la permutation ferme aussi
la course identifiée en relecture : un nettoyage ne peut plus valider l'absence
de marqueur puis effacer une sauvegarde créée par une permutation concurrente.
Cette réservation est attachée à l'installation, indépendamment de `BLINK_HOME`.
Une réparation explicite reste nécessaire après une restauration incomplète ;
le correctif préserve les sauvegardes mais ne prétend pas réparer toute panne d'E/S.
Tests : `test_maj_restauration.py`.

## 3. P2 — Échec des réglages après publication du nouveau stockage

Source : `serve.py:3598` à `serve.py:3612`.

La route publie le changement de stockage, puis écrit les nouveaux réglages.
Une erreur de cette seconde écriture renvoie HTTP 500 sans annuler le
pointeur ni redémarrer. Les modules déjà chargés conservent leurs chemins
initialisés dans l'ancien dossier, alors que `runtime.app_dir()` annonce
le nouveau.

Reproduction : vraie migration vers un dossier temporaire, puis
`PermissionError` simulée dans `ecrire_reglages`. Résultat : HTTP 500,
pointeur neuf présent, racine dynamique nouvelle, `serve.BASE_DIR` ancienne,
aucun redémarrage demandé. Le risque est un fonctionnement partagé entre
deux racines malgré une opération annoncée en échec.

Correction appliquée : copie de l'état et écriture des nouveaux réglages dans
la destination avant publication du pointeur. Une erreur de préparation
laisse donc l'ancienne racine active. Si le marqueur de configuration initiale
échoue ensuite, restauration du pointeur précédent. La priorité de `BLINK_HOME`
et le retour au dossier par défaut sont conservés.
Tests : `test_serve_redemarrage_fiable.py`.

## 4. P2 — Désactiver la suppression automatique ne suspend pas celle du lot courant

Sources : `blink_engine.py:389` / `:450` pour le cloud, `:791` / `:823` pour
le chemin USB.

L'ensemble des caméras autorisées est lu une seule fois avant le traitement
du lot. Une désactivation enregistrée pendant le transfert n'est donc pas
consultée avant les suppressions suivantes.

Reproduction USB et cloud : deux clips, désactivation réelle du fichier de
préférences pendant le premier transfert. Les deux appels de suppression
simulés ont ensuite lieu ; la préférence sur disque est vide au moment de
chacun d'eux. Les copies locales sont conservées : aucune perte de copie
locale n'est démontrée.

Nuance : la relecture au passage suivant est documentée dans les commentaires
du code. Le défaut est l'application différée d'un refus de suppression,
sans avertissement dans l'interface. Ce n'est pas la course entre deux clics
corrigée dans 0.12.9.

Correction appliquée : relecture de l'autorisation après chaque copie locale,
immédiatement avant chaque appel de suppression USB/cloud. Aucun verrou de
réglages n'est gardé pendant l'appel réseau. Une requête déjà envoyée peut
encore aboutir ; la désactivation protège les suppressions suivantes.
Tests : `test_suppression_auto_pendant_lot.py`.

## 5. P2 — La surveillance masque les caméras homonymes

Sources : `watch.py:71`, `watch.py:83`, `watch.py:123`.

L'état brut puis l'état surveillé sont indexés uniquement par nom. Une caméra
homonyme d'un autre réseau écrase la précédente. La date du dernier clip est
elle aussi regroupée par nom.

Reproduction : deux caméras « Jardin », réseaux 111 et 222. La première est
hors ligne avec une batterie faible ; la seconde est en ligne, batterie OK.
L'état produit contient une seule caméra, saine, et `compare()` ne signale
aucune alerte, même au premier passage.

Correction appliquée : identités réseau/appareil dans l'état, les comparaisons
et les dates d'activité, récupération des appareils depuis l'état brut même
si blinkpy a écrasé un homonyme. Les noms uniques restent inchangés ; les
homonymes affichent réseau et appareil. Migration des sourdines et adaptation
du réglage de suppression automatique ; les anciens clips ambigus ne sont
jamais attribués arbitrairement à une caméra pour autoriser leur suppression.
Tests : `test_watch_identity.py`.

## Points antérieurs toujours ouverts, distincts des cinq nouveaux défauts

- Autostart macOS : un chemin contenant `&` donne un plist XML invalide ;
  deux compositions différentes reçoivent aussi le même `Label`. Ces points
  étaient déjà consignés dans l'audit du 13 août, section 28.59. XML et
  collision confirmés sur le contenu généré, pas par un lancement réel macOS.
- Robustesse WebRTC : aucun plafond du tampon élémentaire en l'absence de
  séparateur H.264. Avec 10 000, 20 000 puis 30 000 paquets TS synthétiques
  invalides, le tampon retient respectivement 1 840 000, 3 680 000 et
  5 520 000 octets. Croissance confirmée, pas d'épuisement mémoire ni de
  panne caméra réelle observés.

## Reproductions et limites

Diagnostics locaux sous `build-win7` (ignorés par Git) :

- `audit-restants-0129.py` : retour arrière, autostart et tampon H.264.
- `audit-web-input-20260907.py` : suppression hors dossier et réglages.
- `audit-download-watch-20260907.py` : suppression différée et homonymes.

Les scripts de diagnostic ci-dessus décrivent le comportement défectueux
avant correction ; les tests de non-régression versionnés vérifient le correctif.

## Validation finale — 8 septembre 2026

- `python -m unittest discover -q` : 597 tests recensés sous Python 3.12,
  596 réussis et 1 ignoré ; mêmes résultats sous Python 3.8.10.
- Le test ignoré exige la création d'un lien symbolique natif, non autorisée
  sur cet hôte. Un test distinct simule une résolution hors racine et réussit.
- `python tests.py` : intégration complète réussie sous Python 3.12 et 3.8.10
  (processus temporaires, serveur de test, assemblage FFmpeg et mise à jour factice).
- `python docs.py --check` et `git diff --check` : réussis.
- 44 tests supplémentaires par rapport aux 553 tests de la version 0.12.9,
  dont 14 pour la restauration et la réservation de l'installation.

### Ajustements des tests pour la publication 0.12.11

La CI de 0.12.10 a révélé deux défauts des nouvelles simulations, absents
sur l'hôte de développement : le chemin temporaire peut être normalisé
différemment (nom court Windows, `/var` sur macOS), et le faux lancement de
`stop` interceptait aussi la commande `ps` servant à identifier le propriétaire
du verrou sur POSIX. Les tests normalisent désormais leurs chemins et isolent
explicitement l'identité simulée. Aucun correctif applicatif supplémentaire :
0.12.11 reprend les cinq corrections avec ces tests portables. Le tag 0.12.10,
déjà publié sans archive Win7 à cause de cet échec, n'est pas déplacé.

Résultats reproduits sous Python 3.12 et 3.8.10 sur l'hôte Windows. Aucun
processus utilisateur arrêté, aucune caméra réveillée, aucune suppression
distante ni modification TLS. Les suppressions locales de diagnostic
concernent uniquement des fichiers factices créés pour ces tests. La validation
précède la publication via `deploy.py`. Audit ciblé, pas une preuve d'absence
d'autres défauts.