This is a stolen valet ticket that lets the attacker come back later and drive off the whole garage
CVE-2025-34291 is a *chain*, not a single bug: Langflow's default CORS posture (LANGFLOW_CORS_ORIGINS=* with credentials allowed), a cross-site refresh-token cookie posture, and a refresh flow without meaningful CSRF resistance let an attacker-controlled website mint fresh tokens for a victim's existing Langflow session. Those tokens can then be used against authenticated endpoints, including Langflow's code-validation path, turning a browser-origin bug into full server-side code execution. Authoritative metadata from NVD/GHSA says affected versions are Langflow up to and including 1.6.9.
The vendor's HIGH 8.8 is directionally right, but the v3 vector hides the real shape of the attack. This is *not* a clean unauthenticated socket-level RCE you can spray across the internet; it needs a reachable Langflow instance, unsafe CORS/cookie behavior, and a logged-in victim browser. That friction pushes it down from CRITICAL in real enterprise operations, but KEV status, observed exploitation, and Langflow's habit of storing high-value API keys drag it right back to the top of the HIGH bucket.
4 steps from start to impact.
Find a reachable Langflow and a live user session
- Langflow is reachable from the victim browser
- A user has an active Langflow session or refresh cookie
- The attacker can get that user to load attacker-controlled web content
- No direct exploit if nobody is logged in
- Many enterprises do not expose Langflow broadly
- Phishing, watering-hole, or ad-path delivery is still required
Abuse credentialed CORS to steal fresh tokens
LANGFLOW_CORS_ALLOW_CREDENTIALS=True- CORS origins are wildcard or otherwise too broad
- Refresh cookie/session settings permit cross-site delivery
- Refresh endpoint lacks an effective CSRF barrier
- Any explicit origin allowlist breaks the easy path
SameSite=LaxorStrictmaterially reduces exploitability- Reverse proxies that normalize or block hostile Origin headers can help
Replay the token against authenticated code-execution features
POST /api/v1/validate/code; once the attacker holds the victim's token or API-equivalent session, they can reach the same authenticated surfaces the user can.- Stolen token remains valid
- Victim account can reach authenticated backend endpoints
- Langflow instance exposes the relevant API paths
- Short token lifetimes reduce the window
- Least-privileged users may have narrower workspace access
- Backend-only or heavily proxied deployments can limit reachable surfaces
/api/v1/validate/code, /build, or account/API-key operations from new IPs, ASNs, or user agents.Execute code and harvest downstream secrets
- Langflow host/container can execute the submitted code path
- Useful credentials or network reachability exist on the host/workspace
- Outbound network access is available for payload retrieval or exfiltration
- Container isolation and seccomp can reduce host impact
- Egress controls and metadata blocking slow post-exploitation
- Secret scoping can limit downstream blast radius
The supporting signals.
| In-the-wild status | Confirmed active exploitation. CrowdSec says exploitation was observed starting 2026-01-23, and NVD now flags the CVE as being in CISA's KEV catalog. |
|---|---|
| KEV status | KEV-listed: YES. NVD records CISA adding it on 2026-05-21 with a federal due date of 2026-06-04. |
| Proof-of-concept / scanner availability | Public detection content exists. GitHub/OSV tracks the advisory as GHSA-577h-p2hh-v4mv, and ProjectDiscovery shipped a Nuclei template for CVE-2025-34291, lowering discovery and validation friction. |
| EPSS | User-supplied EPSS: 0.32059. That's elevated enough to take seriously even before KEV; third-party mirrors have shown it in roughly the mid-90th percentile range, but EPSS is daily and moves. |
| CVSS reality check | Vendor v3: 8.8 HIGH (AV:N/AC:L/PR:L/UI:N/...) overstates how "direct" this is. The exploit described by NVD/Obsidian is closer to no prior auth but does require victim browser interaction, which is why the real-world score stays HIGH, not CRITICAL. |
| Affected versions | Authoritative affected range: Langflow <= 1.6.9 in NVD and GHSA/OSV. |
| Fixed version | Patch signal is messy. CSA says Langflow addressed this in 1.9.3, while Obsidian's disclosure notes said 1.6.9 remained exploitable and that 1.7 would change defaults. Treat 1.9.3+ as the safe target, but note the public metadata is not perfectly aligned. |
| Exposure / scanning data | Internet exposure is real. In a prior Censys Langflow advisory, Censys observed 1,156 exposed Langflow servers online. That's product-level exposure data rather than CVE-specific measurement, but it tells you this is not a niche lab-only footprint. |
| Disclosure / research | Disclosed: 2025-12-05. Researcher disclosure and technical write-up came from Obsidian Security, with VulnCheck as the CVE source shown by NVD. |
| Threat-model nuance | This is browser-mediated exploitation. CrowdSec notes observed source IPs skew residential because the victim's browser executes the attack, which makes classic perimeter correlation noisier than with direct RCE scanning. |
noisgate verdict.
The single biggest downward pressure is the logged-in victim browser requirement: this is not a point-and-shoot unauthenticated daemon exploit. The single biggest upward pressure is KEV-listed active exploitation against a secret-rich platform that can turn one stolen session into code execution plus credential theft across downstream SaaS and cloud systems.
Why this verdict
- Downward pressure: requires a live user session and browser interaction — the attacker needs a victim already authenticated to Langflow and able to reach the instance, which narrows reach versus true unauthenticated RCE.
- Downward pressure: exposed population is smaller than generic CVSS implies — only deployments with reachable Langflow plus unsafe CORS/cookie settings are in play, and many enterprises should already have these tools behind a proxy or dev network boundary.
- Upward pressure: KEV plus real campaigns outweigh the friction — exploitation has been observed since 2026-01-23, CISA added it to KEV on 2026-05-21, and the post-exploitation blast radius is unusually high because Langflow stores reusable API keys and executes code by design.
Why not higher?
I am not calling this CRITICAL because the exploit chain is not a clean, unauthenticated network RCE. It depends on a victim browser, a valid session, and a reachable instance with unsafe defaults, which means reachability and reliability are materially lower than something like a one-request pre-auth service exploit.
Why not lower?
I am not dropping this to MEDIUM because the end state is still full code execution and likely secret theft, not a narrow account-only issue. KEV listing and observed exploitation remove the usual benefit of the doubt; attackers are already proving the chain works in real environments.
What to do — in priority order.
- Disable credentialed cross-origin requests — Set
LANGFLOW_CORS_ALLOW_CREDENTIALS=Falseimmediately where operationally possible. Because this CVE is KEV-listed/actively exploited, deploy this within hours; it severs the easiest token-theft leg of the chain while patching is organized. - Replace wildcard origins with an explicit allowlist — Set
LANGFLOW_CORS_ORIGINSto only the exact frontend origins that must talk to Langflow. Do this within hours for exposed instances; wildcard origins are the hinge that makes the browser side of the exploit cheap. - Put Langflow behind a real access boundary — Move exposed instances behind a reverse proxy with authentication, network ACLs, or VPN/identity-aware proxy controls so arbitrary internet traffic cannot reach the app directly. For this KEV item, do it within hours for any internet-facing deployment that cannot be patched immediately.
- Rotate stored secrets after suspected exposure — If an instance was internet-reachable or handled untrusted user browsing since 2026-01-23, assume workspace secrets may be burned and rotate Langflow-held API keys, cloud tokens, and database credentials. Start within hours on exposed/high-value instances because patching does not un-leak already stolen tokens.
- Monitor and alert on refresh and code-validation abuse — Create detections for unusual
/api/v1/refreshtraffic, unexpectedOriginheaders, and follow-on calls to/api/v1/validate/codeor build endpoints from new IPs/user agents. Stand this up within hours for exposed instances to catch replay and post-exploitation while remediation proceeds.
- MFA alone doesn't stop reuse of a stolen live session token once the browser has already authenticated.
- A generic WAF rule set alone is weak here because the exploit uses legitimate browser behavior and authenticated API calls after token theft.
- Only patching the frontend cookie code is insufficient if backend cookie/CORS behavior remains permissive; the chain is a configuration and request-validation problem, not just a UI bug.
- Relying on EDR alone is too late; it may catch the payload stage, but it does not prevent session hijack or token theft.
Crowdsourced verification payload.
Run this on the Langflow host or inside the Langflow container. Invoke it as bash verify_cve_2025_34291.sh /path/to/.env (example: docker exec langflow bash /tmp/verify_cve_2025_34291.sh /app/.env). It needs only local read access to the installed langflow package metadata and, if provided, the Langflow .env file; root is not required unless app files are restricted.
#!/usr/bin/env bash
# verify_cve_2025_34291.sh
# Detect likely exposure state for CVE-2025-34291 on a Langflow host/container.
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 1=vulnerable, 0=patched, 2=unknown
set -u
ENV_FILE="${1:-}"
VERSION=""
CORS_ORIGINS=""
CORS_ALLOW_CREDENTIALS=""
STATUS="UNKNOWN"
REASON=""
trim() {
local s="$1"
s="${s#${s%%[![:space:]]*}}"
s="${s%${s##*[![:space:]]}}"
printf '%s' "$s"
}
norm_bool() {
local v
v="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
case "$v" in
true|1|yes|y|on) printf 'true' ;;
false|0|no|n|off) printf 'false' ;;
*) printf '' ;;
esac
}
verlte() {
# returns 0 if $1 <= $2
[ "$1" = "$2" ] && return 0
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]
}
vergte() {
# returns 0 if $1 >= $2
[ "$1" = "$2" ] && return 0
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n1)" = "$1" ]
}
load_env_file() {
local f="$1"
[ -f "$f" ] || return 0
while IFS='=' read -r k v; do
k="$(trim "$k")"
v="$(trim "$v")"
[ -z "$k" ] && continue
case "$k" in
\#*) continue ;;
export\ *) k="${k#export }" ;;
esac
v="${v%\r}"
v="${v%\"}"
v="${v#\"}"
v="${v%\'}"
v="${v#\'}"
case "$k" in
LANGFLOW_CORS_ORIGINS) CORS_ORIGINS="$v" ;;
LANGFLOW_CORS_ALLOW_CREDENTIALS) CORS_ALLOW_CREDENTIALS="$v" ;;
esac
done < "$f"
}
get_version() {
if command -v python3 >/dev/null 2>&1; then
VERSION="$(python3 - <<'PY' 2>/dev/null
from importlib import metadata
try:
print(metadata.version('langflow'))
except Exception:
pass
PY
)"
fi
if [ -z "$VERSION" ] && command -v pip >/dev/null 2>&1; then
VERSION="$(pip show langflow 2>/dev/null | awk -F': ' '/^Version: /{print $2; exit}')"
fi
if [ -z "$VERSION" ] && command -v pip3 >/dev/null 2>&1; then
VERSION="$(pip3 show langflow 2>/dev/null | awk -F': ' '/^Version: /{print $2; exit}')"
fi
}
# Prefer explicit env file if provided, otherwise fall back to current environment.
if [ -n "$ENV_FILE" ]; then
load_env_file "$ENV_FILE"
fi
[ -z "$CORS_ORIGINS" ] && CORS_ORIGINS="${LANGFLOW_CORS_ORIGINS:-}"
[ -z "$CORS_ALLOW_CREDENTIALS" ] && CORS_ALLOW_CREDENTIALS="${LANGFLOW_CORS_ALLOW_CREDENTIALS:-}"
CORS_ALLOW_CREDENTIALS="$(norm_bool "$CORS_ALLOW_CREDENTIALS")"
get_version
RISKY_CORS="false"
if [ "${CORS_ORIGINS}" = "*" ] && [ "${CORS_ALLOW_CREDENTIALS}" = "true" ]; then
RISKY_CORS="true"
fi
if [ -n "$VERSION" ]; then
if verlte "$VERSION" "1.6.9"; then
STATUS="VULNERABLE"
REASON="Installed langflow version $VERSION is within authoritative affected range (<= 1.6.9)."
elif vergte "$VERSION" "1.9.3"; then
STATUS="PATCHED"
REASON="Installed langflow version $VERSION is at or above the documented security target 1.9.3."
if [ "$RISKY_CORS" = "true" ]; then
REASON="$REASON Warning: CORS is still configured as wildcard + credentials, which is dangerous even if this CVE is patched."
fi
else
STATUS="UNKNOWN"
REASON="Installed langflow version $VERSION is newer than 1.6.9 but below 1.9.3; public advisories are inconsistent on the exact fixed boundary."
if [ "$RISKY_CORS" = "true" ]; then
REASON="$REASON Risky CORS configuration is also present."
fi
fi
else
STATUS="UNKNOWN"
REASON="Could not determine installed langflow version from python package metadata."
if [ "$RISKY_CORS" = "true" ]; then
REASON="$REASON Risky CORS configuration (wildcard origins + credentials) is present."
fi
fi
printf '%s\n' "$STATUS"
printf '%s\n' "$REASON"
case "$STATUS" in
PATCHED) exit 0 ;;
VULNERABLE) exit 1 ;;
*) exit 2 ;;
esac
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.