Langflow CVE-2025-34291: KEV Origin Validation Exposure
CISA added Langflow CVE-2025-34291 to KEV on 2026-05-21. The issue combines permissive CORS and credentialed refresh-token behavior; this article provides dependency, container, HTTP telemetry, and token-abuse audit scripts.
On this page 0% read
Executive Summary
CISA added CVE-2025-34291 to KEV on 2026-05-21 with a due date of 2026-06-04 CISA KEV. CISA describes an origin validation error where permissive CORS and a refresh token cookie configured as SameSite=None allow credentialed cross-origin refresh endpoint requests, enabling authenticated follow-on actions CISA KEV.
NVD lists vulnerable Langflow CPE entries through 1.6.9, while the vendor release reference in CISA KEV points to v1.9.3 NVD, Langflow release.
Key Facts
cve: "CVE-2025-34291"
vendor: "Langflow"
product: "Langflow"
kev_added: "2026-05-21"
kev_due: "2026-06-04"
vulnerability: "Origin validation error with credentialed cross-origin refresh-token requests"
cwe: ["CWE-346"]
nvd_vulnerable_cpe: "cpe:2.3:a:langflow:langflow:*:*:*:*:*:*:*:*"
nvd_vulnerable_version_end_including: "1.6.9"
vendor_reference_release: "v1.9.3"
cvss_v31: "8.8 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
cvss_v40_secondary: "9.4 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H"
exploitation_status: "cisa_kev_exploited"
zero_day_status: "unproven_from_public_primary_sources"
Source Confidence & Evidence Mapping
- confirmed: CISA KEV lists CVE-2025-34291 as known exploited and describes the CORS plus refresh-token failure mode CISA KEV.
- confirmed: NVD lists CWE-346, CVSS 3.1 score 8.8, and vulnerable CPE through 1.6.9 NVD.
- confirmed: CISA KEV references Langflow v1.9.3 and GitHub issue 11465 as vendor context Langflow release, Langflow issue.
- unclear: Public primary sources do not provide a single stable refresh endpoint path or a complete list of exploited IPs.
Impact Determination
| Classification | Criteria | Required evidence | Remediation trigger | Closure condition |
|---|---|---|---|---|
| Confirmed compromise | HTTP or application telemetry shows credentialed cross-origin refresh behavior followed by authenticated Langflow API activity on a vulnerable deployment. | Version evidence, Origin/Cookie/refresh telemetry, session or token activity, and affected host identity. | Preserve Langflow app logs, reverse-proxy logs, deployment manifests, and secrets available to the Langflow process. | Version is fixed or removed and downstream token/API audit shows no unexplained authenticated activity. |
| Presumed exposed | Langflow version is <= 1.6.9 by NVD CPE or below vendor reference release 1.9.3 with browser-reachable deployment. | Lockfile, package inventory, container tag, scanner row, or runtime package output. | Keep the deployment in scope until version closure succeeds. | Script output proves version is outside the NVD vulnerable range and at or above the vendor reference release used for closure. |
| Potentially exposed | Langflow appears in source, images, manifests, or scanner exports but version or browser exposure is unknown. | Repository, image, Kubernetes, CMDB, or scanner evidence naming langflow. | Collect runtime version and exposure evidence. | Asset resolves to confirmed compromise, presumed exposed, not exposed, or unknown. |
| Not exposed | No Langflow package, image, deployment, scanner row, or CVE-2025-34291 selector appears in complete exports. | Negative outputs from repository, image, Kubernetes, and scanner collection. | None for this CVE. | Evidence bundle covers source, build artifacts, containers, and deployed workloads. |
| Unknown | Runtime version, exposure, or HTTP telemetry is unavailable. | Gap statement naming the unavailable source. | Keep externally reachable Langflow deployments in scope. | Evidence is recovered or the risk owner accepts the named gap. |
Timeline
- 2025-12-05: NVD publication timestamp for CVE-2025-34291 NVD.
- 2026-05-21: CISA adds CVE-2025-34291 to KEV with due date 2026-06-04 CISA KEV.
- 2026-05-21: CISA KEV catalog points defenders to Langflow v1.9.3 and issue 11465 Langflow release.
What Happened
The exploitable condition is credentialed cross-origin access to Langflow refresh behavior, not a package compromise. The practical scoping anchors are langflow package versions, container images, exposed Langflow services, and HTTP telemetry containing Origin, credential cookies, and refresh-token activity.
Technical Analysis
Langflow deployments often hold model-provider credentials, workflow secrets, and API tokens. A successful refresh-token abuse path can convert a browser interaction into authenticated Langflow API access. The scripts below classify dependencies, containers, and HTTP telemetry without assuming a vendor route that public primary sources do not provide.
Affected Assets and Blast Radius
asset_selectors:
- "langflow"
- "CVE-2025-34291"
- "CWE-346"
- "v1.9.3"
version_selectors:
nvd_vulnerable_end_including: "1.6.9"
vendor_reference_release: "1.9.3"
credentials_and_data_at_risk:
- "Langflow refresh tokens"
- "authenticated Langflow API tokens"
- "model provider secrets available to Langflow"
- "workflow execution credentials"
Indicators And Detection Selectors
cves: ["CVE-2025-34291"]
packages: ["langflow"]
fixed_or_reference_versions: ["1.9.3"]
http_selectors:
- "Origin"
- "Cookie"
- "SameSite=None"
- "refresh token"
- "refresh_token"
- "Langflow"
Detection and Hunting
#!/usr/bin/env python3
import json
import os
import re
import sys
from pathlib import Path
ROOT = Path(os.environ.get("ROOT", sys.argv[1] if len(sys.argv) > 1 else ".")).resolve()
TELEMETRY_DIR = Path(os.environ.get("TELEMETRY_DIR", "telemetry-export")).resolve()
OUT = Path(os.environ.get("OUT", "hp-langflow-cve-2025-34291-scope")).resolve()
CVE = "CVE-2025-34291"
NVD_VULN_END = "1.6.9"
REFERENCE_RELEASE = "1.9.3"
SELECTORS = [CVE, "langflow", "Langflow", "CWE-346", "SameSite=None", "refresh token", "refresh_token", "Origin", "Cookie"]
def vt(value):
return tuple(int(x) for x in re.findall(r"\d+", str(value))[:4])
def le(left, right):
l, r = vt(left), vt(right)
width = max(len(l), len(r), 1)
return l + (0,) * (width - len(l)) <= r + (0,) * (width - len(r))
def lt(left, right):
l, r = vt(left), vt(right)
width = max(len(l), len(r), 1)
return l + (0,) * (width - len(l)) < r + (0,) * (width - len(r))
def read_text(path):
try:
return path.read_text(encoding="utf-8", errors="ignore")
except Exception:
return ""
OUT.mkdir(parents=True, exist_ok=True)
results = {"dependency_hits": [], "container_or_manifest_hits": [], "http_telemetry_hits": []}
version_patterns = [
re.compile(r"^\s*langflow\s*==\s*([0-9][0-9A-Za-z.\-+]*)", re.I | re.M),
re.compile(r'name\s*=\s*"langflow".{0,200}?version\s*=\s*"([^"]+)"', re.I | re.S),
re.compile(r"langflow[:=@\\s-]+v?([0-9]+\.[0-9]+\.[0-9]+)", re.I),
]
for path in list(ROOT.rglob("requirements*.txt")) + list(ROOT.rglob("pyproject.toml")) + list(ROOT.rglob("poetry.lock")) + list(ROOT.rglob("uv.lock")):
if any(part in {".git", "node_modules", ".venv", "dist"} for part in path.parts):
continue
body = read_text(path)
if "langflow" not in body.lower():
continue
versions = sorted({m.group(1).lstrip("v") for p in version_patterns for m in p.finditer(body)})
results["dependency_hits"].append({
"file": str(path),
"package": "langflow",
"versions": versions,
"cve": CVE,
"nvd_vulnerable_versions": [v for v in versions if le(v, NVD_VULN_END)],
"below_reference_release": [v for v in versions if lt(v, REFERENCE_RELEASE)],
})
for path in list(ROOT.rglob("Dockerfile*")) + list(ROOT.rglob("*.yaml")) + list(ROOT.rglob("*.yml")) + list(ROOT.rglob("*.json")):
body = read_text(path)
if "langflow" in body.lower() or CVE in body:
results["container_or_manifest_hits"].append({"file": str(path), "selector": "langflow-or-cve"})
if TELEMETRY_DIR.exists():
for path in TELEMETRY_DIR.rglob("*"):
if path.is_file():
for line_no, line in enumerate(read_text(path).splitlines(), start=1):
if ("langflow" in line.lower() or CVE in line) and ("Origin" in line or "Cookie" in line or "refresh" in line.lower()):
results["http_telemetry_hits"].append({"file": str(path), "line": line_no, "evidence": line[:1000]})
(OUT / "langflow-cve-2025-34291-scope.json").write_text(json.dumps(results, indent=2, sort_keys=True), encoding="utf-8")
# Positive signal: langflow <= 1.6.9, langflow below 1.9.3, or HTTP telemetry containing Langflow plus Origin/Cookie/refresh selectors.
print(json.dumps({"out": str(OUT), "dependency_hits": len(results["dependency_hits"]), "http_hits": len(results["http_telemetry_hits"])}, indent=2))
Patch, Mitigation, and Verification
#!/usr/bin/env python3
import json
import os
import re
import subprocess
from pathlib import Path
OUT = Path(os.environ.get("OUT", "hp-langflow-cve-2025-34291-closure")).resolve()
CVE = "CVE-2025-34291"
NVD_VULN_END = "1.6.9"
REFERENCE_RELEASE = "1.9.3"
SOURCE = "https://github.com/langflow-ai/langflow/releases/tag/v1.9.3"
def vt(value):
return tuple(int(x) for x in re.findall(r"\d+", str(value))[:4])
def ge(left, right):
l, r = vt(left), vt(right)
width = max(len(l), len(r), 1)
return l + (0,) * (width - len(l)) >= r + (0,) * (width - len(r))
OUT.mkdir(parents=True, exist_ok=True)
result = {"cve": CVE, "reference_release": REFERENCE_RELEASE, "source": SOURCE, "python_runtime": [], "docker_images": []}
pip_cmds = [["python3", "-m", "pip", "show", "langflow"], ["python", "-m", "pip", "show", "langflow"]]
for cmd in pip_cmds:
try:
proc = subprocess.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=20)
except Exception as exc:
result["python_runtime"].append({"cmd": cmd, "error": str(exc)})
continue
version = ""
for line in proc.stdout.splitlines():
if line.lower().startswith("version:"):
version = line.split(":", 1)[1].strip()
if version:
result["python_runtime"].append({
"cmd": cmd,
"installed_version": version,
"nvd_vulnerable_end_including": NVD_VULN_END,
"vendor_reference_release": REFERENCE_RELEASE,
"at_or_above_reference_release": ge(version, REFERENCE_RELEASE),
})
try:
proc = subprocess.run(["docker", "images", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30)
for line in proc.stdout.splitlines():
if "langflow" in line.lower():
result["docker_images"].append({"image": line, "selector": "langflow"})
except Exception as exc:
result["docker_error"] = str(exc)
(OUT / "langflow-cve-2025-34291-version-verification.json").write_text(json.dumps(result, indent=2, sort_keys=True), encoding="utf-8")
# Remediation trigger: any runtime or image below Langflow 1.9.3, especially versions <= 1.6.9, remains open for CVE-2025-34291.
print(json.dumps({"out": str(OUT), "runtime_checks": len(result["python_runtime"]), "docker_hits": len(result["docker_images"])}, indent=2))
Downstream Abuse Audits
#!/usr/bin/env python3
import json
import os
import sys
from pathlib import Path
TELEMETRY_DIR = Path(os.environ.get("TELEMETRY_DIR", sys.argv[1] if len(sys.argv) > 1 else "telemetry-export")).resolve()
OUT = Path(os.environ.get("OUT", "hp-langflow-cve-2025-34291-downstream")).resolve()
CVE = "CVE-2025-34291"
SELECTORS = [
"CVE-2025-34291",
"langflow",
"Langflow",
"refresh_token",
"refresh token",
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"AZURE_OPENAI_API_KEY",
"GOOGLE_API_KEY",
"LANGFLOW_SECRET_KEY",
"SameSite=None",
"Origin",
]
if not TELEMETRY_DIR.exists():
raise SystemExit(f"TELEMETRY_DIR not found: {TELEMETRY_DIR}")
OUT.mkdir(parents=True, exist_ok=True)
matches = []
for path in TELEMETRY_DIR.rglob("*"):
if not path.is_file():
continue
for line_no, line in enumerate(path.read_text(encoding="utf-8", errors="ignore").splitlines(), start=1):
for selector in SELECTORS:
if selector in line:
matches.append({"cve": CVE, "file": str(path), "line": line_no, "selector": selector, "evidence": line[:1000]})
(OUT / "langflow-cve-2025-34291-downstream-selectors.json").write_text(json.dumps(matches, indent=2, sort_keys=True), encoding="utf-8")
# Positive signal: post-exposure Langflow refresh activity, model-provider key exposure, or authenticated API activity tied to a vulnerable deployment.
# Remediation trigger: any Langflow token or provider key selector after the first vulnerable exposure keeps Langflow secrets and connected services in scope.
print(json.dumps({"out": str(OUT), "matches": len(matches)}, indent=2))