11. Les nœuds personnalisés, et leur sécurité
Sections
Réglages de lecture
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
Code et termes techniques en anglais
Désactivé : la voix de la page lit tout, sans pause entre deux voix.
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
D’autres voix : Microsoft Edge propose des voix naturelles en ligne, et Windows (Paramètres › Heure et langue › Voix), macOS et Android en installent d’autres.
Code : le pack de nœuds Guitar Alchemist dans custom-nodes/ga/, avec ses tests et ses workflows, et le script d’audit, sa fixture et la démonstration pickle dans custom-nodes/audit/.
Tous les nœuds des dix premières leçons étaient fournis avec ComfyUI. La leçon 6 avait besoin d’un préprocesseur de pose que le cœur n’a pas, et la leçon 8 a rencontré des fichiers GGUF que seul un nœud personnalisé sait charger. Un nœud personnalisé est un dossier de code Python que ComfyUI importe dans son propre processus au démarrage, avec les droits de l’utilisateur qui l’a lancé. Il n’y a ni bac à sable, ni permission, ni signature. Cette leçon lit comment se passe le chargement, écrit un petit pack, puis examine les risques : ce que vérifie l’installateur, ce qui a déjà mal tourné, comment lire un pack avant de l’installer, et comment limiter les dégâts si tu en installes un mauvais.
Comment ComfyUI charge un pack
Section intitulée « Comment ComfyUI charge un pack »Le dossier et l’import
Section intitulée « Le dossier et l’import »Au démarrage, init_external_custom_nodes liste chaque dossier enregistré comme custom_nodes, par défaut ComfyUI/custom_nodes/, ou custom_nodes/ sous --base-directory, et essaie chaque entrée :
- un fichier qui ne se termine pas par
.pyest ignoré, tout comme un nom qui se termine par.disabled; - avec
--disable-all-custom-nodes, seuls les noms passés à--whitelist-custom-nodessont chargés ; - avec
--enable-manager, ComfyUI-Manager peut bloquer une entrée, avec le message « Blocked by policy », c’est-à-dire bloquée par la politique ; dans la version 4.2.2, sa fonctionshould_be_disabledne bloque qu’une ancienne copie du Manager lui-même, installée comme nœud personnalisé.
Ensuite, load_custom_node importe le pack avec importlib : un fichier .py seul, ou le __init__.py du dossier. La ligne qui compte est module_spec.loader.exec_module(module), à la ligne 2266. Elle exécute tout le module, de haut en bas, avant que ComfyUI n’ait regardé quoi que ce soit de son contenu. Tout ce que __init__.py importe, démarre ou télécharge se produit ici, même si le pack ne définit finalement aucun nœud.
Après l’import, ComfyUI lit trois noms dans le module :
NODE_CLASS_MAPPINGS, un dictionnaire qui associe le nom unique d’un nœud à sa classe ; sans lui, un pack V3 exporte plutôtcomfy_entrypoint, et sans aucun des deux, le journal dit « Skip … due to the lack of NODE_CLASS_MAPPINGS or comfy_entrypoint (need one) », c’est-à-dire que le module est ignoré faute de l’un ou de l’autre ;NODE_DISPLAY_NAME_MAPPINGS, les noms affichés dans l’interface ;WEB_DIRECTORY, un dossier de JavaScript, ou la cléwebsous[tool.comfy]dans lepyproject.tomldu pack.
Un nom de nœud qui existe déjà dans le cœur de ComfyUI est ignoré (c’est l’ensemble ignore des noms intégrés), donc un pack ne peut pas remplacer KSampler par la table de correspondance. Il peut quand même tout remplacer par d’autres moyens : le pack est du Python ordinaire, dans le même processus. ComfyUI le sait, et hook_breaker_ac10a0.py commence par « Prevent custom nodes from hooking anything important », c’est-à-dire empêcher les nœuds personnalisés d’intercepter quoi que ce soit d’important. Il enregistre comfy.model_management.cast_to avant le chargement des packs et le remet en place ensuite. Il protège une seule fonction, pour la stabilité. Ce n’est pas une frontière de sécurité, et il n’essaie pas d’en être une.
Si l’import lève une exception, ComfyUI journalise la trace, « Cannot import … module for custom nodes », c’est-à-dire que le module n’a pas pu être importé, et continue. La page sur le cycle de vie le dit : « If there is an error in your code, Comfy will continue, but will report the module as having failed to load. So check the Python console! », c’est-à-dire qu’en cas d’erreur, Comfy continue mais signale l’échec du chargement, et qu’il faut donc regarder la console Python.
Avant l’import : prestartup_script.py
Section intitulée « Avant l’import : prestartup_script.py »Plus tôt encore, execute_prestartup_script exécute le prestartup_script.py de chaque dossier de pack qui en a un, bien avant le chargement des nœuds. Les packs s’en servent pour modifier tôt des chemins ou des réglages. rgthree-comfy en a un ; ComfyUI-Manager aussi, et il s’en sert pour lancer sa propre fonction security_check().
Du côté du navigateur : WEB_DIRECTORY
Section intitulée « Du côté du navigateur : WEB_DIRECTORY »Chaque dossier enregistré dans EXTENSION_WEB_DIRS est servi sous /extensions/<pack>/ (server.py, ligne 1246), et GET /extensions renvoie la liste de tous leurs fichiers .js. Le frontend importe chacun d’eux dans la page. Le JavaScript d’un pack s’exécute donc dans l’onglet de navigateur de toute personne qui ouvre l’interface, avec accès à la page, à l’API de la file d’attente et à tout ce que la page peut atteindre. La présentation du JavaScript de ComfyUI décrit les points d’extension.
WEB_DIRECTORY est une convention, pas un contrôle. Le __init__.py de ComfyUI-Impact-Pack écrit directement dans le dictionnaire de ComfyUI : nodes.EXTENSION_WEB_DIRS["ComfyUI-Impact-Pack"] = os.path.join(...), avec le commentaire « Inject directly into EXTENSION_WEB_DIRS instead of WEB_DIRECTORY », c’est-à-dire injecter directement dans EXTENSION_WEB_DIRS au lieu de passer par WEB_DIRECTORY. Ça fonctionne, parce que le Python du pack peut modifier n’importe quel objet du serveur.
À l’installation : requirements.txt et install.py
Section intitulée « À l’installation : requirements.txt et install.py »ComfyUI lui-même n’installe jamais rien. ComfyUI-Manager, si, dans execute_install_script : après avoir cloné un pack, il lance pip install pour chaque ligne de son requirements.txt, puis python install.py si le fichier existe. Sous Windows, try_install_script ne les exécute pas tout de suite : il les réserve, et le prestartup du Manager les exécute au démarrage suivant. Dans les deux cas, le code du pack et celui de ses dépendances s’exécutent avant même que tu ajoutes un de ses nœuds à un graphe.
flowchart TD I["ComfyUI-Manager installe un pack"] --> I1["git clone"] I1 --> I2["pip install, une ligne de requirements.txt à la fois"] I2 --> I3["python install.py"] S["python main.py"] --> M{"--enable-manager ?"} M -- oui --> M1["prestartup du Manager : security_check, pip freeze, installations réservées"] M -- non --> P M1 --> P["prestartup_script.py de chaque pack"] P --> E["nœuds du cœur, comfy_extras, nœuds d'API"] E --> C{"pour chaque entrée de custom_nodes"} C -- ".disabled, pas en .py, ou absent de la liste blanche" --> C C -- chargement --> X["exec_module(__init__.py) : n'importe quel code Python s'exécute ici"] X --> W["WEB_DIRECTORY ou web de tool.comfy : EXTENSION_WEB_DIRS"] X --> N["NODE_CLASS_MAPPINGS ou comfy_entrypoint : registre des nœuds"] W --> B["GET /extensions : le navigateur importe chaque .js"] N --> R["GET /object_info, puis POST /prompt appelle FUNCTION"]Un pack de nœuds Guitar Alchemist
Section intitulée « Un pack de nœuds Guitar Alchemist »Le pack du cours dessine ce que Guitar Alchemist (GA) sait du manche, sous forme d’images qu’un graphe ComfyUI peut utiliser. Il a trois nœuds :
| Nœud | Entrées | Sorties |
|---|---|---|
GAChordDiagram (GA Chord Diagram) |
chord : un voicing écrit depuis le mi grave, comme x32010 ou x-10-12-12-12-10, ou un symbole comme C, Am, G7, Bm7b5 ; size : de 128 à 2048 pixels |
image : un diagramme d’accord noir sur blanc, de size × size ; ga_diagram : le même voicing dans l’ordre de GA |
GAFretboardControlMap (GA Fretboard Control Map) |
source : chord ou scale ; chord ; key et mode ; fret_start de 0 à 23 et fret_end de 1 à 24 ; width et height, de 256 à 2048 par pas de 8, 1024 par défaut ; line_width ; en option, note_style, ring (un disque noir cerclé de blanc, par défaut) ou filled (un disque blanc), et inlays, show ou hide ; l’accord xxxxxx dessine un manche vide |
lines : des contours blancs sur noir, comme une carte Canny ; depth : une carte façon profondeur, plus clair signifie plus proche ; layout : du JSON avec le manche, les frettes et les cordes, et le centre et le rayon de chaque note et de chaque repère, en pixels |
GAScalePrompt (GA Scale Prompt) |
key, mode, subject |
prompt : un fragment de texte, identique à chaque fois |
La théorie, calculée localement
Section intitulée « La théorie, calculée localement »Les nœuds n’appellent pas de serveur GA. Ils calculent à partir de tables copiées du code source de GA, figé sur un commit, et theory.py cite chacune d’elles :
- l’accordage est le
Tuning.Defaultde GA, « E2 A2 D3 G3 B3 E4 », que GA stocke en commençant par la corde la plus aiguë ; - les modes sont les rotations du
Scale.Majorde GA, avec les noms deMajorScaleDegree.ToName.
L’ordre des cordes demande de l’attention. Les diagrammes d’accords écrivent un voicing à partir de la corde 6, le mi grave : le do majeur ouvert s’écrit x32010. GA numérote les cordes à partir de la corde 1, « the first string (Highest pitch) », c’est-à-dire la première corde, la plus aiguë, et ses diagrammes se lisent 0-1-0-2-3-x, comme l’a constaté la leçon 3 du cours de théorie musicale. Les nœuds prennent l’ordre des diagrammes, celui que tape un guitariste, et renvoient l’ordre de GA dans ga_diagram, pour qu’un workflow puisse le transmettre à GA.
Les modes sont épelés avec une lettre par degré, suivie d’une altération : do dorien s’écrit C D Eb F G A Bb. La méthode Note.Chromatic.ToAccidented de GA donne un nom en dièse à chaque touche noire, donc la même gamme sortirait avec D# et A# ; le cours de théorie musicale décrit ce problème d’orthographe.
Une classe de nœud
Section intitulée « Une classe de nœud »Un nœud V1 est une simple classe. Voici le diagramme d’accord, tiré de nodes.py :
class GAChordDiagram: """A chord chart for a voicing (x32010, low E first) or a known symbol (C, Am, G7, Bm7b5...)."""
@classmethod def INPUT_TYPES(cls): return { "required": { "chord": ("STRING", {"default": "x32010", "multiline": False}), "size": ("INT", {"default": 512, "min": 128, "max": 2048, "step": 8}), } }
RETURN_TYPES = ("IMAGE", "STRING") RETURN_NAMES = ("image", "ga_diagram") FUNCTION = "draw" CATEGORY = CATEGORY DESCRIPTION = "Draws a chord diagram. ga_diagram is the same voicing in GA's order, high E first."
def draw(self, chord, size): _, frets = theory.resolve_chord(chord) return (to_image(drawing.chord_diagram(frets, size)), theory.ga_diagram(frets))INPUT_TYPES est ce que publie /object_info, et ce contre quoi la leçon 3 a validé les workflows. FUNCTION nomme la méthode qu’appelle l’exécuteur, avec un argument nommé par entrée, et la méthode renvoie un tuple avec une valeur par entrée de RETURN_TYPES. Une IMAGE est un tenseur float32 de forme [batch, height, width, channels], avec des valeurs de 0 à 1, tel que LoadImage le construit. La fonction to_image du pack empile des images Pillow dans cette forme, et renvoie un tableau NumPy quand PyTorch est absent, ce qu’utilisent les tests.
Le __init__.py du pack contient une seule ligne de code, from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS. Il n’y a ni WEB_DIRECTORY, ni requirements.txt, ni install.py, ni prestartup_script.py : NumPy et Pillow font déjà partie des dépendances de ComfyUI.
Les dessins n’utilisent aucune police. Les seuls chiffres, le numéro de case à côté d’un diagramme qui commence au-dessus de la case 5, viennent d’une grille de 3 × 5 dans drawing.py, et le module ImageDraw de Pillow trace les lignes et les ellipses sans anticrénelage. Les pixels dépendent alors des entrées et de la version de Pillow, pas des polices d’une machine. Les frettes de la carte de contrôle sont espacées comme sur un vrai manche, où la frette n se trouve à 1 − 2−n/12 du diapason.

Dessinés par le nœud GA Chord Diagram avec Pillow, pas par un modèle de diffusion : C, G, Am, F, E7 et Bm7b5, 256 pixels chacun, les images de expected/.

Dessinées par le nœud GA Fretboard Control Map avec Pillow : la sortie lines pour l’accord C, cases 0 à 5, et la sortie depth pour la éolien, cases 5 à 12 ; toutes deux en 1024 × 1024, recadrées autour du manche et réduites.
Tester un nœud sans ComfyUI
Section intitulée « Tester un nœud sans ComfyUI »Comme un nœud est une classe, les tests importent le pack comme le fait ComfyUI, en tant que paquet nommé d’après son dossier, et appellent les méthodes. Ils ont besoin de NumPy et de Pillow, pas de ComfyUI, de PyTorch ni d’un GPU. test_ga.py vérifie que chaque forme d’accord joue exactement les classes de hauteurs de son symbole, que x32010 devient 0-1-0-2-3-x, comment six modes sont épelés, et compare chaque image, pixel par pixel, avec un PNG de expected/ :
$ python -m unittest discover -s code/comfyui/custom-nodes/ga/tests -vtest_bad_fret_range (test_ga.NodesTest.test_bad_fret_range) ... oktest_chord_diagrams (test_ga.NodesTest.test_chord_diagrams) ... oktest_control_map_options (test_ga.NodesTest.test_control_map_options) ... oktest_control_maps (test_ga.NodesTest.test_control_maps) ... oktest_empty_neck (test_ga.NodesTest.test_empty_neck) ... oktest_layout_matches_pixels (test_ga.NodesTest.test_layout_matches_pixels) ... oktest_mappings (test_ga.NodesTest.test_mappings) ... oktest_scale_prompt (test_ga.NodesTest.test_scale_prompt) ... oktest_unknown_chord (test_ga.NodesTest.test_unknown_chord) ... oktest_every_shape_plays_its_symbol (test_ga.TheoryTest.test_every_shape_plays_its_symbol) ... oktest_ga_order_is_high_e_first (test_ga.TheoryTest.test_ga_order_is_high_e_first) ... oktest_modes_are_spelled_with_one_letter_per_degree (test_ga.TheoryTest.test_modes_are_spelled_with_one_letter_per_degree) ... oktest_scale_positions (test_ga.TheoryTest.test_scale_positions) ... oktest_voicing_formats (test_ga.TheoryTest.test_voicing_formats) ... ok
----------------------------------------------------------------------Ran 14 tests in 4.113s
OKCeci a tourné sous Windows 11 avec Python 3.14.2, NumPy 2.4.2 et Pillow 12.1.1. Que d’autres versions de Pillow dessinent les mêmes pixels est à vérifier ; UPDATE=1 réécrit les PNG.
Le charger dans un vrai ComfyUI
Section intitulée « Le charger dans un vrai ComfyUI »Pour voir tout le chemin de chargement, le cours a copié le pack dans un répertoire de base jetable sur un autre disque, et a démarré ComfyUI v0.36.0 sur le CPU, avec le Python de la version portable et les options du server.sh de la leçon 4 :
mkdir -p "$BASE/custom_nodes"cp -r code/comfyui/custom-nodes/ga "$BASE/custom_nodes/ga"PYTHONHASHSEED=0 "$COMFYUI_PYTHON" -s "$COMFYUI_DIR/main.py" --cpu --port 8199 \ --base-directory "$BASE" --database-url sqlite:///:memory: --disable-auto-launchLe journal liste le pack avec son temps d’import :
[INFO] Import times for custom nodes:[INFO] 0.0 seconds: G:\learn-33\comfyui-base\custom_nodes\gaGET /object_info/GAChordDiagram a ensuite renvoyé la classe telle que la voit l’interface, avec notamment "display_name": "GA Chord Diagram", "category": "Guitar Alchemist" et "python_module": "custom_nodes.ga". L’envoi de ga-nodes-cpu.api.json, qui branche les trois nœuds sur SaveImage et PreviewAny, a donné "node_errors": {}, « Prompt executed in 0.06 seconds », c’est-à-dire un prompt exécuté en 0,06 seconde, et ces sorties dans l’historique :
{"5": {"images": [{"filename": "map-E-Phrygian_00001_.png", "subfolder": "ga", "type": "output"}]}, "7": {"text": ["flamenco poster, inspired by the E Phrygian mode (E F G A B C D), minor with a lowered second, dark and tense mood"]}, "2": {"images": [{"filename": "chord-Cmaj7_00001_.png", "subfolder": "ga", "type": "output"}]}, "3": {"text": ["0-0-0-2-3-x"]}}Le PNG chord-Cmaj7 qu’a écrit SaveImage a les mêmes pixels que expected/chord-Cmaj7.png. Trois démarrages de plus ont montré les interrupteurs du chargeur. Avec --disable-all-custom-nodes, le journal dit « Skipping loading of custom nodes », c’est-à-dire que le chargement des nœuds personnalisés est sauté, et /object_info/GAChordDiagram renvoie {}. Avec --disable-all-custom-nodes --whitelist-custom-nodes ga, le pack se charge de nouveau. Avec le dossier renommé en ga.disabled, il ne se charge pas.
Pour le ControlNet de la leçon 6, ga-chord-controlnet.api.json est 06-canny.api.json, où LoadImage et Canny sont remplacés par la carte du manche. Il n’a pas encore été rendu : à vérifier.
Ce que ComfyUI-Manager vérifie, et ce qu’il ne vérifie pas
Section intitulée « Ce que ComfyUI-Manager vérifie, et ce qu’il ne vérifie pas »Dans la v0.36.0, le Manager n’est plus un nœud personnalisé que l’on clone : c’est un paquet pip, figé dans le manager_requirements.txt de ComfyUI sous la forme comfyui_manager==4.2.2, et activé avec --enable-manager. Le cours a comparé les 42 fichiers .py, .json et .md de la wheel 4.2.2 sur PyPI avec le tag 4.2.2 du dépôt, au commit bd4ede22 : tous identiques.
Il installe depuis deux sources : le Comfy Registry, où les éditeurs publient des packs versionnés, et une liste choisie de dépôts git, le « default channel », c’est-à-dire le canal par défaut. Ses garde-fous sont de trois sortes.
D’où vient la requête. La politique de sécurité classe les fonctionnalités par risque, et les autorise selon le security_level (strong, normal, normal-, weak) et selon l’adresse d’écoute du serveur. Le code est court, c’est is_allowed_security_level :
- installer un pack depuis le canal par défaut, mettre à jour, désinstaller et installer un modèle sont de niveau
middle+: autorisés ennormalquand le serveur écoute sur une adresse de boucle locale (loopback) ou quenetwork_modevautpersonal_cloud, refusés sinon ; - changer de version de ComfyUI et « Fix nodepack », c’est-à-dire réparer un pack, sont de niveau
high+: ils demandentweakounormal-sur la boucle locale ; - installer depuis une URL git quelconque, et faire un
pip installde paquets quelconques, ne dépendent plus desecurity_level: il fautallow_git_url_installouallow_pip_installàtruedansconfig.ini, tous deux àFalsepar défaut, et une écoute sur la boucle locale (is_dedicated_install_allowed).
La boucle locale est déterminée par ipaddress.ip_address(args.listen).is_loopback. Une valeur qui n’est pas une adresse unique, comme le 0.0.0.0,:: que donne un --listen sans valeur, lève ValueError et compte comme hors boucle locale : en cas de doute, l’accès est refusé.
Ce dont dépend le pack. Pour une installation groupée, get_risky_level compare les fichiers et les paquets pip demandés avec ceux que déclarent les listes du Manager. Une URL inconnue est de niveau high+, un paquet pip inconnu est bloqué.
Les noms connus comme malveillants. À chaque démarrage, le prestartup du Manager exécute security_check(). Cette fonction lance pip freeze, et cherche un dossier ComfyUI_LLMVISION, les versions pip ultralytics==8.3.41, ultralytics==8.3.42, litellm==1.82.7 et litellm==1.82.8, un paquet nommé AppleBotzz, et quelques fichiers comme %LocalAppData%\rundll64.exe. Si l’un d’eux est présent, elle affiche les étapes pour le supprimer.
Ce qu’il ne vérifie pas compte tout autant :
- le code. Rien, dans le chemin d’installation, ne lit le Python ou le JavaScript d’un pack.
execute_install_scriptlance pip etinstall.pytels quels. Les règles du Comfy Registry interdisentevaletexec, lepip installà l’exécution parsubprocess, et l’obfuscation, et la mise à jour de sécurité de janvier 2025 de Comfy dit « We use AI and static analysis tools to scan potential mechanisms that custom nodes might be a security threat and alert a private channel. », c’est-à-dire que des outils d’IA et d’analyse statique cherchent les mécanismes par lesquels des nœuds personnalisés pourraient être une menace, et alertent un canal privé. Comment fonctionne cette analyse et ce qu’elle détecte n’est pas publié : à vérifier ; - le code des dépendances. Un paquet pip que déclarent les listes est accepté sur son nom. Une nouvelle version de ce nom n’est pas examinée, et c’est exactement ainsi qu’est arrivé le mineur d’ultralytics ;
- les mises à jour. Une mise à jour récupère ce que contient désormais le dépôt du pack ou son entrée dans le registre ;
- les lignes de dépendances elles-mêmes. Le Manager passe chaque ligne à
pip install, donc ungit+https://…ou une URL de wheel dansrequirements.txts’installe comme un nom.
Le même problème en C# et en Java
Section intitulée « Le même problème en C# et en Java »Un développeur .NET connaît la forme de ce risque. Un paquet NuGet peut livrer des fichiers build/<package_id>.props et .targets. Avec PackageReference, la restauration les inscrit dans {projectName}.nuget.g.props et .targets, que MSBuild importe (les props et targets MSBuild dans un paquet). Une cible peut lancer une tâche Exec, donc ajouter un paquet peut exécuter du code au build suivant, sur un poste de développeur ou sur un agent de CI. Java a la même chose avec les plugins Maven : « Maven consists of a core engine which provides basic project-processing capabilities and build-process management, and a host of plugins which are used to execute the actual build tasks. », c’est-à-dire un moteur central qui gère le projet et le build, et une foule de plugins qui exécutent les vraies tâches de build. Un plugin est du code Java, et en ajouter un au POM l’exécute au moment du build.
Python a trois moments où un paquet exécute du code :
| Moment | Python | .NET | Java |
|---|---|---|---|
| Installation | construire une distribution source exécute son backend de build, par exemple setup.py (PEP 517) ; une wheel n’exécute rien à l’installation |
rien : « With PackageReference, install.ps1 and uninstall.ps1 PowerShell scripts are not executed », c’est-à-dire que les scripts PowerShell ne sont pas exécutés (migrer vers PackageReference) | rien au téléchargement |
| Build | — | les .props et .targets du paquet |
les plugins du POM |
| Démarrage | un fichier .pth dans site-packages : « Lines starting with import (followed by space or tab) are executed », c’est-à-dire que les lignes qui commencent par import sont exécutées (site) |
les initialiseurs de module au chargement de l’assembly | les initialiseurs statiques à l’initialisation d’une classe |
| Import | le code de haut niveau du module | — | — |
ComfyUI ajoute ses propres moments par-dessus : install.py après une installation, prestartup_script.py et __init__.py à chaque démarrage, et le JavaScript dans le navigateur. pip install --only-binary :all: (pip install) refuse les distributions source, et supprime donc le moment de l’installation, mais pas les autres. La ligne du .pth n’a rien de théorique, comme le montre la section suivante.
Incidents
Section intitulée « Incidents »ComfyUI_LLMVISION, juin 2024. Un pack présenté comme apportant GPT-4 et Claude 3 dans ComfyUI volait les mots de passe du navigateur, les données de carte bancaire et l’historique de navigation, et les envoyait à un serveur Discord, selon le rapport de vpnMentor du 9 juin 2024, qui attribue la découverte à l’utilisateur de Reddit qui l’a trouvé après des tentatives de connexion à ses comptes. Le code malveillant n’était pas dans le nœud lui-même, mais dans ce qu’il installait : des versions modifiées des bibliothèques openai et anthropic. Le guide de suppression de ComfyUI-Manager, toujours présent dans la version 4.2.2, liste les versions de paquets à supprimer (openai-1.16.3.dist-info, anthropic-0.21.4.dist-info et d’autres), un %LocalAppData%\rundll64.exe, et se termine par « Change all of your passwords, everywhere. », c’est-à-dire changer tous ses mots de passe, partout. Sa détection signale un anthropic installé dont les métadonnées exigent pycrypto, une dépendance que la vraie bibliothèque ne déclare pas.
ultralytics, décembre 2024. Des versions de la bibliothèque YOLO d’Ultralytics sur PyPI lançaient le mineur XMRig. La fiche PYSEC-2024-154 d’OSV liste les versions 8.3.41 à 8.3.46 et dit « This code was injected into the PyPI release artifacts and was not present in the public GitHub repository. », c’est-à-dire que le code a été injecté dans les artefacts publiés sur PyPI et n’était pas dans le dépôt GitHub public. Le point d’entrée était une injection de script dans GitHub Actions par un nom de branche, décrite par William Woodruff. Le ticket qui a donné l’alerte, ultralytics#18027, s’intitule « Discrepancy between what’s in GitHub and what’s been published to PyPI for v8.3.41 », c’est-à-dire un écart entre GitHub et ce qui a été publié sur PyPI. La déclaration de ComfyUI du 5 décembre 2024 explique le lien avec ComfyUI : « Ultralytics is not a core ComfyUI dependency but it is a dependency of some very popular custom nodes like the ComfyUI-Impact-Pack. », c’est-à-dire qu’Ultralytics n’est pas une dépendance du cœur, mais de nœuds personnalisés très populaires comme ComfyUI-Impact-Pack. Elle cite les versions 8.3.41 et 8.3.42, sur Mac et Linux, et dit que le Manager « will also automatically pin the ultralytics version to 8.3.40 », c’est-à-dire qu’il figera automatiquement ultralytics en 8.3.40. Le code du pack lui-même était sain, et c’est exactement ce que la lecture du pack ne peut pas détecter.
Nœuds vulnérables, décembre 2024. Snyk Labs a publié quatre CVE : CVE-2024-21574 dans ComfyUI-Manager, dont le point d’accès /customnode/install ne validait pas le champ pip, si bien qu’une requête pouvait « trigger a pip install on a user controlled package or URL, resulting in remote code execution (RCE) on the server », c’est-à-dire déclencher l’installation d’un paquet ou d’une URL choisis par l’attaquant, et donc une exécution de code à distance sur le serveur (GHSA-7p9r-9x76-5h9r) ; CVE-2024-21575, une traversée de répertoires dans le /upload/temp de ComfyUI-Impact-Pack (GHSA-6mx8-m8xp-f2vc) ; CVE-2024-21576 et CVE-2024-21577, un eval sur des entrées dans ComfyUI-Bmad-Nodes et ComfyUI_AceNodes. Ce sont des erreurs, pas des logiciels malveillants, mais avec un ComfyUI joignable depuis un réseau, le résultat est le même. L’article note qu’une écriture de fichier suffit : « attackers to drop malicious .py files into the ./custom_nodes directory. These files are automatically loaded when the server restarts », c’est-à-dire que des attaquants peuvent déposer des fichiers .py malveillants dans le dossier custom_nodes, chargés automatiquement au redémarrage du serveur.
La configuration de ComfyUI-Manager, CVE-2025-67303. Avant la version 3.38, le Manager gardait config.ini sous user/default/ComfyUI-Manager/, un dossier que l’API /userdata de ComfyUI lit et écrit. L’avis de sécurité, GHSA-95pq-hr8p-f5g7, liste ce qu’un attaquant pouvait faire par ce biais, à commencer par « Lower the security level from “strong” to “weak” », c’est-à-dire abaisser le niveau de sécurité de strong à weak. Dans la v0.36.0, les dossiers dont le nom commence par __ sont des « System Users », des utilisateurs système que get_public_user_directory refuse de servir en HTTP, et le Manager garde ses fichiers dans user/__manager/.
litellm, mars 2026. La fiche PYSEC-2026-2 d’OSV décrit deux versions de la bibliothèque litellm, 1.82.7 et 1.82.8, publiées sur PyPI « After an API Token exposure from an exploited Trivy dependency », c’est-à-dire après la fuite d’un jeton d’API par une dépendance Trivy exploitée, et qui collectaient des clés SSH, des identifiants cloud et des jetons. La version 1.82.8 livrait un fichier litellm_init.pth, et la vérification du Manager explique la conséquence : il « executes malware on ANY Python startup, even without importing litellm », c’est-à-dire qu’il exécute le logiciel malveillant à chaque démarrage de Python, même sans importer litellm. Le Manager a ajouté les deux versions à sa vérification le 26 mars 2026, deux jours après l’avis. litellm n’est pas une dépendance de ComfyUI ; la vérification existe parce que n’importe quel pack peut l’installer.
Auditer un pack avant de l’installer
Section intitulée « Auditer un pack avant de l’installer »Lire un pack est la seule vérification qui regarde son code, et un script peut indiquer par où commencer. audit.py n’utilise que la bibliothèque standard et n’importe jamais le pack. Il analyse chaque fichier Python avec ast, résout les alias d’import (import subprocess as sp, from urllib.request import urlopen), et signale :
- les fichiers qui s’exécutent d’eux-mêmes :
__init__.py,prestartup_script.py,install.py,requirements.txt; - les processus, le
pip installdepuis le code,eval,execetcompile, le décodage base64 ou zlib, pickle,torch.load, les appels réseau et les téléchargements ; - lesquels de ces appels se trouvent hors de toute fonction, et s’exécutent donc dès que le fichier est importé (un appel sous
if __name__ == "__main__":ne compte pas) ; - les lignes de dépendances qui installent depuis une URL, un dépôt git, une wheel ou un autre index ;
- en JavaScript,
fetch,WebSocketouEventSourcesur une URL absolue,XMLHttpRequest,evaletnew Function.
Ses tests l’exécutent sur une fixture qui rassemble ces motifs sous une forme inoffensive, avec des URL en .invalid et des commandes qui ne font qu’un echo, ainsi que sur le pack GA :
$ python code/comfyui/custom-nodes/audit/audit.py code/comfyui/custom-nodes/ga== ga: 27 files, 5 Python, 0 JavaScript, web folder: no autorun 1 file that runs without being askedLe cours a ensuite cloné six packs populaires dans un dossier hors du dépôt, et les a figés sur leurs commits du 16 septembre 2026. ComfyUI-GGUF est le chargeur que mentionne la leçon 8 ; comfyui_controlnet_aux contient les préprocesseurs de pose qui manquaient à la leçon 6 ; les autres sont ComfyUI-Impact-Pack, ComfyUI-VideoHelperSuite, rgthree-comfy et ComfyUI_essentials. Chacun avait entre 1 187 et 4 185 étoiles sur GitHub ce jour-là.
$ python audit.py ComfyUI-GGUF comfyui_controlnet_aux ComfyUI-Impact-Pack ComfyUI-VideoHelperSuite rgthree-comfy ComfyUI_essentials== ComfyUI-GGUF: 16 files, 9 Python, 0 JavaScript, web folder: no autorun 2 file that runs without being asked== comfyui_controlnet_aux: 746 files, 667 Python, 0 JavaScript, web folder: no autorun 2 file that runs without being asked subprocess 19 starts a process (subprocess, os.system, os.popen, os.exec*) eval-exec 3 evaluates code from a string (eval, exec, compile) obfuscation 3 decodes data that may be code (base64, zlib, marshal) pickle 12 unpickles data (pickle, dill, joblib, torch.load with weights_only=False, numpy allow_pickle=True) torch-load 72 torch.load without weights_only: restricted by default since PyTorch 2.6, full pickle before network 8 network call (urllib, requests, httpx, aiohttp client, socket) download 2 downloads files or models (hf_hub_download, snapshot_download, torch.hub, wget)== ComfyUI-Impact-Pack: 105 files, 31 Python, 7 JavaScript, web folder: yes autorun 3 file that runs without being asked subprocess 4 starts a process (subprocess, os.system, os.popen, os.exec*) pip-runtime 2 installs packages at run time (pip install from code) obfuscation 1 decodes data that may be code (base64, zlib, marshal) network 2 network call (urllib, requests, httpx, aiohttp client, socket) download 1 downloads files or models (hf_hub_download, snapshot_download, torch.hub, wget) requirement-url 1 requirement installed from a URL, a git repository, a local wheel or another index== ComfyUI-VideoHelperSuite: 45 files, 13 Python, 3 JavaScript, web folder: yes autorun 2 file that runs without being asked subprocess 20 starts a process (subprocess, os.system, os.popen, os.exec*) js-network 1 browser code calling fetch, WebSocket or EventSource on an absolute URL, or XMLHttpRequest== rgthree-comfy: 259 files, 45 Python, 160 JavaScript, web folder: yes autorun 3 file that runs without being asked subprocess 8 starts a process (subprocess, os.system, os.popen, os.exec*) network 2 network call (urllib, requests, httpx, aiohttp client, socket) import-time 2 one of the calls above sits outside any function: it runs as soon as the file is imported or run js-network 4 browser code calling fetch, WebSocket or EventSource on an absolute URL, or XMLHttpRequest js-eval 4 browser code evaluating a string as code (eval, new Function)== ComfyUI_essentials: 23 files, 11 Python, 2 JavaScript, web folder: yes autorun 2 file that runs without being askedUn compte, c’est là où la lecture commence. Avec --details 40, le script affiche chaque emplacement, et leur lecture donne une image qu’aucun compte ne donne. Rien de ce qui suit ne suggère une mauvaise intention ; ce sont des façons ordinaires de construire un pack, et chacune est une raison de savoir ce que tu installes.
- ComfyUI-GGUF et ComfyUI_essentials n’ont rien d’autre que leur
__init__.pyet unrequirements.txtfait de noms de paquets. - comfyui_controlnet_aux embarque de gros codes de recherche (mmcv, mmseg, MeshGraphormer), et c’est là que se trouvent la plupart de ses résultats :
eval(nms_type)dans la copie de mmcv,os.systemdans des outils d’entraînement de maillages de mains, et 72 appels àtorch.loadsansweights_only. Ses préprocesseurs téléchargent leurs poids depuis Hugging Face à la première utilisation, avechf_hub_downloaddanssrc/custom_controlnet_aux/util.py, à la ligne 331. Le seul appel au niveau du module, untorch.loaddanspidi/model.py, se trouve sousif __name__ == '__main__':et ne compte pas. - ComfyUI-Impact-Pack a un
install.pyqui télécharge le modèle SAMsam_vit_b_01ec64.pthdepuisdl.fbaipublicfiles.com, unrequirements.txtavecgit+https://github.com/facebookresearch/sam2, etadditional_dependencies.py, qui lancepip install onnxruntimequand l’import échoue. Le script ne sait pas suivre les appels d’un fichier à l’autre ; la lecture montre queimpact_onnx.pyl’appelle à l’import, et queONNXDetector.detectimporteimpact_onnxà l’intérieur de la méthode, donc le pip install a lieu la première fois qu’un détecteur ONNX s’exécute. - ComfyUI-VideoHelperSuite démarre
ffmpeg,gifskietyt-dlpavecsubprocess, et c’est sa raison d’être. Son uniqueXMLHttpRequestenvoie vers le propre/upload/imagede ComfyUI, construit avecapi.apiURL. - rgthree-comfy a un
prestartup_script.py, et unrequests.getdanspy/server/utils_info.pyqui demande àhttps://civitai.com/api/v1/model-versions/by-hash/<sha256>les informations d’un modèle quand l’interface les réclame, ce qui envoie à Civitai l’empreinte d’un fichier de modèle local. Ses deux résultats à l’import sont dans__commit__.py, un script de mainteneur que le__init__.pydu pack n’importe pas. Ses résultatsjs-evalsont dans une copie embarquée detree-sitter.js.
Les limites sont claires : un script qui compare des noms ne voit pas une URL construite à partir d’une variable, du code récupéré à l’exécution, un appel passé par getattr, ni rien de ce qui se trouve dans une dépendance. Il n’a rien trouvé chez les utilisateurs d’ultralytics, parce que le problème n’était pas chez eux. Sers-t’en pour choisir quoi lire, jamais comme d’un verdict.
Pickle, safetensors et weights_only
Section intitulée « Pickle, safetensors et weights_only »Un fichier .ckpt, .pt ou .pth enregistré par PyTorch est un zip qui contient un pickle, et la documentation de pickle commence par un avertissement : « The pickle module is not secure. Only unpickle data you trust. », c’est-à-dire que le module pickle n’est pas sûr, et qu’il ne faut désérialiser que des données de confiance. Un pickle ne stocke pas seulement des données ; il stocke la façon de reconstruire des objets, et cela peut être n’importe quel appelable. pickle_demo.py en fabrique un dont la « charge utile » ne fait qu’appeler print, puis le charge de trois façons. Avec le Python et le PyTorch de la version portable :
pickle: 151 bytes; the opcodes name a callable to run: contains builtins and printpickle.loads: -> this line was printed by the unpickler, while loading the data result: {'weights': [0.1, 0.2], 'extra': None}torch 2.13.0+cu130, torch.save: 1705 bytestorch.load(weights_only=True): UnpicklingError: Weights only load failed. This file can still be loaded, to do so you have two options, do those steps only if you trust the source of the checkpoint. WeightsUnpickler error: Unsupported global: GLOBAL print was not an allowed global by default. Please use `torch.serialization.add_safe_globals([print])` or the `torch.serialization.safe_globals([print])` context manager to allowlist this global if you trust this class/function.torch.load(weights_only=False): -> this line was printed by the unpickler, while loading the data result keys: ['extra', 'weights']weights_only=True ne laisse passer que les tenseurs, les conteneurs et les types autorisés explicitement. C’est devenu la valeur par défaut dans PyTorch 2.6, sorti en janvier 2025 : « we have changed the default value for weights_only parameter of torch.load. », c’est-à-dire que la valeur par défaut du paramètre a changé. C’est pourquoi l’audit signale à part un torch.load sans l’argument : avec PyTorch 2.6 ou plus récent, il est restreint ; avec un PyTorch plus ancien, il exécute n’importe quoi. ComfyUI lui-même passe l’argument explicitement : load_torch_file lit les .safetensors avec la bibliothèque safetensors, et tout le reste avec torch.load(ckpt, map_location=device, weights_only=True, **torch_args), et le journal de démarrage dit « Checkpoint files will always be loaded safely. », c’est-à-dire que les checkpoints seront toujours chargés de façon sûre. Un nœud personnalisé qui appelle torch.load(..., weights_only=False), ou directement pickle.load, contourne cette protection.
safetensors évite la question : un en-tête JSON suivi des octets bruts des tenseurs, sans rien à exécuter. La leçon 7 lit un tel en-tête sans charger les poids. Préfère les fichiers .safetensors, et traite tout autre format comme du code. L’ancienne interface du Manager applique la même règle aux modèles : dans legacy/manager_server.py, un fichier de modèle qui n’est pas en .safetensors et ne figure pas dans sa liste par défaut demande le niveau high+.
Isoler ComfyUI
Section intitulée « Isoler ComfyUI »Aucune vérification ne rend un pack sûr, alors limite ce qu’un mauvais pack peut atteindre. Du moins coûteux au plus solide :
- Garde l’écoute sur la boucle locale. ComfyUI n’a pas d’authentification.
--listenvaut127.0.0.1par défaut (cli_args.py) ; quiconque atteint le port peut mettre un workflow en file d’attente, et avec un nœud qui appelleevalsur une entrée, cela veut dire exécuter du code. Les règles du Manager se durcissent d’elles-mêmes quand l’écoute n’est pas locale. - Un environnement par installation. La version portable a son propre
python_embeded, et une installation manuelle devrait avoir son propre venv. Les paquets qu’installe un pack restent alors hors de tes autres projets Python, et supprimer le dossier les supprime. Cela protège tes autres environnements, pas tes fichiers. - Un utilisateur sans droits. Lance ComfyUI avec un utilisateur standard qui ne peut pas écrire hors de ses dossiers, jamais en administrateur. Un vol d’identifiants comme celui de ComfyUI_LLMVISION lit quand même tout ce que cet utilisateur peut lire, donc n’utilise pas ton compte de tous les jours.
- Un conteneur. Un conteneur ne voit que ce que tu y montes. Le cours sur les conteneurs dans WSL couvre les éléments : les limites de mémoire et de CPU, les volumes qui ne montent qu’un dossier de modèles et un dossier de sorties, et le réseau, y compris les conteneurs démarrés sans réseau. L’accès au GPU depuis un conteneur est le sujet de la leçon 12.
- Bloque le trafic sortant. Une fois les modèles et les packs installés, un ComfyUI en marche n’a besoin d’aucun accès à Internet pour les modèles locaux. Une règle de pare-feu qui bloque son trafic sortant arrête une exfiltration comme l’envoi vers Discord de LLMVISION, et casse aussi les préprocesseurs qui téléchargent à la première utilisation : télécharge-les d’abord.
--disable-api-nodes« Also prevents the frontend from communicating with the internet », c’est-à-dire empêche aussi le frontend de communiquer avec Internet (cli_args.py). - Désactive des packs.
--disable-all-custom-nodesavec--whitelist-custom-nodesne laisse passer que les packs que tu nommes, et renommer un dossier en*.disableden retire un. Les deux ont été vérifiés plus haut. - Lis le diff avant de mettre à jour. Fige chaque pack sur un commit, et avant
git pull, lisgit log -p <old>..<new>, en particulier__init__.py,install.py,prestartup_script.py,requirements.txtet le JavaScript. Lance l’audit sur les deux commits et compare. Les instantanés du Manager enregistrent l’état d’une installation avant un « Update All », c’est-à-dire une mise à jour de tout, pour que tu puisses y revenir.
Expérience GA
Section intitulée « Expérience GA »Le pack GA est l’exemple du cours pour tout ce qui précède. Son __init__.py importe deux dictionnaires ; rien ne s’exécute à l’import à part des définitions ; il ne déclare ni dépendance, ni JavaScript, ni script d’installation ; l’audit ne signale que le __init__.py que tout pack possède ; et il a tourné dans un répertoire de base jetable sur le CPU, chargé puis écarté par les trois interrupteurs. Ses tests tournent sans ComfyUI, et ses images sont comparées pixel par pixel.
Sa prochaine étape est un vrai rendu. La carte du manche entre dans le ControlNet de la leçon 6 avec ga-chord-controlnet.api.json, une série d’accords de C à Bm7b5 donne des manches avec les bons doigtés, et GAScalePrompt donne une pochette par mode. La leçon 14, le laboratoire Guitar Alchemist (à venir), exécutera ces séries et mesurera si le ControlNet garde les points là où GA les a placés.
Points clés
Section intitulée « Points clés »- Un pack de nœuds personnalisés est du Python que ComfyUI importe dans son propre processus :
exec_moduleexécute__init__.pyavant que ComfyUI liseNODE_CLASS_MAPPINGS,prestartup_script.pys’exécute encore plus tôt, et le code deWEB_DIRECTORYs’exécute dans chaque navigateur qui ouvre l’interface. - ComfyUI-Manager ajoute, à l’installation, le
pip installderequirements.txtetpython install.py. Ses vérifications décident qui peut installer quoi et depuis où, et signalent une liste de noms connus comme malveillants ; rien ne lit le code. - Un nœud est une simple classe : teste-le en l’important et en appelant sa
FUNCTION, sans ComfyUI ni GPU. - Les incidents sont venus des dépendances et des versions publiées plus que du code des nœuds : des versions modifiées d’
openaiet d’anthropic, une version d’ultralytics absente de son dépôt, un fichier.pthdans litellm. - Un script d’audit te dit où lire ; il ne peut pas te dire qu’un pack est sûr.
- Préfère
.safetensors; touttorch.loadsansweights_only=True, ou sur un PyTorch antérieur à 2.6, peut exécuter du code. - Limite les dégâts : boucle locale uniquement, un environnement, un utilisateur sans droits, un conteneur, pas de trafic sortant, une liste blanche de packs, et un diff lu avant chaque mise à jour.
À toi de jouer
Section intitulée « À toi de jouer »Prends un pack que tu veux vraiment installer et audite-le avant qu’il n’approche ton ComfyUI. Lance le script du cours sur un clone, puis lis, de tes propres yeux, les trois fichiers qu’il désigne en premier : __init__.py, tout ce qui s’appelle install, et ce que sert le WEB_DIRECTORY. Note ce que le pack fait à l’import, ce qu’il télécharge et d’où. Si tu ne sais pas répondre à ces trois questions après lecture, c’est une réponse aussi.
Exercices
Section intitulée « Exercices »- Écris un quatrième nœud GA,
GAVoicingNotes, qui prend un voicing et renvoie uneSTRINGavec les noms des notes depuis le mi grave, commeC3 E3 G3 C4 E4pourx32010. Écris son test d’abord. - Ajoute une ligne
import subprocess; subprocess.run(["echo", "hello"])en haut d’une copie du__init__.pydu pack GA, et lanceaudit.pysur la copie. Quelles règles se déclenchent ? Déplace ensuite l’appel dans une fonction, et relance. - Le
requirements.txtd’un pack contienttorch==2.1.0etnumpy<2. L’audit ne signale rien pour ce fichier à partautorun. Que fait ComfyUI-Manager 4.2.2 de chaque ligne, sur une version portable qui a PyTorch 2.13 et NumPy 2 ? Lisis_blacklisteddansmanager_core.pyetpip_downgrade_blacklistdansprestartup_script.py. - Avec ComfyUI démarré avec
--listen 0.0.0.0etsecurity_level = normaldans leconfig.inidu Manager, un client du réseau local peut-il installer un pack depuis le canal par défaut ? Depuis une URL git, avecallow_git_url_install = true? Réponds à partir deis_allowed_security_levelet deis_dedicated_install_allowed. - Lance
pickle_demo.py --torchavec un Python qui a un PyTorch antérieur à 2.6, ou lis les notes de version : que faittorch.load(buffer)sansweights_onlydans ce cas, et que fait-il sur 2.13 ? - Choisis un pack que tu utilises, fige-le sur son commit actuel, et compare
audit.py --details 20sur ce commit et sur celui d’un an plus tôt. Qu’est-ce qui est apparu ?
Solution 1
NOTE_NAMES = ("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
def voicing_notes(frets, tuning=STANDARD_TUNING): """x32010 -> 'C3 E3 G3 C4 E4': MIDI 60 is C4.""" names = [] for open_note, fret in zip(tuning, frets): if fret is not None: midi = open_note + fret names.append(f"{NOTE_NAMES[midi % 12]}{midi // 12 - 1}") return " ".join(names)Le test : self.assertEqual(theory.voicing_notes(theory.parse_voicing("x32010")), "C3 E3 G3 C4 E4"), et "E2 C3 E3 G3 C4 E4" pour 032010, les valeurs de la leçon 3 du cours de théorie musicale. La classe du nœud a RETURN_TYPES = ("STRING",), FUNCTION = "notes", et une nouvelle entrée dans les deux tables de correspondance. Les noms utilisent des dièses, comme GA ; pour les épeler selon une tonalité, il faut la tonalité en entrée.
Solution 2
En haut du fichier, subprocess se déclenche une fois, et import-time se déclenche une fois pour la même ligne, parce que l’appel se trouve hors de toute fonction. Dans une fonction, seul subprocess reste : l’appel ne s’exécute plus quand ComfyUI importe le pack, mais quand quelque chose appelle la fonction. Les deux méritent d’être lus ; le premier s’exécute à chaque démarrage, que tu utilises ou non les nœuds du pack.
Solution 3
Quand le Manager lance lui-même une ligne pip install, try_install_script interroge d’abord is_blacklisted. torch figure dans pip_downgrade_blacklist, avec torchaudio, torchsde, torchvision, transformers, safetensors et kornia : pour torch==2.1.0, l’opérateur est == et la version 2.13 installée est plus récente, donc la ligne est ignorée et la version CUDA reste en place. numpy n’est pas sur cette liste, donc pip install "numpy<2" s’exécute et rétrograde NumPy pour toute l’installation, ce qui peut casser ComfyUI ou d’autres packs qui ont besoin de NumPy 2. Le Manager appelle ensuite pip_fixer.fix_broken() ; quels paquets cette fonction restaure est à vérifier. Et la version 1.x de NumPy que choisit pip est une version que personne n’a examinée pour cette installation. Avec le résolveur unifié activé, l’installation propre au pack est reportée à un lot exécuté au démarrage, qui a sa propre copie de la règle anti-rétrogradation.
Solution 4
0.0.0.0 n’est pas une adresse de boucle locale, et le network_mode par défaut n’est pas personal_cloud. Un pack du canal par défaut est de niveau middle+ : is_allowed_security_level('middle+') renvoie False quand l’écoute n’est ni locale ni personal_cloud, quel que soit le niveau. L’installation depuis une URL git passe par is_dedicated_install_allowed(True, "0.0.0.0", "public"), qui vaut True and (False or False) : refusée aussi. L’option seule ne l’ouvre pas sur une écoute réseau. Les garde-fous du Manager reposent sur la position réseau ; le /prompt de ComfyUI lui-même n’a aucune vérification de ce genre.
Solution 5
Avant PyTorch 2.6, la valeur par défaut était weights_only=False : torch.load(buffer) affiche la ligne de la charge utile et renvoie les deux clés, comme le dernier cas de la démonstration, et depuis PyTorch 2.4, un avertissement signale que l’argument n’a pas été précisé (notes de version de la 2.4). À partir de la 2.6, la valeur par défaut est True, et sur 2.13, l’appel lève la même UnpicklingError que le weights_only=True explicite. Que le texte de l’avertissement corresponde exactement est à vérifier : le cours n’a exécuté la démonstration qu’avec la 2.13.
Solution 6
La réponse dépend du pack. Regarde d’abord les nouveaux fichiers autorun (un install.py ou un prestartup_script.py qui n’existait pas), les nouvelles lignes requirement-url, les nouveaux résultats import-time, et le JavaScript qui a gagné des appels à fetch. Lis ensuite git log -p pour exactement ces fichiers. Un compte qui augmente n’est pas une mauvaise nouvelle en soi : un pack qui ajoute l’export vidéo ajoute des appels subprocess à ffmpeg.
- ComfyUI à la v0.36.0 :
nodes.py,main.py,server.py,comfy/cli_args.py,comfy/utils.py,folder_paths.py,hook_breaker_ac10a0.py. - Documentation de ComfyUI : présentation des nœuds personnalisés, tutoriel pas à pas, cycle de vie, extensions JavaScript, présentation du Registry, règles, spécification du pyproject.
- ComfyUI-Manager 4.2.2, commit
bd4ede22: politique de sécurité,security_utils.py,manager_security.py,security_check.py,manager_core.py. - Blog de Comfy : déclaration sur l’affaire du mineur de cryptomonnaie d’Ultralytics, 5 décembre 2024 ; mise à jour de sécurité de ComfyUI de janvier 2025.
- Incidents et avis de sécurité : vpnMentor sur ComfyUI_LLMVISION ; PYSEC-2024-154 et ultralytics#18027 ; W. Woodruff, l’injection d’Ultralytics ; Snyk Labs, Hacking ComfyUI through custom nodes ; CVE-2024-21574 ; GHSA-6mx8-m8xp-f2vc ; CVE-2025-67303 et GHSA-95pq-hr8p-f5g7 ; PYSEC-2026-2 et la mise à jour de sécurité de litellm.
- Python et PyTorch :
pickle,siteet les fichiers.pth,ast, PEP 517, pip install,torch.load, notes de version de PyTorch 2.6, safetensors. - .NET et Java : les props et targets MSBuild dans un paquet, plugins Maven.
- Guitar Alchemist à
a826864f:Tuning.cs,Str.cs,Scale.cs,MajorScaleDegree.cs.