MoyenneCVE-2026-116691
MLflow CrewAI autologging enregistre les clés API du fournisseur de LLM dans les spans de trace enregistrés
Exploitable dans des cas précis ou avec un impact limité : à corriger à la prochaine mise à jour.
- Sévérité
- Moyenne6.5 / 10
- Publiée le
- 27. Oktober 2026
- Mise à jour
- 27. Oktober 2026
L'avis
Traduction automatique de l'avis officiel. En cas de doute, l'avis d'origine, plus bas, fait foi.
Résumé
mlflow.crewai.autolog() communique les clés API du fournisseur de LLM CrewAI dans la télémétrie de trace MLflow.
Lorsqu'un Agent CrewAI est instrumenté avec l'autologging MLflow, MLflow sérialise le __dict__ de l'agent et convertit ses champs en chaînes de caractères. Le champ llm contient l'objet CrewAI LLM, dont la représentation chaîne inclut sa api_key en clair.
En conséquence, une application qui appelle mlflow.crewai.autolog() peut envoyer involontairement sa clé API du fournisseur de LLM vers le backend de trace MLflow ou la destination OTLP configurée.
L'application n'a pas besoin d'enregistrer explicitement le credential ou de le placer dans un attribut de span. Le credential est extrait automatiquement par l'instrumentation MLflow.
Cela a été vérifié sur MLflow 3.16.0 avec CrewAI 1.15.18 en utilisant l'exportateur OTLP de MLflow. Le credential factice est apparu dans 5 emplacements d'attributs sur 2 spans.
Détails
Le problème principal se trouve dans :
mlflow/crewai/autolog.py::_get_agent_attributes()
La fonction parcourt le Agent.__dict__ CrewAI et convertit les champs de l'agent en chaînes. tools et embedder bénéficient d'un traitement particulier, mais les objets contenant des credentials tels que les suivants ne sont pas nettoyés :
llmfunction_calling_llmagent_executorcrew
Par exemple, agent.llm est un objet CrewAI LLM. Dans CrewAI 1.15.18, le modèle LLM sous-jacent contient api_key comme champ normal. Sa représentation chaîne contient donc le credential :
llm_type='openai' model='gpt-4.1-mini' ... api_key='sk-proj-...' ...
MLflow stocke ensuite cette chaîne comme attribut de span.
Il y a une indication importante que c'est une divulgation non intentionnelle : l'extraction d'attribut LLM dédiée de MLflow exclut déjà explicitement api_key dans _get_llm_attributes(). Le span LLM ne révèle donc pas le credential, tandis que la sérialisation __dict__ Agent plus large contourne cette protection.
Il y a également un deuxième chemin de sérialisation indépendant dans :
mlflow/crewai/autolog.py::_construct_full_inputs()
Ce code utilise :
v.__dict__ if hasattr(v, "__dict__") else v
sur les arguments d'appel. Lors de l'exécution CrewAI, l'objet Agent est passé en argument, ce qui entraîne l'expansion de son __dict__ et l'inclusion de l'objet LLM contenant le credential dans mlflow.spanInputs.
Par conséquent, il y a deux chemins de fuite indépendants :
- Attributs de span Agent via
_get_agent_attributes() - Entrées de span via
_construct_full_inputs()
La correction de seulement la sérialisation des attributs Agent laisserait le chemin mlflow.spanInputs vulnérable.
Une approche plus sûre serait d'éviter la sérialisation récursive des valeurs __dict__ d'objets arbitraires. La télémétrie Agent devrait utiliser une liste explicite de champs scalaires non sensibles, et les objets contenant des credentials tels que llm, function_calling_llm, agent_executor et crew devraient soit être réduits à des métadonnées soit être omis.
De même, _construct_full_inputs() ne devrait pas automatiquement étendre les objets arbitraires via __dict__, ou les valeurs résulantes devraient passer par le même mécanisme de nettoyage avant d'être attachées à un span.
Preuve de concept
Environnement :
MLflow: 3.16.0
CrewAI: 1.15.18
Python: 3.12.3
opentelemetry-exporter-otlp-proto-http: 1.42.1
1. Preuve de sérialisation directe
La démonstration suivante montre la vulnérabilité sans démarrer un Crew, sans faire une requête réseau et sans nécessiter un serveur MLflow :
import importlib
from crewai import LLM, Agent
autolog = importlib.import_module("mlflow.crewai.autolog")
llm = LLM(
model="gpt-4.1-mini",
api_key="sk-proj-EXAMPLE1234567890",
)
agent = Agent(
role="r",
goal="g",
backstory="b",
llm=llm,
)
attrs = autolog._get_agent_attributes(agent)
assert "sk-proj-EXAMPLE1234567890" in attrs["llm"]
print(attrs["llm"])
La sortie contient la clé API en clair, par exemple :
llm_type='openai' model='gpt-4.1-mini' ... api_key='sk-proj-EXAMPLE1234567890' ...
Aucune requête fournisseur n'est nécessaire.
2. Divulgation de trace de bout en bout
La vulnérabilité peut également être reproduite avec l'exportateur OTLP de MLflow.
Configurez un récepteur OTLP HTTP local et définissez :
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4318/v1/traces
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
export CREWAI_DISABLE_TELEMETRY=true
Puis exécutez :
import mlflow
from crewai import Agent, Crew, LLM, Task
mlflow.crewai.autolog()
llm = LLM(
model="gpt-4.1-mini",
api_key="sk-proj-EXAMPLE1234567890",
)
agent = Agent(
role="Support Responder",
goal="Answer support questions",
backstory="A support agent",
llm=llm,
)
task = Task(
description="Answer the user's question",
expected_output="A response",
agent=agent,
)
crew = Crew(
agents=[agent],
tasks=[task],
)
crew.kickoff()
Un récepteur OTLP local peut décoder le ExportTraceServiceRequest envoyé par MLflow.
La requête fournisseur est censée échouer car le credential est intentionnellement factice. Cela n'affecte pas la vulnérabilité : la valeur sensible est ajoutée au span MLflow lorsque l'instrumentation commence, avant que la requête fournisseur ne réussisse ou n'échoue.
Dans l'environnement testé, la clé API factice est apparue dans la télémétrie exportée à 5 emplacements sur 2 spans, y compris :
Support Responder
attributes.llm
attributes.function_calling_llm
attributes.agent_executor
attributes.mlflow.spanInputs
Task.execute_sync
attributes.mlflow.spanInputs
La valeur capturée est produite par le chemin de télémétrie/export réel de MLflow et non insérée manuellement dans un span.
Tous les credentials utilisés dans la reproduction sont des valeurs factices générées localement. Aucun credential d'API réel n'est nécessaire.
Impact
Type : Divulgation d'information sensible / Divulgation de credential CWE : CWE-532 — Insertion of Sensitive Information into Log File
La suite de l'avis n'est pas traduite : elle figure dans l'avis d'origine, plus bas.
Composants touchés
| Composant | Versions touchées | Corrigé en |
|---|---|---|
| mlflowPython (pip) | >= 2.19.0, < 3.17.0 | 3.17.0 |
Vous utilisez une version touchée ? Passez à la version corrigée, puis redéployez.