Aller au contenu principal

Analyse des modèles

Un fichier de modèle peut exécuter du code lorsqu’il est chargé. Un checkpoint PyTorch est un pickle Python, et le désérialiser peut importer et appeler n’importe quoi. Les couches Keras Lambda, les modèles de conversation GGUF et les opérateurs personnalisés ONNX sont d’autres points d’entrée. fremforge analyse les fichiers de modèles où qu’ils arrivent dans votre organisation et conserve un verdict par fichier. La politique de votre organisation décide du sort d’un fichier signalé par l’analyse.

Aucun modèle n’est jamais chargé ni exécuté pour être analysé. L’analyseur lit la structure du fichier : la liste des imports du pickle, les en-têtes safetensors et GGUF, le graphe ONNX et la configuration Keras.

Où les fichiers de modèles sont trouvés

SurfaceMoment de l’analyseEffet du mode Appliquer
Git LFSÀ l’envoiRefuse l’envoi avec 422
Paquets (génériques, et tout type dont le corps d’envoi est le fichier)À l’envoiRefuse l’envoi avec 422
Registre de conteneurs (ModelPack, KitOps, Docker ai/ et fichiers de modèles dans des images ordinaires)Après le pushRefuse avec 403 un pull qui servirait la couche
Proxy de dépendances (images de conteneurs)Au premier pullRefuse avec 403 les pulls suivants de la couche
Artefacts ActionsÀ la fin de l’exécutionRien. Les artefacts ne reçoivent que des résultats

Un fichier est reconnu comme modèle à ses premiers octets, pas à son nom. Un pickle enregistré sous weights.txt est quand même détecté.

Formats et verdicts

FormatCe qui est vérifié
Pickle, joblib, PyTorch (.pt, .pth, .bin, ancien format et zip), tableaux d’objets NumPyChaque import du pickle par rapport à une liste d’autorisation. Celle-ci correspond à ce qu’accepte torch.load(weights_only=True) de PyTorch, plus les types NumPy dont un state dict a besoin
safetensorsL’en-tête : longueurs, types de données et décalages, et le fait que le fichier ne peut pas aussi être ouvert comme zip ou comme pickle
GGUFLes métadonnées, le modèle de conversation (par rapport à un sous-ensemble sûr de Jinja), et les mêmes vérifications zip et pickle
ONNXLes domaines d’opérateurs personnalisés, et les chemins de données externes qui sortent du répertoire du modèle
Keras (.keras, .h5)Les couches Lambda et les objets personnalisés hors de l’espace de noms Keras

Chaque fichier reçoit un verdict, partagé par toutes les organisations qui détiennent les mêmes octets :

  • Sain (clean) : rien trouvé.
  • Suspect (suspicious) : un import hors de la liste d’autorisation (pickle-unknown-import), une classe d’un framework ML connu (pickle-framework-object, voir Pickles de frameworks), une construction de modèle GGUF hors du sous-ensemble sûr, un opérateur personnalisé ONNX, ou un fichier qui peut aussi être ouvert dans un autre format.
  • Malveillant (malicious) : un import figurant sur la liste de blocage de l’analyseur (tel que os.system ou builtins.eval), ou une couche Keras Lambda.
  • Non analysable (unscannable) : le fichier n’a pas pu être lu jusqu’au bout : zip endommagé, archive 7z, flux tronqué.
  • Format vérifié (format-verified) : un fichier safetensors ou GGUF de plus de 4 Gio dont la vérification de structure a réussi. Voir Au-delà de 4 Gio.

L’analyseur est open source : picklescan pour le parcours du pickle, avec la liste d’autorisation de fremforge par-dessus.

La politique de votre organisation

La politique a trois modes. Définissez-la sous Sécurité › Politiques › Analyse des modèles, ou avec l’API.

ModeCe qui se passe
Observation (par défaut)Chaque fichier est analysé et les fichiers que la politique bloquerait sont listés comme résultats. Rien n’est refusé.
AppliquerComme en observation, et les fichiers ayant un résultat ouvert sont refusés, comme indiqué dans Où les fichiers de modèles sont trouvés.
DésactivéLes fichiers sont toujours analysés et les résultats toujours listés, mais rien n’est refusé ni compté comme « serait bloqué ».

En mode Appliquer, la politique bloque un fichier dont le verdict est Malveillant, Non analysable ou Suspect, sauf si le résultat est rejeté. Un fichier Sain ou Format vérifié passe toujours.

À quoi ressemble le mode Appliquer

Un envoi refusé répond 422 avec le verdict, les codes des résultats et le sha256 du fichier :

{
  "error": "model file rejected",
  "rule": "suspicious",
  "verdict": "suspicious",
  "findings": ["pickle-unknown-import"],
  "sha256": "d24ec017bdcf45a8737b4a9398e6eea64fa37a5821f473041cb3bc03f8489687"
}

Un fichier qui n’a jamais été analysé l’est pendant l’envoi. Cela ajoute au plus quelques secondes pour un fichier jusqu’à 256 Mio. Un fichier safetensors ou GGUF de plus de 256 Mio est vérifié par son en-tête et ses 64 derniers Kio. Tout autre fichier de modèle de plus de 256 Mio est accepté et analysé juste après l’envoi : il apparaît alors comme résultat une minute plus tard au lieu d’être refusé.

Si l’analyseur est injoignable, l’envoi d’un fichier de modèle dans une organisation en mode Appliquer répond 503 avec Retry-After: 30. Les envois d’autres fichiers ne sont pas concernés.

Un pull de conteneur qui servirait une couche bloquée répond 403 DENIED, avec la raison model-malicious, model-suspicious ou model-unscannable et le condensé de la couche. Une couche de modèle que vous venez de pousser est analysée en une minute environ. D’ici là, un pull répond 403 avec la raison model-scan-pending, comme pour une image dont une couche est encore en cours d’analyse antimalware. Les pulls des runners hébergés par fremforge ne sont pas retenus pour une analyse en attente.

git-lfs masque la raison. Pour un envoi refusé, git-lfs affiche Uploading failed due to unsupported Content-Type header(s), et git lfs push --object-id se termine avec le code 0. Un git push normal échoue bien. La raison se trouve dans le corps de la réponse, et le fichier est listé comme résultat sous Analyse des modèles.

Pickles de frameworks

Certains checkpoints stockent des objets Python entiers, pas seulement des poids : un modèle YOLO, un fichier TrainingArguments, un pipeline scikit-learn. En charger un exécute le code de cette classe. L’analyseur les étiquette pickle-framework-object lorsque chaque import hors de la liste d’autorisation des poids provient d’un framework connu : modules PyTorch, Transformers, Ultralytics, scikit-learn, joblib, timm, Lightning, SpeechBrain ou Diffusers.

Ils sont Suspect comme tout autre import inconnu. Pour les laisser passer en mode Appliquer, activez Autoriser les pickles des frameworks ML connus (allow_framework_pickles). Un fichier ayant tout autre résultat reste bloqué.

Rejeter un résultat

Un propriétaire de l’organisation peut rejeter un résultat pour un fichier (un sha256), avec une raison et une date d’expiration : 90 jours par défaut, 365 au plus. Un fichier rejeté passe le mode Appliquer et quitte la liste des résultats. Les rejets sont propres à chaque organisation ; une autre organisation qui détient les mêmes octets n’est pas concernée. Un rejet peut être révoqué à tout moment.

Au-delà de 4 Gio

L’analyseur antimalware, ClamAV, ne peut pas lire plus de 4 Gio d’un même fichier, et fremforge refuse un fichier qu’il ne peut pas analyser. Pour les poids de modèles, qui dépassent souvent 4 Gio, il existe une exception, dans tous les modes : un fichier safetensors ou GGUF dont la vérification de structure réussit est accepté comme Format vérifié. Ces formats ne contiennent que des tenseurs et des métadonnées, et la vérification de structure prouve que le fichier ne contient rien d’autre. Le fichier n’est pas analysé par l’antimalware.

Tout autre fichier de plus de 4 Gio, y compris tout pickle, reste refusé. La limite pour un fichier est de 10 Gio. Les grands modèles sont publiés en fragments de 5 Go au plus, si bien que c’est rarement cette limite qui compte.

Artefacts Actions

À la fin d’une exécution de workflow, les fichiers qu’elle a envoyés avec actions/upload-artifact sont examinés à la recherche de fichiers de modèles, qui sont alors analysés. Un résultat est listé sous Analyse des modèles avec le dépôt, le numéro d’exécution et l’artefact. Les artefacts ne sont jamais refusés ni supprimés : bloquer un envoi ferait échouer votre workflow sans aucun moyen de passer outre.

Inventaire IA

Sécurité › Composants › Inventaire IA liste les modèles d’apprentissage automatique que détient votre organisation et ceux que sa CI télécharge. Il est en lecture seule ; la politique et les rejets restent sur la page d’analyse des modèles.

Fichiers de modèles

Une ligne par fichier de modèle (par sha256) trouvé dans votre Git LFS, vos paquets, vos images de conteneurs, les pulls du proxy de dépendances et les artefacts Actions, avec :

  • son format et sa taille, et son verdict (Analyse en attente tant qu’il attend son analyse) ;
  • sa licence, telle qu’elle est déclarée (voir D’où viennent les licences), avec l’endroit où elle a été déclarée. Un modèle sans licence déclarée affiche Inconnue. fremforge ne devine pas une licence à partir d’un nom de fichier ;
  • ce qu’indique son en-tête : nombre de paramètres, types de données des tenseurs et, pour GGUF, l’architecture et le nom du modèle ;
  • où il apparaît : chaque dépôt, paquet, image ou artefact où il a été vu.

Chaque fichier de modèle figure aussi dans l’inventaire des composants sous la forme pkg:generic/<file name>@<sha256>, si bien qu’une recherche de composant le trouve et que « où est-il utilisé » fonctionne aussi pour les modèles. Un modèle que votre CI télécharge depuis Hugging Face y figure sous la forme pkg:huggingface/<org>/<name>@<revision>, au niveau du dépôt dont le workflow le télécharge.

D’où viennent les licences

fremforge lit une licence uniquement à partir de métadonnées déjà stockées sur fremforge, et ne demande rien à personne d’autre : rien concernant vos modèles ne quitte la plateforme. Dans l’ordre :

  1. Le fichier lui-même : general.license de GGUF, ou une clé de licence dans les métadonnées d’un fichier safetensors.
  2. L’artefact de modèle dont le fichier est une couche, lorsque vous le poussez dans le registre de votre organisation ou le téléchargez via le proxy de dépendances. fremforge lit les petits blobs de métadonnées de l’artefact depuis son propre stockage, jamais les poids et jamais le registre amont :
    Format de paquetLu
    ModelPack (modctl)descriptor.licenses de la configuration
    KitOps (kit)le Kitfile : la license d’une partie de modèle pour cette partie, puis model.license, puis package.license
    Docker Model Runner (docker model, ai/…)general.license de GGUF dans la configuration du modèle, sinon le fichier de licence de l’artefact (application/vnd.docker.ai.license) : Apache-2.0 et MIT sont reconnues par leur texte, toute autre licence par son titre, tel que LLAMA 3.2 COMMUNITY LICENSE AGREEMENT
    chacun d’euxl’annotation de manifeste org.opencontainers.image.licenses

L’API renvoie l’endroit où chaque licence a été déclarée dans licence_from, par exemple ModelPack config descriptor.licenses ; l’inventaire IA l’affiche à côté de la licence. Une licence provenant d’une image que vous avez poussée n’est visible que par votre organisation. Un modèle téléchargé avant le 13 octobre 2026 obtient sa licence depuis l’artefact dans l’heure, si l’artefact est encore stocké.

Modèles que votre CI télécharge

L’analyse quotidienne des workflows lit chaque workflow de la branche par défaut de chaque dépôt, ainsi que les scripts Python et shell qu’un workflow exécute depuis le même dépôt (python scripts/fetch.py, bash ci/prepare.sh). Elle trouve :

OutilDétecté sous la forme
Hugging Facefrom_pretrained("org/name"), hf_hub_download(repo_id=…), snapshot_download(…), huggingface-cli download, hf download
Ollamaollama pull name:tag
Dockerdocker model pull ref
KitOpskit pull ref
ModelPackmodctl pull ref

Une référence est épinglée lorsqu’elle ne peut pas changer sous votre build : un modèle Hugging Face avec un sha de commit complet de 40 caractères comme revision (revision="<sha>" ou --revision <sha>), et un modèle Ollama, Docker, KitOps ou ModelPack avec un condensé @sha256:. Une branche ou une étiquette telle que main ou v1.0 n’est pas épinglée. Chaque référence non épinglée est un résultat, comme une action épinglée à une étiquette plutôt qu’à un commit.

Un nom de modèle qui n’est pas écrit dans le fichier, tel que from_pretrained(model_name), est listé comme Nom défini à l’exécution. Un HF_ENDPOINT défini dans le même fichier est affiché, car le téléchargement va alors vers ce point de terminaison au lieu de huggingface.co. Une référence que vous supprimez disparaît dans les trois jours.

Non détectés : oras pull (un artefact ORAS n’est un modèle que par son type de média, que la commande ne montre pas ; téléchargé via le proxy de dépendances, il est classé d’après son manifeste), et le code Python hors des scripts qu’exécute un workflow.

La licence d’un modèle Hugging Face que votre CI télécharge par son nom est affichée comme Inconnue : fremforge ne contacte ni Hugging Face ni personne d’autre pour la chercher. Un modèle que votre CI télécharge comme artefact OCI via le proxy de dépendances obtient sa licence depuis l’artefact (voir D’où viennent les licences).

ML-BOM

Télécharger le ML-BOM (CycloneDX) sur la page, ou GET /ai/ml-bom, renvoie l’inventaire sous la forme d’un document CycloneDX 1.6. Chaque fichier de modèle est un composant de type machine-learning-model avec son sha256, sa licence déclarée (l’endroit où elle a été déclarée est la propriété fremforge:model:licence_from), son architecture dans modelCard.modelParameters.modelArchitecture lorsque l’en-tête en nomme une, et son format, son verdict, ses surfaces, sa taille, son nombre de paramètres et ses types de données sous forme de propriétés fremforge:model:*. Seuls les faits lus dans le fichier sont indiqués.

Les SBOM que votre CI envoie ne sont pas modifiés : ce sont vos fichiers. Le ML-BOM est un document distinct.

Rapport de conformité

Le rapport de conformité comporte une section Inventaire IA : le nombre de fichiers de modèles, les fichiers que votre politique bloque ou bloquerait, les fichiers ayant un résultat rejeté, votre mode d’analyse des modèles, les téléchargements de modèles en CI sans révision fixe, et les fichiers par licence déclarée. Son CSV liste chaque fichier de modèle. La section couvre toujours l’ensemble de l’organisation, y compris dans un rapport portant sur un seul dépôt.

Journal d’audit

ÉvénementQuand
security_policy.model_scan.updatedLe mode ou le paramètre des pickles de frameworks a changé
security_policy.model_scan.dismissedUn résultat a été rejeté, ou un rejet renouvelé
security_policy.model_scan.dismissal_revokedUn rejet a été révoqué
registry.pull.blocked avec gate: modelUn pull a été refusé par la politique des modèles

Un envoi de paquet ou un pull de conteneur refusé figure aussi dans le journal d’accès au registre, avec la raison model_policy:<rule>. Le journal d’accès ne couvre pas Git LFS.

API

AppelPortée
GET /_app/api/v1/orgs/{org}/security/model-policypolicy:read
PUT /_app/api/v1/orgs/{org}/security/model-policypolicy:write
GET /_app/api/v1/orgs/{org}/security/model-findings?status=open|dismissed|allfindings:read
POST /_app/api/v1/orgs/{org}/security/model-findings/{sha256}/dismisspolicy:write
DELETE /_app/api/v1/orgs/{org}/security/model-findings/{sha256}/dismissalpolicy:write
GET /_app/api/v1/orgs/{org}/ai/models?verdict=&format=&licence=&surface=components:read
GET /_app/api/v1/orgs/{org}/ai/models/{sha256}components:read
GET /_app/api/v1/orgs/{org}/ai/ml-bomcomponents:read
GET /_app/api/v1/orgs/{org}/ai/references?tool=&unpinned=true&repository=&model=components:read

PUT remplace toute la politique : un champ que vous omettez prend sa valeur par défaut.

curl -X PUT \
  -H "Authorization: Bearer ${FREMFORGE_PAT}" \
  -H "Content-Type: application/json" \
  -d '{"mode":"enforce","allow_framework_pickles":false}' \
  https://frem.sh/_app/api/v1/orgs/acme/security/model-policy

Le rejet nécessite policy:write, et non findings:write, car un fichier rejeté passe le mode Appliquer. Le jeton de runner d’un job CI ne le porte jamais.

curl -X POST \
  -H "Authorization: Bearer ${FREMFORGE_PAT}" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Our own YOLO checkpoint, built in CI","expires_at":"2027-01-31"}' \
  https://frem.sh/_app/api/v1/orgs/acme/security/model-findings/<sha256>/dismiss

Consultez la référence de l’API pour la forme des réponses.

Ce qui n’est pas couvert

  • Les fichiers joints aux tickets, aux pull requests et aux versions, et les envois de paquets dont le corps contient plus que le fichier (PyPI, NuGet et autres envois multipart). Ceux-ci sont analysés par l’antimalware comme auparavant.
  • Les modèles présents dans les objets Git ordinaires de vos dépôts. Les gros fichiers de modèles ont leur place dans Git LFS.
  • Le chargement ou l’exécution d’un modèle, et la détection d’une porte dérobée dans ses poids. Aucun analyseur ne peut distinguer un poids empoisonné d’un poids normal.