--- issueRef: "#133" version: 5 updatedBy: {"kind":"user"} updatedAt: 1785881187645 --- ## Décision d'architecture (2026-08-02) Documentée dans `ARCHITECTURE.md` §22.1 (nouvelle section "Plugins — service des assets multi-fichiers & persistance plugin-owned"). **Contrat tranché :** `asset_allowed` (`crates/app-tauri/src/plugins.rs:504-536`) doit servir tout chemin relatif confiné dès lors que les trois gardes déjà présentes sont satisfaites — entrée registre trouvée + `lifecycle_state.is_runtime_active()` + `entry.content_hash == hash` de l'URL (intégrité du package entier) — sans plus restreindre au triplet `declared_main || declared_icon || starts_with("assets/")`. Le confinement canonicalize aval (lignes 467-483, `target.starts_with(&root)`) reste inchangé et continue de protéger contre l'évasion de racine. `validator.validate(manifest)` reste appelé comme garde d'intégrité globale du manifeste, mais cesse de gater le service fichier par fichier. **Rationale sécurité :** aucune perte de garantie — le modèle de menace est fixé par `content_hash` à l'installation (audité en #135), donc restreindre les fichiers *siblings* d'un package déjà intégralement vérifié n'arrête aucune attaque supplémentaire, ça casse juste des graphes de modules ESM légitimes. **Limite figée :** pas de résolution `node_modules`/bare specifiers — hors scope, aucun résolveur de module à construire. Un plugin avec dépendances tierces les bundle ou vendore en relatif, à son choix. **Contrat de confinement/désinstallation formalisé :** racine servie = exclusivement `app_data/plugins/installed//` ; jamais d'écriture/exposition hors project root ou `.ideai/` de l'utilisateur ; désinstallation = suppression complète + entrée registre, zéro résidu (périmètre détaillé pour #135). ## Débloque - **#134** : remplacer la dernière ligne de `asset_allowed` — `Ok(declared_main || declared_icon || rel.as_str().starts_with("assets/"))` — par une autorisation basée uniquement sur les gardes déjà calculées plus haut dans la fonction. Tests de non-régression path-traversal et hash/lifecycle invalides déjà spécifiés dans #134, contrat inchangé. - **#135** : périmètre d'audit = confinement à l'install (`RelativePath::new` déjà rejette `..`/absolu côté domaine — vérifier qu'il est bien appliqué à l'INSTALL, pas seulement au SERVE) + désinstallation 100%. Aucun changement de code applicatif dans ce ticket (portée strictement architecture, conforme à l'objectif du ticket). Fichier touché : `ARCHITECTURE.md` (§22.1 ajouté).