{"id":"CVE-2026-116691","kind":"vulnerability","aliases":["CVE-2026-116691","GHSA-j7qq-vr8c-xvw3"],"related":[],"title":"MLflow CrewAI autologging records LLM provider API keys in logged trace spans","description":"### Summary\n\n`mlflow.crewai.autolog()` leaks CrewAI LLM provider API keys into MLflow trace telemetry.\n\nWhen a CrewAI `Agent` is instrumented with MLflow autologging, MLflow serializes the agent's `__dict__` and converts its fields to strings. The `llm` field contains the CrewAI LLM object, whose string representation includes its `api_key` in plaintext.\n\nAs a result, an application that calls `mlflow.crewai.autolog()` can unintentionally send its LLM provider API key to the MLflow trace backend or configured OTLP destination.\n\nThe application does not need to explicitly log the credential or place it in a span attribute. The credential is extracted automatically by MLflow's instrumentation.\n\nThis was verified on **MLflow 3.16.0** with **CrewAI 1.15.18** using MLflow's own OTLP exporter. The fake credential appeared in **5 attribute locations across 2 spans**.\n\n### Details\n\nThe primary issue is in:\n\n`mlflow/crewai/autolog.py::_get_agent_attributes()`\n\nThe function walks the CrewAI `Agent.__dict__` and converts agent fields to strings. `tools` and `embedder` receive special handling, but credential-bearing objects such as the following are not sanitized:\n\n* `llm`\n* `function_calling_llm`\n* `agent_executor`\n* `crew`\n\nFor example, `agent.llm` is a CrewAI `LLM` object. In CrewAI 1.15.18, the underlying LLM model contains `api_key` as a normal field. Its string representation therefore contains the credential:\n\n```text\nllm_type='openai' model='gpt-4.1-mini' ... api_key='sk-proj-...' ...\n```\n\nMLflow subsequently stores this string as a span attribute.\n\nThere is an important indication that this is an unintended disclosure: MLflow's dedicated LLM attribute extraction already explicitly excludes `api_key` in `_get_llm_attributes()`. The LLM span therefore does not expose the credential, while the broader Agent `__dict__` serialization bypasses that protection.\n\nThere is also a second, independent serialization path in:\n\n`mlflow/crewai/autolog.py::_construct_full_inputs()`\n\nThis code uses:\n\n```python\nv.__dict__ if hasattr(v, \"__dict__\") else v\n```\n\non call arguments. During CrewAI execution, the `Agent` object is passed as an argument, causing its `__dict__` to be expanded and the credential-bearing LLM object to be included in `mlflow.spanInputs`.\n\nTherefore, there are two independent leakage paths:\n\n1. **Agent span attributes** through `_get_agent_attributes()`\n2. **Span inputs** through `_construct_full_inputs()`\n\nFixing only the Agent attribute serialization would leave the `mlflow.spanInputs` path vulnerable.\n\nA safer approach would be to avoid recursively serializing arbitrary object `__dict__` values. Agent telemetry should use an explicit allowlist of non-sensitive scalar fields, and credential-bearing objects such as `llm`, `function_calling_llm`, `agent_executor`, and `crew` should either be reduced to safe metadata or omitted.\n\nSimilarly, `_construct_full_inputs()` should not automatically expand arbitrary objects through `__dict__`, or the resulting values should pass through the same sanitization mechanism before being attached to a span.\n\n### PoC\n\nEnvironment:\n\n```text\nMLflow: 3.16.0\nCrewAI: 1.15.18\nPython: 3.12.3\nopentelemetry-exporter-otlp-proto-http: 1.42.1\n```\n\n#### 1. Direct serialization proof\n\nThe following demonstrates the vulnerability without starting a Crew, making a network request, or requiring an MLflow server:\n\n```python\nimport importlib\nfrom crewai import LLM, Agent\n\nautolog = importlib.import_module(\"mlflow.crewai.autolog\")\n\nllm = LLM(\n    model=\"gpt-4.1-mini\",\n    api_key=\"sk-proj-EXAMPLE1234567890\",\n)\n\nagent = Agent(\n    role=\"r\",\n    goal=\"g\",\n    backstory=\"b\",\n    llm=llm,\n)\n\nattrs = autolog._get_agent_attributes(agent)\n\nassert \"sk-proj-EXAMPLE1234567890\" in attrs[\"llm\"]\n\nprint(attrs[\"llm\"])\n```\n\nThe output contains the API key in plaintext, for example:\n\n```text\nllm_type='openai' model='gpt-4.1-mini' ... api_key='sk-proj-EXAMPLE1234567890' ...\n```\n\nNo provider request is required.\n\n#### 2. End-to-end trace disclosure\n\nThe issue can also be reproduced through MLflow's actual OTLP exporter.\n\nConfigure a local OTLP HTTP receiver and set:\n\n```bash\nexport OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4318/v1/traces\nexport OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf\nexport CREWAI_DISABLE_TELEMETRY=true\n```\n\nThen run:\n\n```python\nimport mlflow\n\nfrom crewai import Agent, Crew, LLM, Task\n\nmlflow.crewai.autolog()\n\nllm = LLM(\n    model=\"gpt-4.1-mini\",\n    api_key=\"sk-proj-EXAMPLE1234567890\",\n)\n\nagent = Agent(\n    role=\"Support Responder\",\n    goal=\"Answer support questions\",\n    backstory=\"A support agent\",\n    llm=llm,\n)\n\ntask = Task(\n    description=\"Answer the user's question\",\n    expected_output=\"A response\",\n    agent=agent,\n)\n\ncrew = Crew(\n    agents=[agent],\n    tasks=[task],\n)\n\ncrew.kickoff()\n```\n\nA local OTLP receiver can decode the `ExportTraceServiceRequest` sent by MLflow.\n\nThe provider request is expected to fail because the credential is intentionally fake. This does not affect the vulnerability: the sensitive value is added to the MLflow span when the instrumentation starts, before the provider call succeeds or fails.\n\nIn the tested environment, the fake API key appeared in the exported telemetry in **5 locations across 2 spans**, including:\n\n```text\nSupport Responder\n  attributes.llm\n  attributes.function_calling_llm\n  attributes.agent_executor\n  attributes.mlflow.spanInputs\n\nTask.execute_sync\n  attributes.mlflow.spanInputs\n```\n\nThe captured value is produced by MLflow's actual telemetry/export path rather than being manually inserted into a span.\n\nAll credentials used in the reproduction are locally generated fake values. No real API credential is required.\n\n### Impact\n\n**Type:** Sensitive Information Disclosure / Credential Disclosure\n**CWE:** CWE-532 — Insertion of Sensitive Information into Log File\n\nAny application using `mlflow.crewai.autolog()` with a CrewAI `LLM` configured with an API key can unintentionally disclose that credential through MLflow traces.\n\nThe credential is written to trace telemetry even though the application does not explicitly log it.\n\nAnyone with access to the MLflow trace storage or configured OTLP destination may therefore obtain the provider credential. Depending on the deployment, this can include MLflow users, observability operators, or third-party telemetry providers.\n\nThe leaked credential can then potentially be used against the corresponding LLM provider account, including for unauthorized API usage and associated costs.\n\nThe issue is particularly significant because the credential crosses a trust boundary: a provider API key intended for use by the application is automatically copied into observability telemetry where a broader set of users or systems may have access.\n\nThe vulnerability does not require the developer to intentionally log the secret. The only relevant prerequisite is enabling the CrewAI autologging integration.\n\n### Patches\n\nFixed in MLflow 3.17.0 by #25845. `mlflow.crewai.autolog()` now reduces CrewAI LLM objects to their model name and drops credential-like keys before they are written to span attributes or `mlflow.spanInputs`.\n\n### Remediation\n\n- Upgrade to `mlflow>=3.17.0`.\n- Upgrading stops new leakage but does not remove credentials already written. Rotate any LLM provider key that a CrewAI `LLM` object held (for example one passed as `LLM(api_key=...)`) while `mlflow.crewai.autolog()` was enabled on MLflow 2.19.0 through 3.16.x.\n- Delete or redact the affected traces in the MLflow tracking backend and in any OTLP destination they were exported to. The key appears in Agent and Crew span attributes and in `mlflow.spanInputs`.\n\n### Workarounds\n\nIf you cannot upgrade yet, disable CrewAI autologging with `mlflow.crewai.autolog(disable=True)`.","summary":"When an application uses mlflow.crewai.autolog(), MLflow records CrewAI Agent objects, and the Agent's llm field is converted to a string that includes the provider API key. Anyone who can read the MLflow trace backend or a configured OTLP destination can obtain those plaintext provider credentials.","severity":"medium","cvss":6.5,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"mlflow","fixed":["3.17.0"],"affected":">= 2.19.0, < 3.17.0","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["ai-agents","ai-tooling","crewai","cwe-200","cwe-312","cwe-532","llm-apps","mlflow","python"],"not_affected_if":["mlflow-3.17.0-or-later","no-crewai-autolog"],"how_to_check":"- Run `pip show mlflow` and check whether the installed version is below `3.17.0`\n- Search application code for `mlflow.crewai.autolog` to confirm CrewAI autologging is enabled\n- Review stored traces or the OTLP destination for spanInputs containing provider API keys","fix":"- Upgrade MLflow to `3.17.0` or later\n- Rotate any provider API keys that were sent to MLflow traces or an OTLP destination while on an affected version\n- Restrict read access to the MLflow trace backend and OTLP destination until traces are reviewed","status":"open","published_at":"2026-10-27T20:17:14+00:00","first_seen_at":"2026-10-27T20:33:27+00:00","triaged_at":"2026-10-27T20:35:31+00:00","updated_at":"2026-10-27T21:29:43+00:00","category":"ai-tooling","confidence":0.7,"triage":{"at":"2026-10-27T20:35:31Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade MLflow to `3.17.0` or later\n- Rotate any provider API keys that were sent to MLflow traces or an OTLP destination while on an affected version\n- Restrict read access to the MLflow trace backend and OTLP destination until traces are reviewed","reach":"unknown","title":"MLflow: CrewAI autologging stores provider API keys in traces","impact":"medium","summary":"When an application uses mlflow.crewai.autolog(), MLflow records CrewAI Agent objects, and the Agent's llm field is converted to a string that includes the provider API key. Anyone who can read the MLflow trace backend or a configured OTLP destination can obtain those plaintext provider credentials.","category":"ai-tooling","nicknames":[],"who_cares":["mlflow","crewai","python","ai-agents","llm-apps"],"confidence":0.7,"needs_auth":"unknown","worst_case":"Plaintext provider API keys readable by anyone who can view traces","analyst_note":"","how_to_check":"- Run `pip show mlflow` and check whether the installed version is below `3.17.0`\n- Search application code for `mlflow.crewai.autolog` to confirm CrewAI autologging is enabled\n- Review stored traces or the OTLP destination for spanInputs containing provider API keys","needs_analyst":false,"not_affected_if":["mlflow-3.17.0-or-later","no-crewai-autolog"],"who_is_affected":"Teams that run MLflow between versions `2.19.0` and `3.17.0` and call mlflow.crewai.autolog() with CrewAI Agents that hold a provider API key in the llm field. Applications without CrewAI autologging, or running MLflow 3.17.0 or later, are not affected.","user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000668,"input_tokens":3454,"output_tokens":645,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-116691':15A,23A '-200':33B '-2026':14A,22A '-312':35B '-4.1':298C,581C,629C '-532':37B '1':421C,543C '1.15.18':198C,273C,533C '1.42.1':542C '2':214C,429C,642C '3.12.3':535C '3.16.0':195C,531C '5':210C 'across':213C 'agent':27B,51B,55B,104C,113C,224C,234C,260C,352C,389C,422C,427C,439C,461C,485C,572C,589C,590C,601C 'agent.__dict__':231C 'agent.llm':265C 'ai':26B,29B 'ai-ag':25B 'ai-tool':28B 'allowlist':467C 'alreadi':331C 'also':360C 'anyon':70B 'api':7A,68B,95C,135C,153C,279C,300C,334C,583C,617C,631C 'app':40B 'appear':208C 'applic':45B,143C,165C 'approach':450C 'arbitrari':457C,506C 'argument':384C,395C 'assert':602C 'attach':523C 'attr':599C,608C,611C 'attribut':180C,211C,225C,313C,329C,339C,424C,428C,440C 'autolog':3A,109C,573C 'autolog._get_agent_attributes':600C 'automat':185C,504C 'avoid':454C 'b':596C 'backend':77B,159C 'backstori':595C 'bear':247C,406C,477C 'broader':351C 'bypass':355C 'call':145C,258C,383C,483C 'caus':396C 'code':372C 'configur':80B,161C 'construct':368C,433C,499C 'contain':125C,278C,289C,615C 'convert':60B,117C,233C 'credenti':88B,173C,182C,207C,246C,291C,348C,405C,476C 'credential-bear':245C,404C,475C 'crew':262C,488C,555C 'crewai':2A,31B,50B,92C,103C,127C,197C,230C,268C,272C,386C,532C,569C 'cve':13A,21A 'cwe':32B,34B,36B 'dedic':327C 'demonstr':549C 'destin':82B,163C 'detail':216C 'dict':115C,353C,379C,398C,459C,509C 'direct':544C 'disclosur':324C 'either':490C 'els':380C 'embedd':240C 'en':646C 'end':644C 'end-to-en':643C 'environ':528C 'exampl':264C,622C 'example1234567890':588C,606C,636C 'exclud':333C 'execut':387C 'executor':261C,486C 'expand':401C,505C 'explicit':170C,332C,466C 'export':204C,538C 'expos':346C 'extract':184C,330C 'fake':206C 'field':58B,119C,124C,235C,284C,473C 'fix':436C 'follow':252C,548C 'full':369C,434C,500C 'function':227C,257C,482C 'g':594C 'get':223C,337C,426C 'ghsa':17A 'ghsa-j7qq-vr8c-xvw3':16A 'goal':593C 'gpt':297C,580C,628C 'handl':243C 'hasattr':377C 'http':541C 'import':317C,566C,570C 'importlib':567C 'importlib.import':574C 'includ':65B,133C,411C 'independ':363C,418C 'indic':318C 'input':370C,431C,435C,501C 'instrument':106C,189C 'issu':219C 'j7qq':18A 'key':8A,69B,96C,136C,154C,280C,301C,335C,584C,618C,632C 'leak':91C 'leakag':419C 'leav':443C 'llm':5A,39B,57B,93C,123C,128C,151C,256C,259C,269C,276C,293C,328C,338C,341C,407C,481C,484C,571C,577C,578C,597C,598C,609C,612C,624C 'llm-app':38B 'locat':212C 'log':10A,171C 'make':556C 'mechan':520C 'metadata':495C 'mini':299C,582C,630C 'mlflow':1A,24A,41B,48B,75B,98C,108C,110C,157C,187C,194C,200C,305C,325C,530C,563C 'mlflow.crewai.autolog':47B,90C,146C,576C 'mlflow.spaninputs':413C,445C 'mlflow/crewai/autolog.py':222C,367C 'model':277C,296C,579C,627C 'modul':575C 'need':168C 'network':558C 'non':470C 'non-sensit':469C 'normal':283C 'object':52B,129C,248C,270C,390C,408C,458C,478C,507C 'obtain':84B 'omit':497C 'openai':295C,626C 'opentelemetri':537C 'opentelemetry-exporter-otlp-proto-http':536C 'otlp':81B,162C,203C,539C 'output':614C 'pass':392C,515C 'path':365C,420C,446C 'place':175C 'plaintext':86B,138C,620C 'poc':527C 'primari':218C 'print':610C 'proj':304C,587C,605C,635C 'proof':546C 'protect':357C 'proto':540C 'provid':6A,67B,87B,94C,152C,638C 'python':42B,374C,534C,565C 'r':592C 'read':73B 'receiv':241C 'record':4A,49B 'recurs':455C 'reduc':492C 'represent':132C,287C 'request':559C,639C 'requir':561C,641C 'result':141C,512C 'role':591C 'safe':494C 'safer':449C 'sanit':255C,519C 'scalar':472C 'second':362C 'send':149C 'sensit':471C 'serial':111C,354C,364C,441C,456C,545C 'server':564C 'similar':498C 'sk':303C,586C,604C,634C 'sk-proj':302C 'sk-proj-example1234567890':585C,603C,633C 'span':12A,179C,215C,312C,342C,423C,430C,526C 'special':242C 'start':553C 'store':307C 'string':63B,121C,131C,237C,286C,309C 'subsequ':306C 'summari':89C 'telemetri':100C,462C 'text':292C,529C,623C 'therefor':288C,343C,414C 'tool':30B,238C 'trace':11A,76B,99C,158C 'two':417C 'type':294C,625C 'under':275C 'unintend':323C 'unintent':148C 'use':46B,199C,373C,464C 'v':378C,381C 'v.__dict__':375C 'valu':460C,513C 'verifi':192C 'vr8c':19A 'vulner':447C,551C 'walk':228C 'whose':130C 'without':552C 'would':442C,451C 'xvw3':20A","nicknames":[],"mitigations":[],"who_affected":"Teams that run MLflow between versions `2.19.0` and `3.17.0` and call mlflow.crewai.autolog() with CrewAI Agents that hold a provider API key in the llm field. Applications without CrewAI autologging, or running MLflow 3.17.0 or later, are not affected.","tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-27T20:17:14+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"nvd","external_id":"CVE-2026-116691","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-116691","severity":null,"published_at":"2026-10-27T20:17:14+00:00","modified_at":"2026-10-27T20:17:14+00:00","first_seen_at":"2026-10-27T20:33:27+00:00","fetched_at":"2026-10-27T20:33:27+00:00","note":null},{"source":"ghsa","external_id":"GHSA-j7qq-vr8c-xvw3","url":"https://github.com/advisories/GHSA-j7qq-vr8c-xvw3","severity":"medium","published_at":"2026-10-27T21:26:39+00:00","modified_at":"2026-10-27T21:26:40+00:00","first_seen_at":"2026-10-27T21:29:43+00:00","fetched_at":"2026-10-27T21:29:43+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}