This is a hotel guest key that can be tricked into opening the staff hallway if you already got through the lobby
CVE-2026-41948 is a path traversal in Dify's proxying to the Plugin Daemon internal REST API. Affected builds are Dify 1.14.1 and earlier. The vulnerable flow forwards caller-controlled path material to the plugin daemon without adequately rejecting traversal segments, so an attacker can use crafted task identifiers or filename parameters to step outside the intended tenant-scoped path and hit internal endpoints such as debug surfaces.
The vendor/NVD-style 9.4 CRITICAL rating is too hot for most enterprise patch queues. The real-world chain requires an attacker to first become an authenticated Dify user, then know or discover a victim tenant UUID, then hit the specific proxyable path in a way that survives normalization. That is still bad in shared Dify environments because it breaks tenant isolation, but it is not equivalent to anonymous one-packet internet compromise. The one major amplifier is that Dify Cloud allows free self-registration, which weakens the usual value of the auth prerequisite.
4 steps from start to impact.
Get a Dify account
curl.- Dify instance is reachable to the attacker
- Attacker can self-register or otherwise obtain a valid user session
- Many self-hosted enterprise deployments sit behind SSO, VPN, or private ingress
- If invite-only signup is enforced, the bug becomes post-auth instead of internet-sprayable
Identify a victim tenant UUID
Burp Repeater or curl. This is where GitHub's AC:H view makes more practical sense than the flat AC:L baseline.- Attacker has a valid session
- Attacker can learn or infer a target tenant UUID
- UUID discovery is a real prerequisite and may require tenant-specific recon
- Well-isolated tenants and minimal metadata leakage slow this step
Abuse the plugin-daemon forwarder
#35796 joins a caller-supplied path onto the plugin daemon's inner API URL without rejecting traversal segments first. Using Burp Repeater, curl, or a custom script, the attacker supplies .. or encoded dot-segments in task identifiers or filename parameters so the forwarded request escapes the expected tenant-scoped namespace.- The target is running Dify <= 1.14.1 or lacks the guard from PR #35796
- The relevant plugin/file proxy route is exposed to the attacker's user role
- Traversal must survive decoding and routing quirks
- Modern reverse proxies or custom request filters may already reject obvious
..sequences
.., %2e%2e, or other suspicious path-segment anomalies in plugin/file routes. Scanner coverage is likely uneven because the advisory still lists patched versions as unknown.Reach internal daemon endpoints and cross tenant boundaries
- Traversal request is accepted and forwarded
- The internal endpoint reached exposes useful data or control surfaces
- Impact depends on what internal endpoints are reachable from the forwarded namespace
- This is a tenant-isolation failure, not a demonstrated root-on-host exploit
The supporting signals.
| In-the-wild status | No public exploitation evidence found in reviewed sources. I found no CISA KEV entry and no campaign reporting tied to this CVE. |
|---|---|
| Proof-of-concept availability | No reliable public weaponized PoC found. Huntr is referenced by NVD/VulnCheck, but the publicly retrievable details were limited; PR #35796 exposes the fix logic and enough detail for a competent operator to recreate the attack. |
| EPSS | 0.079% (23rd percentile) on the GitHub advisory page, sourced from FIRST. That is very low and argues against mass attacker interest right now. |
| KEV status | Not KEV-listed in the CISA KEV catalog as reviewed. |
| CVSS disagreement | Important split: NVD/CVE metadata shows 9.4 / AV:N/AC:L/PR:N/UI:N, while the GitHub advisory scores CVSS v4 at 9.2 with AC:H. The GitHub view better matches the real exploit chain because target-specific UUID knowledge and path-shaping are not zero-friction. |
| Affected versions | Dify <= 1.14.1 according to NVD, VulnCheck, and GitHub advisory references. |
| Fixed version | Publicly ambiguous. PR #35796 merged on 2026-05-25, but the latest public release page in reviewed sources was v1.14.2 on 2026-05-19, and the GitHub advisory still says patched versions: unknown. Verify code presence, not just tag names. |
| Exposure shape | Dify docs show the plugin daemon defaults to http://plugin_daemon:5002, i.e. an internal service-to-service path, which limits direct internet reach. However, Dify also documents optional remote debugging on port 5003 and exposed host mappings, so misconfigured self-hosted installs can widen exposure. |
| Disclosure and credits | Disclosed 2026-05-18. VulnCheck credits Ido Shani and Gal Zaban of Zafran Security. |
| Population / prevalence | Dify is not niche hobbyware: the GitHub repo shows ~143k stars in reviewed sources. That raises operational importance, even though I found no authoritative Shodan/Censys count specific to this flaw. |
noisgate verdict.
The decisive downward pressure is that exploitation is not meaningfully pre-auth in most enterprise deployments: the attacker needs a valid Dify user context and target-specific tenant knowledge before the traversal matters. It stays HIGH because the bug breaks tenant isolation and can expose internal plugin-daemon functionality in a platform that is often used as a shared service.
Why this verdict
- Auth requirement is real downward pressure — in self-hosted enterprises this usually implies prior access, SSO enrollment, or invitation. Vendor
PR:Nis technically defensible for Dify Cloud self-registration, but it overstates exposure for private deployments. - Target-knowledge raises friction — the attacker needs the victim tenant UUID and a path that breaks out of the intended namespace. GitHub's advisory modeling this as
AC:Hmatches field reality better than a flat low-complexity assumption. - Impact is still serious once it lands — this is not cosmetic traversal. It can cross tenant boundaries into internal plugin-daemon/debug surfaces, which makes the blast radius meaningful inside shared Dify estates.
Why not higher?
I did not find KEV listing, public campaign reporting, or a broadly circulating turnkey exploit. More importantly, this is not demonstrated host-level RCE and it usually requires either self-registration or pre-existing user access plus target-specific recon, which keeps it below the 'drop everything' bucket.
Why not lower?
Free self-registration on Dify Cloud weakens the protection normally provided by authentication, so calling this merely medium would understate cloud/SaaS risk. And if you run Dify as a shared internal platform, tenant-boundary failures are exactly the sort of bug that turns one low-privilege foothold into cross-workspace data exposure.
What to do — in priority order.
- Disable open signup and tighten identity gates — If your deployment permits self-registration, stop treating this as a harmless auth-required bug. Move Dify behind SSO, invitation-only access, or VPN where possible; for a HIGH verdict, deploy this control within 30 days.
- Block traversal patterns at the reverse proxy — Reject requests containing
..,%2e%2e, mixed-encoding dot segments, and suspicious filename/task identifier patterns on Dify API routes that touch plugin or file proxying. This is not a perfect fix, but it meaningfully cuts exploit reliability; deploy within 30 days. - Keep plugin-daemon and debug ports private — Dify docs show internal daemon traffic on
5002and optional debugging on5003. Ensure those ports are not internet-exposed and are reachable only from the app tier or explicit admin workflows; validate within 30 days. - Hunt for tenant-crossing path probes — Search ingress, app, and plugin-daemon logs for dot-segment traversal markers and for one account referencing multiple tenant UUIDs. This is your best immediate signal of attempted exploitation; turn on monitoring within 30 days.
- Relying on the plugin daemon's shared secret alone doesn't solve this, because the vulnerable path is forwarded by the trusted Dify application on the attacker's behalf.
- Patching or removing individual plugins is not the fix; the flaw is in the proxying path handling before plugin-daemon requests are made.
- A generic perimeter WAF with no application-specific path normalization rules may miss encoded or route-specific traversal variants.
Crowdsourced verification payload.
Run this on a self-hosted Dify app host or container filesystem where the Dify source tree is present. Invoke it as bash verify_cve_2026_41948.sh /path/to/dify (example: bash verify_cve_2026_41948.sh /app/dify). It needs read-only access to the Dify tree; no root is required unless your container filesystem permissions demand it.
#!/usr/bin/env bash
# verify_cve_2026_41948.sh
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN
set -euo pipefail
ROOT="${1:-.}"
FILE="$ROOT/api/core/plugin/impl/base.py"
semver_lte() {
# returns 0 if $1 <= $2
local a b IFS=.
read -r -a a <<< "${1#v}"
read -r -a b <<< "${2#v}"
for i in 0 1 2; do
local ai="${a[$i]:-0}"
local bi="${b[$i]:-0}"
if (( ai < bi )); then return 0; fi
if (( ai > bi )); then return 1; fi
done
return 0
}
if [[ ! -f "$FILE" ]]; then
echo "UNKNOWN - expected file not found: $FILE"
exit 2
fi
# The merged fix in PR #35796 adds a traversal guard in api/core/plugin/impl/base.py.
if grep -q "Invalid plugin daemon path: traversal sequence detected" "$FILE" \
|| grep -q "traversal sequence detected" "$FILE"; then
echo "PATCHED - traversal guard string found in $FILE"
exit 0
fi
if grep -q "unquote(path)" "$FILE" && grep -q "%2e%2e" "$FILE"; then
echo "PATCHED - path decode and encoded-dot traversal checks found in $FILE"
exit 0
fi
VERSION=""
if command -v git >/dev/null 2>&1 && [[ -d "$ROOT/.git" ]]; then
VERSION="$(git -C "$ROOT" describe --tags --exact-match 2>/dev/null || true)"
fi
if [[ -n "$VERSION" ]]; then
if semver_lte "$VERSION" "1.14.1"; then
echo "VULNERABLE - version $VERSION and no traversal guard found"
exit 1
else
echo "UNKNOWN - version $VERSION is newer than 1.14.1, but guard pattern was not found; validate whether the fix was backported differently"
exit 2
fi
fi
echo "UNKNOWN - no git tag found and no known fix pattern detected; compare api/core/plugin/impl/base.py against PR #35796"
exit 2
If you remember one thing.
#35796's guard is present instead of trusting release labels. For this HIGH verdict, use the noisgate mitigation SLA to put compensating controls in place within 30 days and the noisgate remediation SLA to move to a vendor build that definitively contains the fix within 180 days; given the public fixed-version ambiguity, your remediation should include a code-level validation step before you close the ticket.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.