
Blog
Zafran Team
CISA's BOD 26-04 Signals the End of Patch-Everything
June 15, 2026
Zafran Labs discovered four vulnerabilities, two of them critical and two requiring no authentication, that let attackers siphon private chat history, preview other tenants' documents, and reach internal APIs.

Zafran Security uncovered four vulnerabilities in Dify, the open-source AI platform powering over one million applications and used by enterprises including Volvo, Maersk, Panasonic, and Thermo Fisher. Two were critical severity, two required no authentication, and three carried cross-tenant impact on Dify's multi-tenant cloud service, allowing one customer's data to be exposed to another.
With over 140,000 GitHub stars, more than 10 million Docker pulls of its API image, and deployments across 60+ industries, Dify is one of the most widely adopted LLMOps platforms in production today. During our investigation, we identified tens of thousands of internet-facing Dify instances.
The vulnerabilities allowed attackers to:
We also found that Dify's file parsing stack ran a version of PDFium vulnerable to CVE-2024-5846, a known use-after-free vulnerability, for more than 18 months after disclosure. Any end user could trigger it by uploading a malicious PDF.
All findings were reported to Dify under responsible disclosure. CVE-2026-41947, CVE-2026-41948, CVE-2026-41949, CVE-2026-41950 are patched in version 1.14.2.
These findings are the latest from Project DarkSide, our ongoing initiative exposing the hidden weaknesses in the building blocks of AI applications, and they follow ChainLeak, where Zafran Labs found two critical vulnerabilities in the Chainlit AI framework that exposed cloud API keys and enabled cloud takeover. Like ChainLeak, the Dify research points to a broader gap in how the industry secures AI systems. Many AI systems implement a microservices architecture, which makes heavy use of container images. Traditional scanners tend to miss application-level CVEs whenever the container holds a complex application or project instead of wrapping a simple binary or OS package.
As a direct answer, Zafran introduced "shadow container image component enrichment". This new feature applies our research by inferring the underlying application/project represented by the image, enriching the SBOM with the inferred component, and applying rigorous vulnerability matching against project-level CVEs. This ensures that critical application-level risks, previously invisible to image scanning, are reliably surfaced to security teams.
Dify is an open-source LLMOps platform. It enables the creation of AI applications, chatbots, and workflows through a visual low-code block interface, similar to N8N. It provides many features out-of-the-box, such as RAG pipelines and app tracing, and is highly customizable via an extensive plugin system. Dify also simplifies application operations by managing hosting, authentication, data persistence, and other infrastructural concerns.

Dify can be deployed locally, but it also offers a multi-tenant cloud deployment at cloud.dify.ai operating on a freemium model.
Currently, Dify has over 140K stars on GitHub and consistently ranks among the top 10 most popular AI repositories. The langgenius/dify-api image has been pulled from the docker hub over 10M times. A survey of publicly accessible systems identified tens of thousands internet-facing Dify instances including instances that are used by major enterprises.
According to ossinsight.io, Dify is mostly popular in China, as 55% of stargazers & 69% of the PR creators originate there.

Based on Dify’s blog, this is the general architecture of the platform:

Since Dify can be deployed in different ways depending on their customers decision we differentiate and mapped the attack entry point on different deployments before diving into the vulnerability research process. Dify has SaaS deployment and local deployment including an enterprise product that includes proprietary code delivered as binary.
This is a public, multi-tenant cloud environment. The main entry points are the shared APIs and cloud infrastructure. Because multiple organizations share the same backend, a severe vulnerability here carries the risk of a cross-tenant data leak, where one company's data could be exposed to another. 3 out of 4 vulnerabilities we found have an increased impact in the Dify SaaS solution causing different types of cross tenant impact.
This is a single-tenant version typically running on a single server. The entry point is isolated entirely to that specific tenant and your local network. While a compromised user could attack other users within that same instance, the breach is confined to your local tenant. In this deployment the amount of resources and private data that is integrated to the local solution is usually extremely sensitive, including proprietary customer data. For this deployment a user might use vulnerabilities to attack and leak sensitive information from users across the tenant.
Our research revealed 4 vulnerabilities, including 2 critical severity vulnerabilities (CVSS of 9.4 and 9.1). After understanding the impact and risk of each Dify deployment and the general architecture of the system we would dive into the technical part of the system, and explain our logic vulnerabilities, including the methodology and research surrounding the vulnerabilities we found.
Tracing refers to the profiling and monitoring of AI applications - allowing developers to track processing times, token usage, and more. There are various tracing providers, such as Opik or LangSmith. Those providers may receive traces from multiple applications, and allow visualization and analytics of the raw data. Crucially, traces contain the actual messages and responses sent to and from the model.
Dify allows you to configure tracing for your application by simply providing authentication details to a tracing provider; Dify then handles the actual tracing.
All endpoints relevant to configuring tracing do not validate the sender’s tenant. You can send requests to these endpoints for any application hosted on the Dify instance, as long as you have a Dify console user - a workspace user that can create, manage, or monitor applications. In the Dify cloud deployment, this is trivial, as anyone can sign up to the platform.
To configure tracing, you need an App ID, which is a Dify internal identifier. You may get it by accessing the victim application as a client - Dify will provide a JWT containing this internal App ID.

Consequently, an attacker can configure their own tracing for any application they can access as a client, which includes all publicly accessible applications. This allows an attacker to create a persistent exfiltration channel for all messages and responses sent in the application.
A significant portion of Dify’s functionality relies on a rich plugin ecosystem. Plugins handle interactions with Model Providers (like Ollama), add logic (like Rate Limiting) that can be used as workflow blocks, and provide tools such as Wikipedia search, Slack integration, or GitHub Webhook listeners. While the Dify Marketplace offers many official plugins, signed by Dify, users can also write, install, and debug their own plugins. For more information about the plugin system, see the Dify Blog.
The service responsible for managing and running these is the Plugin Daemon. It exposes an internal API for system use. For example, when a workflow uses a plugin, the API Service sends an execution request to the Plugin Daemon including the invocation arguments and the plugin ID. Tenant separation in the daemon is done by including a Tenant ID in the URL, which is populated and validated by the API Service.

We discovered two primitives that allow access to arbitrary endpoints within the Plugin Daemon: one via GET and one via POST.
To fetch a plugin icon, the client sends a GET request to the following URL (handled by the API Service):
https://<DIFY_HOST>/console/api/workspaces/current/plugin/icon?tenant_id=<UUID>&filename=<UUID>.svg
This request is converted into a GET request to the internal Plugin Daemon:
http://<INTERNAL_PLUGIN_DAEMON_HOST>:5002/plugin/<TENANT_ID>/asset/<FILENAME>
The Plugin Daemon returns the file bytes as an Octet Stream, which the API Service passes back to the client.
Note that the filename query parameter is injected as a path segment in the internal URL. A client can use it to perform a path traversal and reach any internal API endpoint - the backend does not enforce any limitations on the filename value.
# api/core/plugin/impl/asset.py
def fetch_asset(self, tenant_id: str, id: str) -> bytes:
response = self._request(method="GET", path=f"plugin/{tenant_id}/asset/{id}")
return response.content
# api/core/plugin/impl/base.py
def _request(self, method: str, path: str, ...) -> httpx.Response:
url, headers, prepared_data, params, files = self._prepare_request(path, ...)
response = httpx.request(method=method, url=url, ...)
return response
plugin_daemon_inner_api_baseurl = URL(str(dify_config.PLUGIN_DAEMON_URL))
def _prepare_request(self, path: str, ...) -> tuple[str, ...]:
url = plugin_daemon_inner_api_baseurl / path
...
return str(url), ...
Example: Setting filename to ../../../debug/pprof results in a request to: http://<INTERNAL_PLUGIN_DAEMON_HOST>:5002/debug/pprof
Since the expected response format is an Octet Stream, any data received is passed back to the client. However, we are limited to requests without a payload, since the icon request to the internal API does not have one, and we cannot pass query parameters (the ? character is treated as part of the path).
The vulnerability is amplified by additional flaws in this endpoint:
Tenant ID can be provided by the user as well. Besides it being another surface for the path traversal, an attacker can request icons of another tenant even without the path traversal.The POST primitive exploits a similar flaw in the Task Deletion endpoint (used, for example, when canceling a plugin installation).
The client sends a request to: https://<DIFY_HOST>/console/api/workspaces/current/plugin/tasks/<TASK_ID>/delete/<IDENTIFIER>
This is converted to: http://<INTERNAL_PLUGIN_DAEMON_HOST>:5002/plugin/<TENANT_ID>/management/install/tasks/<TASK_ID>/delete/<IDENTIFIER>
Because this parameter is passed within the URL in the client-to-API communication, we must use URL Encoding (e.g., %2E%2E/%2E%2E/delete_all).
This primitive does not allow the attacker to see the response because the API Service handler expects a boolean return value, and will throw an error upon receiving unexpected data. However, the request is still processed by the Daemon.
Those primitives grant access to internal, private endpoints in the Plugin Daemon. In addition, an attacker can affect other tenant’s environments by supplying their IDs in the path traversal. One can learn a Tenant ID by, for example, uploading a file to one of the tenant’s apps - the response will contain it.
However, most of the existing endpoints aren’t reachable considering the limitations of the primitives. The current impact is limited in scope, primarily allowing access to debug/pprof for performance data.
With this in mind, this is still a fundamental architectural flaw; any new or changed endpoint in the Plugin Daemon could become a high-severity vulnerability.
When a client uploads a file to a Dify app, it hits the /api/files/upload endpoint. The handler generates a UUID, saves the data using some storage layer (S3, etc.), and writes the file metadata to the Upload Files table. It then sends the generated UUID back to the client.
If the file has been uploaded to be sent with a message, the client receives the UUID, and appends it to the files array of the /api/chat-messages payload.
During handling of the message, the relationship between the file and the message is stored in the Message Files table.

When revisiting a conversation, the client fetches the conversation’s messages. The handler will query Message Files and generate a viewable link for each relevant file. These links have an expiration time, which defaults to 5 minutes, and are cryptographically signed upon generation. The link also contains a randomly generated nonce - however, the code doesn’t use it as a number-used-once as expected, and links can be accessed many times until their expiry.

Essentially, file permissions are derived from message permissions - if you can't see the conversation, you can't generate a link to the file. We found two primitives that exploit the fact that this permission check is indirect, allowing access via UUID alone, bypassing all link protections.
UUIDs can leak through independent vulnerabilities, lateral movement, or access to a client’s browser history/cache. Remember, the files’ contents aren’t saved on the DB - so if, for example, an attacker exploits a DB read vulnerability, he will be able to escalate it to read file contents.
Dify supports RAG via Knowledge objects. Console users can upload various formats (PDF, TXT, etc.) which Dify then chunks and processes to use in a pipeline. The upload itself happens via the /console/api/files/upload endpoint, which has practically identical handling to /api/files/upload.
During the process of knowledge creation, the user may want to view the uploaded documents. This is done via the /console/files/<FILE_ID>/preview endpoint.
The endpoint takes a UUID and retrieves the first 3,000 characters. It performs no permission checks on the UUID, verifying only that the file type is a "Document."
This allows any console user (which is, once again, trivial to obtain in the cloud deployment) to preview any document in the system, including Cross-Tenant access.
When a client sends a message with an attached file UUID, the system records the link in Message Files. However, it does not validate that the UUID doesn’t already belong to another message. It only validates the Tenant ID.
Therefore, an attacker can attach an existing UUID (belonging to someone else) to a message they send to a chatbot. If the chatbot has file-reading capabilities, an attacker can simply prompt it to return the file’s contents. For example, given a document, the attacker can prompt "Repeat the contents of this file exactly."
This allows any client with access to the tenant’s app to read any file under it.
During our research of the Dify file handling, we noted that it can parse a wide variety of files. For example, the preview endpoint may extract text from HTML, PDF, Microsoft Office formats and more. This extensive support, however, may also be used as an attack vector.
For example, the preview endpoint uses pypdfium2 to parse PDF files, which uses Chromium’s PDFium binary behind the scenes. Up until December 21, 2025, it used version 4.30.0 of the library, which made use of the 126.0.6462.0 version of the binary. This version was susceptible to security vulnerabilities, such as CVE-2024-5846, a Use-After-Free vulnerability published to NVD on June 11, 2024.
This means that the Dify cluster was vulnerable to a known CVE for more than a year and a half. All it would take for an attacker to gain access is to upload a malicious PDF, and render it through the preview endpoint.
On a wider scale, many AI applications face the same danger. Those applications support parsing of many file formats from untrusted sources, allowing any end user to attempt and trigger known vulnerabilities in programs such as PDFium, ffmpeg or others. Besides bumping versions regularly, applications should also sandbox these kinds of operations.
Our investigation revealed a significant gap in container security: Some container images won’t have their main application detected as a component by traditional cloud scanners, and therefore, vulnerabilities in it will be missed. This happens whenever the container image employs a complex build pattern, instead of simply wrapping a binary or package. These scanners primarily identify what can be derived from structured image contents like libraries and packages.
Take Dify as an example. Its build process simply copies unpackaged Python code directly into the container image. In addition, two container images are built from the repo’s source code, “dify-web” and “dify-api”, while vulnerabilities are reported against "Dify" on the project level. As a result, the standard scanners fail to make the connections, and severe vulnerabilities remain completely invisible to security teams.
To address this, we developed a methodology to attach application-level CVEs to container image assets. This solution operates without the need for hardcoded logic per project or a dependency on public container registries.
As part of our research we added a “shadow container image component enrichment” step that implements this methodology. Zafran infers what application/project the image represents, and runs our existing vulnerability matching on it. If we find matches, we store them as an additional enrichment result tied to the image, so customers now have full visibility into the risk posed by their container images.

Traditional vulnerability management must change. So many are drowning in detections, and still lack insights. The time-to-exploit window sits at 5 days. Implementing a Continuous Threat Exposure Management (CTEM) program is the path forward. Moving from vulnerability management to CTEM doesn't have to be complicated. This guide outlines steps you can take to begin, continue, or refine your CTEM journey.
